mirror of
https://github.com/coder/coder.git
synced 2025-03-14 10:09:57 +00:00
Apply Kaylas suggestion
This commit is contained in:
@ -30,12 +30,6 @@ type Story = StoryObj<typeof JobRow>;
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -16,12 +16,11 @@ import { Tag, Tags, TruncateTags } from "./Tags";
|
||||
|
||||
type JobRowProps = {
|
||||
job: ProvisionerJob;
|
||||
defaultOpen?: boolean;
|
||||
};
|
||||
|
||||
export const JobRow: FC<JobRowProps> = ({ job, defaultOpen }) => {
|
||||
export const JobRow: FC<JobRowProps> = ({ job }) => {
|
||||
const metadata = job.metadata;
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -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<typeof OrganizationProvisionerJobsPage> = {
|
||||
title: "pages/OrganizationProvisionerJobsPage",
|
||||
component: OrganizationProvisionerJobsPage,
|
||||
decorators: [
|
||||
(Story, { parameters }) => (
|
||||
<OrganizationSettingsContext.Provider
|
||||
value={parameters.organizationSettingsValue}
|
||||
>
|
||||
<Story />
|
||||
</OrganizationSettingsContext.Provider>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
getProvisionerJobs: async () => MockProvisionerJobs,
|
||||
},
|
||||
parameters: {
|
||||
organizationSettingsValue: defaultOrganizationSettingsValue,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof OrganizationProvisionerJobsPage>;
|
||||
|
||||
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),
|
||||
}),
|
||||
);
|
@ -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 <EmptyState message="Organization not found" />;
|
||||
}
|
||||
|
||||
const {
|
||||
data: jobs,
|
||||
isLoadingError,
|
||||
refetch,
|
||||
} = useQuery(provisionerJobs(organization.id, getProvisionerJobs));
|
||||
} = useQuery({
|
||||
...provisionerJobs(organization?.id || ""),
|
||||
enabled: organization !== undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{pageTitle(
|
||||
"Provisioner Jobs",
|
||||
organization.display_name || organization.name,
|
||||
)}
|
||||
</title>
|
||||
</Helmet>
|
||||
|
||||
<section className="flex flex-col gap-8">
|
||||
<header className="flex flex-row items-baseline justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl m-0">Provisioner Jobs</h1>
|
||||
<p className="text-sm text-content-secondary m-0">
|
||||
Provisioner Jobs are the individual tasks assigned to Provisioners
|
||||
when the workspaces are being built.{" "}
|
||||
<Link href={docs("/admin/provisioners")}>View docs</Link>
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Template</TableHead>
|
||||
<TableHead>Tags</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{jobs ? (
|
||||
jobs.length > 0 ? (
|
||||
jobs.map((j) => <JobRow key={j.id} job={j} />)
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState message="No provisioner jobs found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
) : isLoadingError ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="Error loading the provisioner jobs"
|
||||
cta={
|
||||
<Button size="sm" onClick={() => refetch()}>
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Loader />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</section>
|
||||
</>
|
||||
<OrganizationProvisionerJobsPageView
|
||||
jobs={jobs}
|
||||
organization={organization}
|
||||
error={isLoadingError}
|
||||
onRetry={refetch}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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<typeof OrganizationProvisionerJobsPageView> = {
|
||||
title: "pages/OrganizationProvisionerJobsPage",
|
||||
component: OrganizationProvisionerJobsPageView,
|
||||
args: {
|
||||
organization: MockOrganization,
|
||||
jobs: MockProvisionerJobs,
|
||||
onRetry: fn(),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof OrganizationProvisionerJobsPageView>;
|
||||
|
||||
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: [],
|
||||
},
|
||||
};
|
@ -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 (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>{pageTitle("Provisioner Jobs")}</title>
|
||||
</Helmet>
|
||||
<EmptyState message="Organization not found" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>
|
||||
{pageTitle(
|
||||
"Provisioner Jobs",
|
||||
organization.display_name || organization.name,
|
||||
)}
|
||||
</title>
|
||||
</Helmet>
|
||||
|
||||
<section className="flex flex-col gap-8">
|
||||
<header className="flex flex-row items-baseline justify-between">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl m-0">Provisioner Jobs</h1>
|
||||
<p className="text-sm text-content-secondary m-0">
|
||||
Provisioner Jobs are the individual tasks assigned to Provisioners
|
||||
when the workspaces are being built.{" "}
|
||||
<Link href={docs("/admin/provisioners")}>View docs</Link>
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Template</TableHead>
|
||||
<TableHead>Tags</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{jobs ? (
|
||||
jobs.length > 0 ? (
|
||||
jobs.map((j) => <JobRow key={j.id} job={j} />)
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState message="No provisioner jobs found" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
) : error ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message="Error loading the provisioner jobs"
|
||||
cta={
|
||||
<Button size="sm" onClick={onRetry}>
|
||||
Retry
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Loader />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationProvisionerJobsPageView;
|
Reference in New Issue
Block a user