From f14f680c0042ce5f0975ab1398b7ffdbd12b5cd1 Mon Sep 17 00:00:00 2001 From: SupaJoon Date: Mon, 18 Sep 2023 11:25:13 -0400 Subject: [PATCH] EVG-20177: Show Admin button to EVG admins (#2035) --- cypress/constants/index.ts | 2 + cypress/integration/nav_bar.ts | 58 +++++++++++++++---- cypress/integration/version/action_buttons.ts | 2 +- .../unscheduled_patch/configure_patch.ts | 3 +- cypress/support/commands.ts | 17 +++++- cypress/support/e2e.ts | 6 ++ cypress/utils/graphql-test-utils.ts | 2 - cypress/utils/mockErrorResponse.ts | 3 +- src/analytics/navbar/useNavbarAnalytics.ts | 1 + src/components/Header/NavDropdown/index.tsx | 2 +- src/components/Header/UserDropdown.tsx | 16 +++-- src/constants/externalResources.ts | 2 + src/gql/generated/types.ts | 2 + src/gql/mocks/getUser.ts | 3 + src/gql/queries/get-user.graphql | 3 + 15 files changed, 98 insertions(+), 24 deletions(-) create mode 100644 cypress/constants/index.ts 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 } }