diff --git a/cypress/constants/index.ts b/cypress/constants/index.ts
new file mode 100644
index 0000000000..b93b86707e
--- /dev/null
+++ b/cypress/constants/index.ts
@@ -0,0 +1,2 @@
+export const EVG_BASE_URL = "http://localhost:9090";
+export const GQL_URL = `${EVG_BASE_URL}/graphql/query`;
diff --git a/cypress/integration/nav_bar.ts b/cypress/integration/nav_bar.ts
index c5ebb3a876..b910aa2128 100644
--- a/cypress/integration/nav_bar.ts
+++ b/cypress/integration/nav_bar.ts
@@ -1,3 +1,5 @@
+import { EVG_BASE_URL } from "../constants";
+
const PATCH_ID = "5e4ff3abe3c3317e352062e4";
const USER_ID = "admin";
const SPRUCE_URLS = {
@@ -6,9 +8,10 @@ const SPRUCE_URLS = {
cli: `/preferences/cli`,
};
const LEGACY_URLS = {
- version: `/version/${PATCH_ID}`,
- userPatches: `/patches/user/${USER_ID}`,
- distros: `/distros`,
+ version: `${EVG_BASE_URL}/version/${PATCH_ID}`,
+ userPatches: `${EVG_BASE_URL}/patches/user/${USER_ID}`,
+ distros: `${EVG_BASE_URL}/distros`,
+ admin: `${EVG_BASE_URL}/admin`,
};
describe("Nav Bar", () => {
const projectCookie = "mci-project-cookie";
@@ -16,9 +19,11 @@ describe("Nav Bar", () => {
it("Should have a nav bar linking to the proper page on the legacy UI", () => {
cy.visit(SPRUCE_URLS.version);
cy.dataCy("legacy-ui-link").should("exist");
- cy.dataCy("legacy-ui-link")
- .should("have.attr", "href")
- .and("include", LEGACY_URLS.version);
+ cy.dataCy("legacy-ui-link").should(
+ "have.attr",
+ "href",
+ LEGACY_URLS.version
+ );
});
it("Navigating to a different page should change the nav link to the legacy UI", () => {
cy.visit(SPRUCE_URLS.version);
@@ -28,9 +33,11 @@ describe("Nav Bar", () => {
.and("include", LEGACY_URLS.version);
cy.visit(SPRUCE_URLS.userPatches);
cy.dataCy("legacy-ui-link").should("exist");
- cy.dataCy("legacy-ui-link")
- .should("have.attr", "href")
- .and("include", LEGACY_URLS.userPatches);
+ cy.dataCy("legacy-ui-link").should(
+ "have.attr",
+ "href",
+ LEGACY_URLS.userPatches
+ );
});
it("Visiting a page with no legacy equivalent should not display a nav link", () => {
cy.visit(SPRUCE_URLS.cli);
@@ -41,9 +48,7 @@ describe("Nav Bar", () => {
cy.dataCy("legacy_route").should("not.exist");
cy.dataCy("auxiliary-dropdown-link").click();
cy.dataCy("legacy_route").should("exist");
- cy.dataCy("legacy_route")
- .should("have.attr", "href")
- .and("include", LEGACY_URLS.distros);
+ cy.dataCy("legacy_route").should("have.attr", "href", LEGACY_URLS.distros);
});
it("Nav Dropdown should link to patches page of most recent project if cookie exists", () => {
cy.setCookie(projectCookie, "spruce");
@@ -100,4 +105,33 @@ describe("Nav Bar", () => {
);
cy.getCookie(projectCookie).should("have.property", "value", "spruce");
});
+
+ describe("Admin settings", () => {
+ it("Should not show Admin button to non-admins", () => {
+ const userData = {
+ data: {
+ user: {
+ userId: "admin",
+ displayName: "Evergreen Admin",
+ emailAddress: "admin@evergreen.com",
+ permissions: {
+ canEditAdminSettings: false,
+ },
+ },
+ },
+ };
+ cy.overwriteGQL("User", userData);
+ cy.visit(SPRUCE_URLS.version);
+ cy.dataCy("user-dropdown-link").click();
+ cy.dataCy("admin-link").should("not.exist");
+ });
+
+ it("Should show Admin button to admins", () => {
+ cy.visit(SPRUCE_URLS.version);
+ cy.dataCy("user-dropdown-link").click();
+ cy.dataCy("admin-link")
+ .should("be.visible")
+ .should("have.attr", "href", LEGACY_URLS.admin);
+ });
+ });
});
diff --git a/cypress/integration/version/action_buttons.ts b/cypress/integration/version/action_buttons.ts
index 1750a88d40..2d3c19ed19 100644
--- a/cypress/integration/version/action_buttons.ts
+++ b/cypress/integration/version/action_buttons.ts
@@ -51,7 +51,7 @@ describe("Action Buttons", () => {
it("Clicking 'Set Priority' button shows popconfirm with input and toast on success", () => {
const priority = "99";
cy.dataCy("prioritize-patch").click();
- cy.dataCy("patch-priority-input").type(priority).type("{enter}");
+ cy.dataCy("patch-priority-input").type(`${priority}{enter}`);
cy.validateToast("success", priority);
});
diff --git a/cypress/integration/version/unscheduled_patch/configure_patch.ts b/cypress/integration/version/unscheduled_patch/configure_patch.ts
index 45e0da27e3..e8f215760a 100644
--- a/cypress/integration/version/unscheduled_patch/configure_patch.ts
+++ b/cypress/integration/version/unscheduled_patch/configure_patch.ts
@@ -1,4 +1,5 @@
-import { hasOperationName, GQL_URL } from "../../../utils/graphql-test-utils";
+import { GQL_URL } from "../../../constants";
+import { hasOperationName } from "../../../utils/graphql-test-utils";
import { mockErrorResponse } from "../../../utils/mockErrorResponse";
describe("Configure Patch Page", () => {
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index 83fb2affbd..3fdb8998fe 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -1,3 +1,6 @@
+import { EVG_BASE_URL, GQL_URL } from "../constants";
+import { hasOperationName } from "../utils/graphql-test-utils";
+
const user = {
username: "admin",
password: "password",
@@ -61,14 +64,14 @@ Cypress.Commands.add("getInputByLabel", (label: string) => {
Cypress.Commands.add("login", () => {
cy.getCookie("mci-token").then((c) => {
if (!c) {
- cy.request("POST", "http://localhost:9090/login", { ...user });
+ cy.request("POST", `${EVG_BASE_URL}/login`, { ...user });
}
});
});
/* logout */
Cypress.Commands.add("logout", () => {
- cy.origin("http://localhost:9090", () => {
+ cy.origin(EVG_BASE_URL, () => {
cy.request({ url: "/logout", followRedirect: false });
});
});
@@ -113,3 +116,13 @@ Cypress.Commands.add(
});
}
);
+
+Cypress.Commands.add("overwriteGQL", (operationName: string, body: any) => {
+ cy.intercept("POST", GQL_URL, (req) => {
+ if (hasOperationName(req, operationName)) {
+ req.reply((res) => {
+ res.body = body;
+ });
+ }
+ });
+});
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
index 7d3f6af7e6..7e4938f400 100644
--- a/cypress/support/e2e.ts
+++ b/cypress/support/e2e.ts
@@ -114,6 +114,12 @@ declare global {
message?: string,
shouldClose?: boolean
): void;
+ /**
+ * Custom command to overwrite a GQL response
+ * @param operationName - The operation name of the query
+ * @param body - The replacement response body
+ */
+ overwriteGQL(operationName: string, body: any);
}
}
}
diff --git a/cypress/utils/graphql-test-utils.ts b/cypress/utils/graphql-test-utils.ts
index 062d85141f..8c7f60ae20 100644
--- a/cypress/utils/graphql-test-utils.ts
+++ b/cypress/utils/graphql-test-utils.ts
@@ -30,5 +30,3 @@ export const aliasMutation = (
req.alias = `gql${operationName}Mutation`;
}
};
-
-export const GQL_URL = "http://localhost:9090/graphql/query";
diff --git a/cypress/utils/mockErrorResponse.ts b/cypress/utils/mockErrorResponse.ts
index e3bb2d1c20..3ce16a730e 100644
--- a/cypress/utils/mockErrorResponse.ts
+++ b/cypress/utils/mockErrorResponse.ts
@@ -1,4 +1,5 @@
-import { GQL_URL, hasOperationName } from "./graphql-test-utils";
+import { hasOperationName } from "./graphql-test-utils";
+import { GQL_URL } from "../constants";
interface Args {
errorMessage: string;
diff --git a/src/analytics/navbar/useNavbarAnalytics.ts b/src/analytics/navbar/useNavbarAnalytics.ts
index 46db8bdc8a..71c5c25841 100644
--- a/src/analytics/navbar/useNavbarAnalytics.ts
+++ b/src/analytics/navbar/useNavbarAnalytics.ts
@@ -1,6 +1,7 @@
import { useAnalyticsRoot } from "analytics/useAnalyticsRoot";
type Action =
+ | { name: "Click Admin Link" }
| { name: "Click Legacy UI Link" }
| { name: "Click Logo Link" }
| { name: "Click Waterfall Link" }
diff --git a/src/components/Header/NavDropdown/index.tsx b/src/components/Header/NavDropdown/index.tsx
index b9fe0156ec..9c38ff02ee 100644
--- a/src/components/Header/NavDropdown/index.tsx
+++ b/src/components/Header/NavDropdown/index.tsx
@@ -12,7 +12,7 @@ const NavDropdownMenuIcon: React.FC<{ open: boolean }> = ({ open }) => (
);
-interface MenuItemType {
+export interface MenuItemType {
"data-cy"?: string;
text: string;
href?: string;
diff --git a/src/components/Header/UserDropdown.tsx b/src/components/Header/UserDropdown.tsx
index eec1b6cd99..aac144d773 100644
--- a/src/components/Header/UserDropdown.tsx
+++ b/src/components/Header/UserDropdown.tsx
@@ -1,20 +1,21 @@
import { useQuery } from "@apollo/client";
import { useNavbarAnalytics } from "analytics";
+import { adminSettingsURL } from "constants/externalResources";
import { PreferencesTabRoutes, getPreferencesRoute } from "constants/routes";
import { useAuthDispatchContext } from "context/auth";
import { UserQuery } from "gql/generated/types";
import { GET_USER } from "gql/queries";
-import { NavDropdown } from "./NavDropdown";
+import { MenuItemType, NavDropdown } from "./NavDropdown";
export const UserDropdown = () => {
const { data } = useQuery(GET_USER);
const { user } = data || {};
- const { displayName } = user || {};
+ const { displayName, permissions } = user || {};
const { logoutAndRedirect } = useAuthDispatchContext();
const { sendEvent } = useNavbarAnalytics();
- const menuItems = [
+ const menuItems: MenuItemType[] = [
{
text: "Preferences",
to: getPreferencesRoute(PreferencesTabRoutes.Profile),
@@ -31,7 +32,14 @@ export const UserDropdown = () => {
onClick: () => logoutAndRedirect(),
},
];
-
+ if (permissions?.canEditAdminSettings) {
+ menuItems.splice(1, 0, {
+ "data-cy": "admin-link",
+ text: "Admin",
+ href: adminSettingsURL,
+ onClick: () => sendEvent({ name: "Click Admin Link" }),
+ });
+ }
return (
= {
userId: "admin",
displayName: "Evergreen Admin",
emailAddress: "admin@evergreen.com",
+ permissions: {
+ canEditAdminSettings: true,
+ },
},
},
},
diff --git a/src/gql/queries/get-user.graphql b/src/gql/queries/get-user.graphql
index e0ee7e3034..33d97d438b 100644
--- a/src/gql/queries/get-user.graphql
+++ b/src/gql/queries/get-user.graphql
@@ -2,6 +2,9 @@ query User {
user {
displayName
emailAddress
+ permissions {
+ canEditAdminSettings
+ }
userId
}
}