Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

DEVPROD-940: Surface repotracker error on Project Health page #2150

Merged
merged 7 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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