From 8a62eccdef121e7a41a805552fa69c5373aa9c43 Mon Sep 17 00:00:00 2001 From: BrunoQuaresma Date: Thu, 13 Mar 2025 13:25:44 +0000 Subject: [PATCH] Apply Kaylas suggestion --- .../JobRow.stories.tsx | 24 +--- .../JobRow.tsx | 5 +- ...rganizationProvisionerJobsPage.stories.tsx | 113 ----------------- .../OrganizationProvisionerJobsPage.tsx | 115 ++---------------- ...izationProvisionerJobsPageView.stories.tsx | 77 ++++++++++++ .../OrganizationProvisionerJobsPageView.tsx | 113 +++++++++++++++++ 6 files changed, 209 insertions(+), 238 deletions(-) delete mode 100644 site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.stories.tsx create mode 100644 site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx create mode 100644 site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.tsx diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.stories.tsx index 67f760a463..35818baeed 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.stories.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.stories.tsx @@ -30,12 +30,6 @@ type Story = StoryObj; export const Close: Story = {}; -export const Open: Story = { - args: { - defaultOpen: true, - }, -}; - export const OpenOnClick: Story = { play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); @@ -46,29 +40,19 @@ export const OpenOnClick: Story = { const jobId = canvas.getByText(args.job.id); expect(jobId).toBeInTheDocument(); }, - parameters: { - chromatic: { - disableSnapshot: true, - }, - }, }; export const HideOnClick: Story = { - args: { - defaultOpen: true, - }, play: async ({ canvasElement, args }) => { const canvas = within(canvasElement); - const showMoreButton = canvas.getByRole("button", { name: /hide/i }); + const showMoreButton = canvas.getByRole("button", { name: /show more/i }); await userEvent.click(showMoreButton); + const hideButton = canvas.getByRole("button", { name: /hide/i }); + await userEvent.click(hideButton); + const jobId = canvas.queryByText(args.job.id); expect(jobId).not.toBeInTheDocument(); }, - parameters: { - chromatic: { - disableSnapshot: true, - }, - }, }; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx index 2d84fe9625..9c7aecbba5 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/JobRow.tsx @@ -16,12 +16,11 @@ import { Tag, Tags, TruncateTags } from "./Tags"; type JobRowProps = { job: ProvisionerJob; - defaultOpen?: boolean; }; -export const JobRow: FC = ({ job, defaultOpen }) => { +export const JobRow: FC = ({ job }) => { const metadata = job.metadata; - const [isOpen, setIsOpen] = useState(defaultOpen); + const [isOpen, setIsOpen] = useState(false); return ( <> diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.stories.tsx deleted file mode 100644 index 3347f2f1bb..0000000000 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.stories.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import { expect, userEvent, waitFor, within } from "@storybook/test"; -import type { ProvisionerJob } from "api/typesGenerated"; -import { OrganizationSettingsContext } from "modules/management/OrganizationSettingsLayout"; -import { - MockOrganization, - MockOrganizationPermissions, - MockProvisionerJob, -} from "testHelpers/entities"; -import { daysAgo } from "utils/time"; -import OrganizationProvisionerJobsPage from "./OrganizationProvisionerJobsPage"; - -const defaultOrganizationSettingsValue = { - organization: MockOrganization, - organizationPermissionsByOrganizationId: {}, - organizations: [MockOrganization], - organizationPermissions: MockOrganizationPermissions, -}; - -const meta: Meta = { - title: "pages/OrganizationProvisionerJobsPage", - component: OrganizationProvisionerJobsPage, - decorators: [ - (Story, { parameters }) => ( - - - - ), - ], - args: { - getProvisionerJobs: async () => MockProvisionerJobs, - }, - parameters: { - organizationSettingsValue: defaultOrganizationSettingsValue, - }, -}; - -export default meta; -type Story = StoryObj; - -export const Default: Story = {}; - -export const OrganizationNotFound: Story = { - parameters: { - organizationSettingsValue: { - ...defaultOrganizationSettingsValue, - organization: null, - }, - }, -}; - -export const Loading: Story = { - args: { - getProvisionerJobs: () => - new Promise((res) => { - setTimeout(res, 100_000); - }), - }, -}; - -export const LoadingError: Story = { - args: { - getProvisionerJobs: async () => { - throw new Error("Failed to load jobs"); - }, - }, -}; - -export const RetryAfterError: Story = { - args: { - getProvisionerJobs: (() => { - let count = 0; - - return async () => { - count++; - - if (count === 1) { - throw new Error("Failed to load jobs"); - } - - return MockProvisionerJobs; - }; - })(), - }, - play: async ({ canvasElement }) => { - const canvas = within(canvasElement); - const retryButton = await canvas.findByRole("button", { name: "Retry" }); - - userEvent.click(retryButton); - - await waitFor(() => { - const rows = canvasElement.querySelectorAll("tbody > tr"); - expect(rows).toHaveLength(MockProvisionerJobs.length); - }); - }, -}; - -export const Empty: Story = { - args: { - getProvisionerJobs: async () => [], - }, -}; - -const MockProvisionerJobs: ProvisionerJob[] = Array.from( - { length: 50 }, - (_, i) => ({ - ...MockProvisionerJob, - id: i.toString(), - created_at: daysAgo(2), - }), -); diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.tsx index bf02b44572..bae561c4a9 100644 --- a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.tsx +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPage.tsx @@ -1,116 +1,27 @@ -import type { API } from "api/api"; import { provisionerJobs } from "api/queries/organizations"; -import { Button } from "components/Button/Button"; -import { EmptyState } from "components/EmptyState/EmptyState"; -import { Link } from "components/Link/Link"; -import { Loader } from "components/Loader/Loader"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "components/Table/Table"; -import { - type OrganizationSettingsValue, - useOrganizationSettings, -} from "modules/management/OrganizationSettingsLayout"; +import { useOrganizationSettings } from "modules/management/OrganizationSettingsLayout"; import type { FC } from "react"; -import { Helmet } from "react-helmet-async"; import { useQuery } from "react-query"; -import { docs } from "utils/docs"; -import { pageTitle } from "utils/page"; -import { JobRow } from "./JobRow"; +import OrganizationProvisionerJobsPageView from "./OrganizationProvisionerJobsPageView"; -type OrganizationProvisionerJobsPageProps = { - getProvisionerJobs?: typeof API.getProvisionerJobs; -}; - -const OrganizationProvisionerJobsPage: FC< - OrganizationProvisionerJobsPageProps -> = ({ getProvisionerJobs }) => { +const OrganizationProvisionerJobsPage: FC = () => { const { organization } = useOrganizationSettings(); - - if (!organization) { - return ; - } - const { data: jobs, isLoadingError, refetch, - } = useQuery(provisionerJobs(organization.id, getProvisionerJobs)); + } = useQuery({ + ...provisionerJobs(organization?.id || ""), + enabled: organization !== undefined, + }); return ( - <> - - - {pageTitle( - "Provisioner Jobs", - organization.display_name || organization.name, - )} - - - -
-
-
-

Provisioner Jobs

-

- Provisioner Jobs are the individual tasks assigned to Provisioners - when the workspaces are being built.{" "} - View docs -

-
-
- - - - - Created - Type - Template - Tags - Status - - - - - {jobs ? ( - jobs.length > 0 ? ( - jobs.map((j) => ) - ) : ( - - - - - - ) - ) : isLoadingError ? ( - - - refetch()}> - Retry - - } - /> - - - ) : ( - - - - - - )} - -
-
- + ); }; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx new file mode 100644 index 0000000000..9b6a25a352 --- /dev/null +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.stories.tsx @@ -0,0 +1,77 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { expect, fn, userEvent, waitFor, within } from "@storybook/test"; +import type { ProvisionerJob } from "api/typesGenerated"; +import { MockOrganization, MockProvisionerJob } from "testHelpers/entities"; +import { daysAgo } from "utils/time"; +import OrganizationProvisionerJobsPageView from "./OrganizationProvisionerJobsPageView"; + +const MockProvisionerJobs: ProvisionerJob[] = Array.from( + { length: 50 }, + (_, i) => ({ + ...MockProvisionerJob, + id: i.toString(), + created_at: daysAgo(2), + }), +); + +const meta: Meta = { + title: "pages/OrganizationProvisionerJobsPage", + component: OrganizationProvisionerJobsPageView, + args: { + organization: MockOrganization, + jobs: MockProvisionerJobs, + onRetry: fn(), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const OrganizationNotFound: Story = { + args: { + organization: undefined, + }, +}; + +export const Loading: Story = { + args: { + jobs: undefined, + }, +}; + +export const LoadingError: Story = { + args: { + jobs: undefined, + error: new Error("Failed to load jobs"), + }, +}; + +export const RetryAfterError: Story = { + args: { + jobs: undefined, + error: new Error("Failed to load jobs"), + onRetry: fn(), + }, + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + const retryButton = await canvas.findByRole("button", { name: "Retry" }); + userEvent.click(retryButton); + + await waitFor(() => { + expect(args.onRetry).toHaveBeenCalled(); + }); + }, + parameters: { + chromatic: { + disableSnapshot: true, + }, + }, +}; + +export const Empty: Story = { + args: { + jobs: [], + }, +}; diff --git a/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.tsx b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.tsx new file mode 100644 index 0000000000..a9c189ee37 --- /dev/null +++ b/site/src/pages/OrganizationSettingsPage/OrganizationProvisionerJobsPage/OrganizationProvisionerJobsPageView.tsx @@ -0,0 +1,113 @@ +import { Button } from "components/Button/Button"; +import { EmptyState } from "components/EmptyState/EmptyState"; +import { Link } from "components/Link/Link"; +import { Loader } from "components/Loader/Loader"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "components/Table/Table"; +import type { FC } from "react"; +import { Helmet } from "react-helmet-async"; +import { docs } from "utils/docs"; +import { pageTitle } from "utils/page"; +import { JobRow } from "./JobRow"; +import type { ProvisionerJob, Organization } from "api/typesGenerated"; + +type OrganizationProvisionerJobsPageViewProps = { + jobs: ProvisionerJob[] | undefined; + organization: Organization | undefined; + error: unknown; + onRetry: () => void; +}; + +const OrganizationProvisionerJobsPageView: FC< + OrganizationProvisionerJobsPageViewProps +> = ({ jobs, organization, error, onRetry }) => { + if (!organization) { + return ( + <> + + {pageTitle("Provisioner Jobs")} + + + + ); + } + + return ( + <> + + + {pageTitle( + "Provisioner Jobs", + organization.display_name || organization.name, + )} + + + +
+
+
+

Provisioner Jobs

+

+ Provisioner Jobs are the individual tasks assigned to Provisioners + when the workspaces are being built.{" "} + View docs +

+
+
+ + + + + Created + Type + Template + Tags + Status + + + + + {jobs ? ( + jobs.length > 0 ? ( + jobs.map((j) => ) + ) : ( + + + + + + ) + ) : error ? ( + + + + Retry + + } + /> + + + ) : ( + + + + + + )} + +
+
+ + ); +}; + +export default OrganizationProvisionerJobsPageView;