Skip to content

Commit

Permalink
DEVPROD-940: Surface repotracker error on Project Health page (evergr…
Browse files Browse the repository at this point in the history
  • Loading branch information
minnakt authored Nov 22, 2023
1 parent e0a30c7 commit ecacc78
Show file tree
Hide file tree
Showing 15 changed files with 530 additions and 19 deletions.
27 changes: 27 additions & 0 deletions cypress/integration/projectHealth/project_banners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
describe("project banners", () => {
const projectWithRepotrackerError = "/commits/mongodb-mongo-test";

describe("repotracker banner", () => {
beforeEach(() => {
cy.visit(projectWithRepotrackerError);
});

it("should be able to clear the repotracker error", () => {
cy.dataCy("repotracker-error-banner").should("be.visible");
cy.dataCy("repotracker-error-trigger").should("be.visible");
cy.dataCy("repotracker-error-trigger").click();
cy.dataCy("repotracker-error-modal").should("be.visible");
cy.getInputByLabel("Base Revision").type(
"7ad0f0571691fa5063b757762a5b103999290fa8"
);
cy.contains("button", "Confirm").should(
"have.attr",
"aria-disabled",
"false"
);
cy.contains("button", "Confirm").click();
cy.validateToast("success", "Successfully updated merge base revision");
cy.dataCy("repotracker-error-banner").should("not.exist");
});
});
});
12 changes: 6 additions & 6 deletions src/components/Banners/PortalBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { createPortal } from "react-dom";
import { SiteBanner, SiteBannerProps } from "./SiteBanner";

interface PortalBannerProps extends SiteBannerProps {}
export const PortalBanner: React.FC<PortalBannerProps> = ({ text, theme }) => {
interface PortalBannerProps {
banner: React.ReactNode;
}

export const PortalBanner: React.FC<PortalBannerProps> = ({ banner }) => {
const bannerContainerEl = document.getElementById("banner-container");
return bannerContainerEl
? createPortal(<SiteBanner text={text} theme={theme} />, bannerContainerEl)
: null;
return bannerContainerEl ? createPortal(banner, bannerContainerEl) : null;
};
3 changes: 2 additions & 1 deletion src/components/Banners/ProjectBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "gql/generated/types";
import { PROJECT_BANNER } from "gql/queries";
import { PortalBanner } from "./PortalBanner";
import { SiteBanner } from "./SiteBanner";

interface ProjectBannerProps {
projectIdentifier: string;
Expand All @@ -23,5 +24,5 @@ export const ProjectBanner: React.FC<ProjectBannerProps> = ({
if (!text) {
return null;
}
return <PortalBanner theme={theme} text={text} />;
return <PortalBanner banner={<SiteBanner text={text} theme={theme} />} />;
};
240 changes: 240 additions & 0 deletions src/components/Banners/RepotrackerBanner.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import { MockedProvider } from "@apollo/client/testing";
import { RepotrackerBanner } from "components/Banners";
import { RenderFakeToastContext } from "context/toast/__mocks__";
import {
UserProjectSettingsPermissionsQuery,
UserProjectSettingsPermissionsQueryVariables,
RepotrackerErrorQuery,
RepotrackerErrorQueryVariables,
SetLastRevisionMutation,
SetLastRevisionMutationVariables,
} from "gql/generated/types";
import { SET_LAST_REVISION } from "gql/mutations";
import {
USER_PROJECT_SETTINGS_PERMISSIONS,
REPOTRACKER_ERROR,
} from "gql/queries";
import { render, screen, userEvent, waitFor } from "test_utils";
import { ApolloMock } from "types/gql";

describe("repotracker banner", () => {
beforeEach(() => {
const bannerContainer = document.createElement("div");
bannerContainer.setAttribute("id", "banner-container");
const body = document.body as HTMLElement;
body.appendChild(bannerContainer);
});

afterEach(() => {
const bannerContainer = document.getElementById("banner-container");
const body = document.body as HTMLElement;
body.removeChild(bannerContainer);
});

describe("repotracker error does not exist", () => {
it("does not render banner", async () => {
const { Component } = RenderFakeToastContext(
<MockedProvider mocks={[projectNoError]}>
<RepotrackerBanner projectIdentifier="evergreen" />
</MockedProvider>
);
render(<Component />);
await waitFor(() => {
expect(screen.queryByDataCy("repotracker-error-banner")).toBeNull();
});
});
});

describe("repotracker error exists", () => {
it("renders a banner", async () => {
const { Component } = RenderFakeToastContext(
<MockedProvider mocks={[projectWithError, adminUser]}>
<RepotrackerBanner projectIdentifier="evergreen" />
</MockedProvider>
);
render(<Component />);
await waitFor(() => {
expect(screen.queryByDataCy("repotracker-error-banner")).toBeVisible();
});
});

it("does not render modal trigger if user is not admin", async () => {
const { Component } = RenderFakeToastContext(
<MockedProvider mocks={[projectWithError, basicUser]}>
<RepotrackerBanner projectIdentifier="evergreen" />
</MockedProvider>
);
render(<Component />);
await waitFor(() => {
expect(screen.queryByDataCy("repotracker-error-banner")).toBeVisible();
});
expect(screen.queryByDataCy("repotracker-error-trigger")).toBeNull();
});

it("renders modal trigger if user is admin", async () => {
const { Component } = RenderFakeToastContext(
<MockedProvider mocks={[projectWithError, adminUser]}>
<RepotrackerBanner projectIdentifier="evergreen" />
</MockedProvider>
);
render(<Component />);
await waitFor(() => {
expect(screen.queryByDataCy("repotracker-error-banner")).toBeVisible();
});
expect(screen.queryByDataCy("repotracker-error-trigger")).toBeVisible();
});

it("can submit new base revision via modal", async () => {
const user = userEvent.setup();
const { Component, dispatchToast } = RenderFakeToastContext(
<MockedProvider
mocks={[projectWithError, adminUser, setLastRevision, projectNoError]}
>
<RepotrackerBanner projectIdentifier="evergreen" />
</MockedProvider>
);
render(<Component />);
await waitFor(() => {
expect(screen.queryByDataCy("repotracker-error-banner")).toBeVisible();
});
expect(screen.queryByDataCy("repotracker-error-trigger")).toBeVisible();

// Open modal.
await user.click(screen.queryByDataCy("repotracker-error-trigger"));
await waitFor(() => {
expect(screen.queryByDataCy("repotracker-error-modal")).toBeVisible();
});

// Submit new base revision.
const confirmButton = screen.getByRole("button", { name: "Confirm" });
expect(confirmButton).toHaveAttribute("aria-disabled", "true");
await user.type(screen.getByLabelText("Base Revision"), baseRevision);
expect(confirmButton).toHaveAttribute("aria-disabled", "false");
await user.click(confirmButton);
expect(dispatchToast.success).toHaveBeenCalledTimes(1);
});
});
});

const baseRevision = "7ad0f0571691fa5063b757762a5b103999290fa8";

const projectNoError: ApolloMock<
RepotrackerErrorQuery,
RepotrackerErrorQueryVariables
> = {
request: {
query: REPOTRACKER_ERROR,
variables: {
projectIdentifier: "evergreen",
},
},
result: {
data: {
project: {
__typename: "Project",
id: "evergreen",
branch: "",
repotrackerError: null,
},
},
},
};

const projectWithError: ApolloMock<
RepotrackerErrorQuery,
RepotrackerErrorQueryVariables
> = {
request: {
query: REPOTRACKER_ERROR,
variables: {
projectIdentifier: "evergreen",
},
},
result: {
data: {
project: {
__typename: "Project",
id: "evergreen",
branch: "main",
repotrackerError: {
__typename: "RepotrackerError",
exists: true,
invalidRevision: "invalid_revision",
},
},
},
},
};

const adminUser: ApolloMock<
UserProjectSettingsPermissionsQuery,
UserProjectSettingsPermissionsQueryVariables
> = {
request: {
query: USER_PROJECT_SETTINGS_PERMISSIONS,
variables: { projectIdentifier: "evergreen" },
},
result: {
data: {
user: {
__typename: "User",
userId: "admin",
permissions: {
__typename: "Permissions",
canCreateProject: true,
projectPermissions: {
__typename: "ProjectPermissions",
edit: true,
},
},
},
},
},
};

const basicUser: ApolloMock<
UserProjectSettingsPermissionsQuery,
UserProjectSettingsPermissionsQueryVariables
> = {
request: {
query: USER_PROJECT_SETTINGS_PERMISSIONS,
variables: { projectIdentifier: "evergreen" },
},
result: {
data: {
user: {
__typename: "User",
userId: "basic",
permissions: {
__typename: "Permissions",
canCreateProject: false,
projectPermissions: {
__typename: "ProjectPermissions",
edit: false,
},
},
},
},
},
};

const setLastRevision: ApolloMock<
SetLastRevisionMutation,
SetLastRevisionMutationVariables
> = {
request: {
query: SET_LAST_REVISION,
variables: {
projectIdentifier: "evergreen",
revision: baseRevision,
},
},
result: {
data: {
setLastRevision: {
__typename: "SetLastRevisionPayload",
mergeBaseRevision: baseRevision,
},
},
},
};
Loading

0 comments on commit ecacc78

Please sign in to comment.