From b527924c331c05c38e21ae15535aa4a0ae173a8b Mon Sep 17 00:00:00 2001 From: sophiabarness <80068149+sophiabarness@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:09:08 -0400 Subject: [PATCH] DEVPROD-7655: Create General table on Build Information page (#317) --- .../integration/image/build_information.ts | 19 ++++ apps/spruce/src/gql/generated/types.ts | 20 ++++ .../src/gql/queries/image-general.graphql | 12 ++ apps/spruce/src/gql/queries/index.ts | 2 + .../image/GeneralTable/GeneralTable.test.tsx | 88 +++++++++++++++ .../src/pages/image/GeneralTable/index.tsx | 103 ++++++++++++++++++ .../image/tabs/BuildInformationTab/index.tsx | 4 + 7 files changed, 248 insertions(+) create mode 100644 apps/spruce/src/gql/queries/image-general.graphql create mode 100644 apps/spruce/src/pages/image/GeneralTable/GeneralTable.test.tsx create mode 100644 apps/spruce/src/pages/image/GeneralTable/index.tsx diff --git a/apps/spruce/cypress/integration/image/build_information.ts b/apps/spruce/cypress/integration/image/build_information.ts index 2736933e2..ef71b01b6 100644 --- a/apps/spruce/cypress/integration/image/build_information.ts +++ b/apps/spruce/cypress/integration/image/build_information.ts @@ -1,4 +1,23 @@ describe("build information", () => { + describe("general", () => { + it("should show correct property values", () => { + cy.visit("/image/ubuntu2204"); + cy.dataCy("general-table-row").should("have.length", 4); + cy.dataCy("general-table-row") + .eq(0) + .should("contain.text", "Last deployed"); + cy.dataCy("general-table-row") + .eq(1) + .should("contain.text", "Amazon Machine Image (AMI)"); + cy.dataCy("general-table-row") + .eq(2) + .should("contain.text", "Latest task"); + cy.dataCy("general-table-row") + .eq(3) + .should("contain.text", "Latest task time"); + }); + }); + describe("distros", () => { it("should show the corresponding distros", () => { cy.visit("/image/ubuntu1804"); diff --git a/apps/spruce/src/gql/generated/types.ts b/apps/spruce/src/gql/generated/types.ts index dbbb42945..c63c5cf15 100644 --- a/apps/spruce/src/gql/generated/types.ts +++ b/apps/spruce/src/gql/generated/types.ts @@ -6286,6 +6286,26 @@ export type ImageEventsQuery = { } | null; }; +export type ImageGeneralQueryVariables = Exact<{ + imageId: Scalars["String"]["input"]; +}>; + +export type ImageGeneralQuery = { + __typename?: "Query"; + image?: { + __typename?: "Image"; + ami: string; + id: string; + lastDeployed: Date; + latestTask?: { + __typename?: "Task"; + execution: number; + finishTime?: Date | null; + id: string; + } | null; + } | null; +}; + export type ImagePackagesQueryVariables = Exact<{ imageId: Scalars["String"]["input"]; opts: PackageOpts; diff --git a/apps/spruce/src/gql/queries/image-general.graphql b/apps/spruce/src/gql/queries/image-general.graphql new file mode 100644 index 000000000..546af364b --- /dev/null +++ b/apps/spruce/src/gql/queries/image-general.graphql @@ -0,0 +1,12 @@ +query ImageGeneral($imageId: String!) { + image(imageId: $imageId) { + ami + id + lastDeployed + latestTask { + execution + finishTime + id + } + } +} diff --git a/apps/spruce/src/gql/queries/index.ts b/apps/spruce/src/gql/queries/index.ts index 42414d8c2..e2c4ce09b 100644 --- a/apps/spruce/src/gql/queries/index.ts +++ b/apps/spruce/src/gql/queries/index.ts @@ -25,6 +25,7 @@ import HOST from "./host.graphql"; import HOSTS from "./hosts.graphql"; import IMAGE_DISTROS from "./image-distros.graphql"; import IMAGE_EVENTS from "./image-events.graphql"; +import IMAGE_GENERAL from "./image-general.graphql"; import IMAGE_PACKAGES from "./image-packages.graphql"; import IMAGE_TOOLCHAINS from "./image-toolchains.graphql"; import IMAGES from "./images.graphql"; @@ -114,6 +115,7 @@ export { HOSTS, IMAGE_DISTROS, IMAGE_EVENTS, + IMAGE_GENERAL, IMAGE_PACKAGES, IMAGE_TOOLCHAINS, IMAGES, diff --git a/apps/spruce/src/pages/image/GeneralTable/GeneralTable.test.tsx b/apps/spruce/src/pages/image/GeneralTable/GeneralTable.test.tsx new file mode 100644 index 000000000..6b7dccbf4 --- /dev/null +++ b/apps/spruce/src/pages/image/GeneralTable/GeneralTable.test.tsx @@ -0,0 +1,88 @@ +import { MockedProvider } from "@apollo/client/testing"; +import { RenderFakeToastContext } from "context/toast/__mocks__"; +import { + ImageGeneralQuery, + ImageGeneralQueryVariables, +} from "gql/generated/types"; +import { IMAGE_GENERAL } from "gql/queries"; +import { + renderWithRouterMatch as render, + screen, + waitFor, + within, +} from "test_utils"; +import { ApolloMock } from "types/gql"; +import { GeneralTable } from "."; + +const wrapper = ({ children }: { children: React.ReactNode }) => ( + {children} +); + +describe("general table", () => { + it("displays correct information", async () => { + const { Component } = RenderFakeToastContext( + , + ); + render(, { wrapper }); + await waitFor(() => { + expect(screen.getAllByDataCy("general-table-row")).toHaveLength(4); + }); + + const lastDeployed = screen.getAllByDataCy("general-table-row")[0]; + expect(within(lastDeployed).getAllByRole("cell")[0]).toHaveTextContent( + "Last deployed", + ); + expect(within(lastDeployed).getAllByRole("cell")[1]).toHaveTextContent( + "Aug 7, 2021, 9:57:00 PM UTC", + ); + + const ami = screen.getAllByDataCy("general-table-row")[1]; + expect(within(ami).getAllByRole("cell")[0]).toHaveTextContent( + "Amazon Machine Image (AMI)", + ); + expect(within(ami).getAllByRole("cell")[1]).toHaveTextContent( + "ami-9809871715289206", + ); + + const latestTask = screen.getAllByDataCy("general-table-row")[2]; + expect(within(latestTask).getAllByRole("cell")[0]).toHaveTextContent( + "Latest task", + ); + expect(within(latestTask).getAllByRole("link")[0]).toHaveAttribute( + "href", + "/task/evergreen_ui_spruce_check_codegen_patch_b01dd570dbb2d060bf56d853e9eb1a71d60a6464_66be78cc4f92ba0007d94fcf_24_08_15_21_53_20", + ); + + const latestTaskTime = screen.getAllByDataCy("general-table-row")[3]; + expect(within(latestTaskTime).getAllByRole("cell")[0]).toHaveTextContent( + "Latest task time", + ); + expect(within(latestTaskTime).getAllByRole("cell")[1]).toHaveTextContent( + "Aug 8, 2021, 9:57:00 PM UTC", + ); + }); +}); + +const imageGeneralMock: ApolloMock< + ImageGeneralQuery, + ImageGeneralQueryVariables +> = { + request: { + query: IMAGE_GENERAL, + variables: { imageId: "ubuntu2204" }, + }, + result: { + data: { + image: { + id: "ubuntu2204", + ami: "ami-9809871715289206", + lastDeployed: new Date("2021-08-07T17:57:00-04:00"), + latestTask: { + finishTime: new Date("2021-08-08T17:57:00-04:00"), + id: "evergreen_ui_spruce_check_codegen_patch_b01dd570dbb2d060bf56d853e9eb1a71d60a6464_66be78cc4f92ba0007d94fcf_24_08_15_21_53_20", + execution: 0, + }, + }, + }, + }, +}; diff --git a/apps/spruce/src/pages/image/GeneralTable/index.tsx b/apps/spruce/src/pages/image/GeneralTable/index.tsx new file mode 100644 index 000000000..ecda9ae68 --- /dev/null +++ b/apps/spruce/src/pages/image/GeneralTable/index.tsx @@ -0,0 +1,103 @@ +import { useMemo, useRef } from "react"; +import { useQuery } from "@apollo/client"; +import { useLeafyGreenTable, LGColumnDef } from "@leafygreen-ui/table"; +import { StyledRouterLink, WordBreak } from "components/styles"; +import { BaseTable } from "components/Table/BaseTable"; +import { getTaskRoute } from "constants/routes"; +import { useToastContext } from "context/toast"; +import { + ImageGeneralQuery, + ImageGeneralQueryVariables, +} from "gql/generated/types"; +import { IMAGE_GENERAL } from "gql/queries"; +import { useDateFormat } from "hooks"; + +type GeneralInfo = { + property: string; + value: React.ReactNode | string; +}; + +type GeneralTableProps = { + imageId: string; +}; + +export const GeneralTable: React.FC = ({ imageId }) => { + const dispatchToast = useToastContext(); + const { data: imageData, loading } = useQuery< + ImageGeneralQuery, + ImageGeneralQueryVariables + >(IMAGE_GENERAL, { + variables: { imageId }, + onError(err) { + dispatchToast.error( + `There was an error loading the image general table: ${err.message}`, + ); + }, + }); + + const getDateCopy = useDateFormat(); + + const data = useMemo(() => { + const image = imageData?.image; + + return [ + { + property: "Last deployed", + value: image?.lastDeployed ? getDateCopy(image.lastDeployed) : "N/A", + }, + { + property: "Amazon Machine Image (AMI)", + value: image?.ami ?? "N/A", + }, + { + property: "Latest task", + value: image?.latestTask?.id ? ( + + {image.latestTask.id} + + ) : ( + "N/A" + ), + }, + { + property: "Latest task time", + value: image?.latestTask?.finishTime + ? getDateCopy(image.latestTask.finishTime) + : "N/A", + }, + ]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [imageData?.image]); + + const tableContainerRef = useRef(null); + const table = useLeafyGreenTable({ + columns, + data, + containerRef: tableContainerRef, + defaultColumn: { + enableColumnFilter: false, + }, + }); + + return ( + + ); +}; + +const columns: LGColumnDef[] = [ + { + header: "Property", + accessorKey: "property", + }, + { + header: "Value", + accessorKey: "value", + cell: ({ getValue }) => getValue(), + }, +]; diff --git a/apps/spruce/src/pages/image/tabs/BuildInformationTab/index.tsx b/apps/spruce/src/pages/image/tabs/BuildInformationTab/index.tsx index 6bd13e84a..4c5a68814 100644 --- a/apps/spruce/src/pages/image/tabs/BuildInformationTab/index.tsx +++ b/apps/spruce/src/pages/image/tabs/BuildInformationTab/index.tsx @@ -1,5 +1,6 @@ import { SpruceFormContainer } from "components/SpruceForm"; import { DistrosTable } from "pages/image/DistrosTable"; +import { GeneralTable } from "pages/image/GeneralTable"; import { PackagesTable } from "pages/image/PackagesTable"; import { ToolchainsTable } from "pages/image/ToolchainsTable"; @@ -11,6 +12,9 @@ export const BuildInformationTab: React.FC = ({ imageId, }) => ( <> + + +