chore(site): refactor stories and test from page components (#9603)

* Refactor AuditPage

* Refactor CliAuthPageView stories

* Refactor CreateTemplateForm stories

* Refactor CreateUserPage test

* Refactor CreateWorkspacePage tests

* Fix stories name

* Refactor AppereancePageView stories

* Refactor GitAuthSettingsPageView stories

* Refactor NetworkSettingsPageView stories

* Refactor SecuritySettingsPageView stories

* Refactor UserAuthSettingsPageView stories

* Refactor GroupsPage stories

* Refactor LoginPage tests

* Refactor SetupPage stories

* Refactor StarterTemplatePageView stories

* Refactor StarterTemplatesPage tests

* Refactor TemplatePage tests

* RefactorTemplateSettingsPage tests

* Refactor TemplatesPage tests

* Flat TemplateVersionEditorPage

* Refactor TemplateVersionPage stories

* Refactor UserSettingsPage stories

* Refactor UsersPage stories

* Simplify IndexPage

* Refactor WorkspaceSettingsPage stories

* Refactor WorkspacePage stories

* Refactor Conditionals stories

* Fix typo

* Fix imports

* Fix ChooseOne story

* Fix UserAuthSettingsPageView stories
This commit is contained in:
Bruno Quaresma
2023-09-08 15:14:13 -03:00
committed by GitHub
parent 9e5a59e222
commit 554ddb11cd
90 changed files with 1807 additions and 2191 deletions
+7 -3
View File
@@ -1,7 +1,6 @@
import { FullScreenLoader } from "components/Loader/FullScreenLoader";
import { TemplateLayout } from "components/TemplateLayout/TemplateLayout";
import { UsersLayout } from "components/UsersLayout/UsersLayout";
import IndexPage from "pages";
import AuditPage from "pages/AuditPage/AuditPage";
import GroupsPage from "pages/GroupsPage/GroupsPage";
import LoginPage from "pages/LoginPage/LoginPage";
@@ -11,7 +10,12 @@ import TemplatesPage from "pages/TemplatesPage/TemplatesPage";
import UsersPage from "pages/UsersPage/UsersPage";
import WorkspacesPage from "pages/WorkspacesPage/WorkspacesPage";
import { FC, lazy, Suspense } from "react";
import { Route, Routes, BrowserRouter as Router } from "react-router-dom";
import {
Route,
Routes,
BrowserRouter as Router,
Navigate,
} from "react-router-dom";
import { DashboardLayout } from "./components/Dashboard/DashboardLayout";
import { RequireAuth } from "./components/RequireAuth/RequireAuth";
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout";
@@ -195,7 +199,7 @@ export const AppRouter: FC = () => {
{/* Dashboard routes */}
<Route element={<RequireAuth />}>
<Route element={<DashboardLayout />}>
<Route index element={<IndexPage />} />
<Route index element={<Navigate to="/workspaces" replace />} />
<Route path="health" element={<HealthPage />} />
@@ -1,46 +1,71 @@
import { Story } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import { ChooseOne, Cond } from "./ChooseOne";
export default {
const meta: Meta<typeof ChooseOne> = {
title: "components/Conditionals/ChooseOne",
component: ChooseOne,
subcomponents: { Cond },
};
export const FirstIsTrue: Story = () => (
<ChooseOne>
<Cond condition>The first one shows.</Cond>
<Cond condition={false}>The second one does not show.</Cond>
<Cond>The default does not show.</Cond>
</ChooseOne>
);
export default meta;
type Story = StoryObj<typeof ChooseOne>;
export const SecondIsTrue: Story = () => (
<ChooseOne>
<Cond condition={false}>The first one does not show.</Cond>
<Cond condition>The second one shows.</Cond>
<Cond>The default does not show.</Cond>
</ChooseOne>
);
export const FirstIsTrue: Story = {
args: {
children: [
<Cond key="1" condition>
The first one shows.
</Cond>,
<Cond key="2" condition={false}>
The second one does not show.
</Cond>,
<Cond key="3">The default does not show.</Cond>,
],
},
};
export const AllAreTrue: Story = () => (
<ChooseOne>
<Cond condition>Only the first one shows.</Cond>
<Cond condition>The second one does not show.</Cond>
<Cond>The default does not show.</Cond>
</ChooseOne>
);
export const SecondIsTrue: Story = {
args: {
children: [
<Cond key="1" condition={false}>
The first one does not show.
</Cond>,
<Cond key="2" condition>
The second one shows.
</Cond>,
<Cond key="3">The default does not show.</Cond>,
],
},
};
export const AllAreTrue: Story = {
args: {
children: [
<Cond key="1" condition>
Only the first one shows.
</Cond>,
<Cond key="2" condition>
The second one does not show.
</Cond>,
<Cond key="3">The default does not show.</Cond>,
],
},
};
export const NoneAreTrue: Story = () => (
<ChooseOne>
<Cond condition={false}>The first one does not show.</Cond>
<Cond condition={false}>The second one does not show.</Cond>
<Cond>The default shows.</Cond>
</ChooseOne>
);
export const NoneAreTrue: Story = {
args: {
children: [
<Cond key="1" condition={false}>
The first one does not show.
</Cond>,
<Cond key="2" condition={false}>
The second one does not show.
</Cond>,
<Cond key="3">The default shows.</Cond>,
],
},
};
export const OneCond: Story = () => (
<ChooseOne>
<Cond>An only child renders.</Cond>
</ChooseOne>
);
export const OneCond: Story = {
args: {
children: <Cond>An only child renders.</Cond>,
},
};
@@ -1,21 +1,25 @@
import { Story } from "@storybook/react";
import { Maybe, MaybeProps } from "./Maybe";
import { StoryObj, Meta } from "@storybook/react";
import { Maybe } from "./Maybe";
export default {
const meta: Meta<typeof Maybe> = {
title: "components/Conditionals/Maybe",
component: Maybe,
args: {
children: "Now you see me",
},
};
const Template: Story<MaybeProps> = (args: MaybeProps) => (
<Maybe {...args}>Now you see me</Maybe>
);
export default meta;
type Story = StoryObj<typeof Maybe>;
export const ConditionIsTrue = Template.bind({});
ConditionIsTrue.args = {
condition: true,
export const ConditionIsTrue: Story = {
args: {
condition: true,
},
};
export const ConditionIsFalse = Template.bind({});
ConditionIsFalse.args = {
condition: false,
export const ConditionIsFalse: Story = {
args: {
condition: false,
},
};
@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from "@storybook/react";
import { AuditLogDescription } from "./AuditLogDescription";
import {
MockAuditLog,
MockAuditLogSuccessfulLogin,
MockAuditLogUnsuccessfulLoginKnownUser,
MockAuditLogWithWorkspaceBuild,
MockWorkspaceCreateAuditLogForDifferentOwner,
} from "testHelpers/entities";
const meta: Meta<typeof AuditLogDescription> = {
title: "components/AuditLogDescription",
component: AuditLogDescription,
};
export default meta;
type Story = StoryObj<typeof AuditLogDescription>;
export const WorkspaceCreate: Story = {
args: {
auditLog: MockAuditLog,
},
};
export const WorkspaceBuildStop: Story = {
args: {
auditLog: MockAuditLogWithWorkspaceBuild,
},
};
export const WorkspaceBuildDuplicatedWord: Story = {
args: {
auditLog: {
...MockAuditLogWithWorkspaceBuild,
additional_fields: {
workspace_name: "workspace",
},
},
},
};
export const CreateWorkspaceWithDiffOwner: Story = {
args: {
auditLog: MockWorkspaceCreateAuditLogForDifferentOwner,
},
};
export const SuccessLogin: Story = {
args: {
auditLog: MockAuditLogSuccessfulLogin,
},
};
export const UnsuccessfulLoginForUnknownUser: Story = {
args: {
auditLog: MockAuditLogUnsuccessfulLoginKnownUser,
},
};
@@ -1,106 +0,0 @@
import {
MockAuditLog,
MockAuditLogWithWorkspaceBuild,
MockWorkspaceCreateAuditLogForDifferentOwner,
MockAuditLogSuccessfulLogin,
MockAuditLogUnsuccessfulLoginKnownUser,
} from "testHelpers/entities";
import { AuditLogDescription } from "./AuditLogDescription";
import { AuditLogRow } from "../AuditLogRow";
import { render } from "testHelpers/renderHelpers";
import { screen } from "@testing-library/react";
import { i18n } from "i18n";
const t = (str: string, variables: Record<string, unknown>) =>
i18n.t<string>(str, variables);
const getByTextContent = (text: string) => {
return screen.getByText((_, element) => {
const hasText = (element: Element | null) => element?.textContent === text;
const elementHasText = hasText(element);
const childrenDontHaveText = Array.from(element?.children || []).every(
(child) => !hasText(child),
);
return elementHasText && childrenDontHaveText;
});
};
describe("AuditLogDescription", () => {
it("renders the correct string for a workspace create audit log", async () => {
render(<AuditLogDescription auditLog={MockAuditLog} />);
expect(screen.getByText("TestUser created workspace")).toBeDefined();
expect(screen.getByText("bruno-dev")).toBeDefined();
});
it("renders the correct string for a workspace_build stop audit log", async () => {
render(<AuditLogDescription auditLog={MockAuditLogWithWorkspaceBuild} />);
expect(getByTextContent("TestUser stopped workspace test2")).toBeDefined();
});
it("renders the correct string for a workspace_build audit log with a duplicate word", async () => {
const AuditLogWithRepeat = {
...MockAuditLogWithWorkspaceBuild,
additional_fields: {
workspace_name: "workspace",
},
};
render(<AuditLogDescription auditLog={AuditLogWithRepeat} />);
expect(
getByTextContent("TestUser stopped workspace workspace"),
).toBeDefined();
});
it("renders the correct string for a workspace created for a different owner", async () => {
render(
<AuditLogDescription
auditLog={MockWorkspaceCreateAuditLogForDifferentOwner}
/>,
);
expect(
screen.getByText(
`on behalf of ${MockWorkspaceCreateAuditLogForDifferentOwner.additional_fields.workspace_owner}`,
{ exact: false },
),
).toBeDefined();
});
it("renders the correct string for successful login", async () => {
render(<AuditLogRow auditLog={MockAuditLogSuccessfulLogin} />);
expect(
screen.getByText(
t("auditLog:table.logRow.description.unlinkedAuditDescription", {
truncatedDescription: `${MockAuditLogSuccessfulLogin.user?.username} logged in`,
target: "",
onBehalfOf: undefined,
})
.replace(/<[^>]*>/g, " ")
.replace(/\s{2,}/g, " ")
.trim(),
),
).toBeInTheDocument();
const statusPill = screen.getByRole("status");
expect(statusPill).toHaveTextContent("201");
});
it("renders the correct string for unsuccessful login for a known user", async () => {
render(<AuditLogRow auditLog={MockAuditLogUnsuccessfulLoginKnownUser} />);
expect(
screen.getByText(
t("auditLog:table.logRow.description.unlinkedAuditDescription", {
truncatedDescription: `${MockAuditLogUnsuccessfulLoginKnownUser.user?.username} logged in`,
target: "",
onBehalfOf: undefined,
})
.replace(/<[^>]*>/g, " ")
.replace(/\s{2,}/g, " ")
.trim(),
),
).toBeInTheDocument();
const statusPill = screen.getByRole("status");
expect(statusPill).toHaveTextContent("401");
});
});
@@ -1 +0,0 @@
export { AuditLogDescription } from "./AuditLogDescription";
@@ -1,2 +0,0 @@
export { AuditLogDiff } from "./AuditLogDiff";
export { determineGroupDiff } from "./auditUtils";
@@ -4,7 +4,6 @@ import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import { ComponentMeta, Story } from "@storybook/react";
import {
MockAuditLog,
MockAuditLog2,
@@ -12,89 +11,102 @@ import {
MockAuditLogWithDeletedResource,
MockAuditLogGitSSH,
} from "testHelpers/entities";
import { AuditLogRow, AuditLogRowProps } from "./AuditLogRow";
import { AuditLogRow } from "./AuditLogRow";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof AuditLogRow> = {
title: "components/AuditLogRow",
component: AuditLogRow,
} as ComponentMeta<typeof AuditLogRow>;
const Template: Story<AuditLogRowProps> = (args) => (
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell style={{ paddingLeft: 32 }}>Logs</TableCell>
</TableRow>
</TableHead>
<TableBody>
<AuditLogRow {...args} />
</TableBody>
</Table>
</TableContainer>
);
export const NoDiff = Template.bind({});
NoDiff.args = {
auditLog: {
...MockAuditLog,
diff: {},
},
decorators: [
(Story) => (
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell style={{ paddingLeft: 32 }}>Logs</TableCell>
</TableRow>
</TableHead>
<TableBody>
<Story />
</TableBody>
</Table>
</TableContainer>
),
],
};
export const WithDiff = Template.bind({});
WithDiff.args = {
auditLog: MockAuditLog2,
defaultIsDiffOpen: true,
};
export default meta;
type Story = StoryObj<typeof AuditLogRow>;
export const WithLongDiffRow = Template.bind({});
WithLongDiffRow.args = {
auditLog: {
...MockAuditLog2,
diff: {
...MockAuditLog2.diff,
icon: {
old: "https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.docker.com%2Fcompany%2Fnewsroom%2Fmedia-resources%2F&psig=AOvVaw3hLg_lm0tzXPBt74XZD2GC&ust=1666892413988000&source=images&cd=vfe&ved=0CAwQjRxqFwoTCPDsiKa4_voCFQAAAAAdAAAAABAD",
new: "https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.kindpng.com%2Fimgv%2FhRowRxi_docker-icon-png-transparent-png%2F&psig=AOvVaw3hLg_lm0tzXPBt74XZD2GC&ust=1666892413988000&source=images&cd=vfe&ved=0CAwQjRxqFwoTCPDsiKa4_voCFQAAAAAdAAAAABAI",
secret: false,
},
export const NoDiff: Story = {
args: {
auditLog: {
...MockAuditLog,
diff: {},
},
},
defaultIsDiffOpen: true,
};
export const WithStoppedWorkspaceBuild = Template.bind({});
WithStoppedWorkspaceBuild.args = {
auditLog: {
...MockAuditLogWithWorkspaceBuild,
action: "stop",
export const WithDiff: Story = {
args: {
auditLog: MockAuditLog2,
defaultIsDiffOpen: true,
},
};
export const WithStartedWorkspaceBuild = Template.bind({});
WithStartedWorkspaceBuild.args = {
auditLog: {
...MockAuditLogWithWorkspaceBuild,
action: "start",
export const WithLongDiffRow: Story = {
args: {
auditLog: {
...MockAuditLog2,
diff: {
...MockAuditLog2.diff,
icon: {
old: "https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.docker.com%2Fcompany%2Fnewsroom%2Fmedia-resources%2F&psig=AOvVaw3hLg_lm0tzXPBt74XZD2GC&ust=1666892413988000&source=images&cd=vfe&ved=0CAwQjRxqFwoTCPDsiKa4_voCFQAAAAAdAAAAABAD",
new: "https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.kindpng.com%2Fimgv%2FhRowRxi_docker-icon-png-transparent-png%2F&psig=AOvVaw3hLg_lm0tzXPBt74XZD2GC&ust=1666892413988000&source=images&cd=vfe&ved=0CAwQjRxqFwoTCPDsiKa4_voCFQAAAAAdAAAAABAI",
secret: false,
},
},
},
defaultIsDiffOpen: true,
},
};
export const WithDeletedWorkspaceBuild = Template.bind({});
WithDeletedWorkspaceBuild.args = {
auditLog: {
...MockAuditLogWithWorkspaceBuild,
action: "delete",
is_deleted: true,
export const WithStoppedWorkspaceBuild: Story = {
args: {
auditLog: {
...MockAuditLogWithWorkspaceBuild,
action: "stop",
},
},
};
export const DeletedResource = Template.bind({});
DeletedResource.args = {
auditLog: MockAuditLogWithDeletedResource,
export const WithStartedWorkspaceBuild: Story = {
args: {
auditLog: {
...MockAuditLogWithWorkspaceBuild,
action: "start",
},
},
};
export const SecretDiffValue = Template.bind({});
SecretDiffValue.args = {
auditLog: MockAuditLogGitSSH,
export const WithDeletedWorkspaceBuild: Story = {
args: {
auditLog: {
...MockAuditLogWithWorkspaceBuild,
action: "delete",
is_deleted: true,
},
},
};
export const DeletedResource: Story = {
args: {
auditLog: MockAuditLogWithDeletedResource,
},
};
export const SecretDiffValue: Story = {
args: {
auditLog: MockAuditLogGitSSH,
},
};
@@ -12,10 +12,11 @@ import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { UserAvatar } from "components/UserAvatar/UserAvatar";
import { useState } from "react";
import userAgentParser from "ua-parser-js";
import { AuditLogDiff, determineGroupDiff } from "./AuditLogDiff";
import { AuditLogDiff } from "./AuditLogDiff/AuditLogDiff";
import { useTranslation } from "react-i18next";
import { AuditLogDescription } from "./AuditLogDescription";
import { AuditLogDescription } from "./AuditLogDescription/AuditLogDescription";
import { PaletteIndex } from "theme/theme";
import { determineGroupDiff } from "./AuditLogDiff/auditUtils";
const httpStatusColor = (httpStatus: number): PaletteIndex => {
// redirects are successful
@@ -59,15 +59,6 @@ describe("AuditPage", () => {
);
});
it("shows the audit logs", async () => {
// When
await renderPage();
// Then
await screen.findByTestId(`audit-log-row-${MockAuditLog.id}`);
screen.getByTestId(`audit-log-row-${MockAuditLog2.id}`);
});
it("renders page 5", async () => {
// Given
const page = 5;
@@ -1,7 +1,6 @@
import { Meta, StoryObj } from "@storybook/react";
import { MockAuditLog, MockAuditLog2, MockUser } from "testHelpers/entities";
import { AuditPageView } from "./AuditPageView";
import { WorkspacesPageView } from "pages/WorkspacesPage/WorkspacesPageView";
import { ComponentProps } from "react";
import {
MockMenu,
@@ -38,7 +37,7 @@ const meta: Meta<typeof AuditPageView> = {
};
export default meta;
type Story = StoryObj<typeof WorkspacesPageView>;
type Story = StoryObj<typeof AuditPageView>;
export const AuditPage: Story = {};
+1 -1
View File
@@ -4,7 +4,7 @@ import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import { AuditLog } from "api/typesGenerated";
import { AuditLogRow } from "pages/AuditPage/AuditLogRow/AuditLogRow";
import { AuditLogRow } from "./AuditLogRow/AuditLogRow";
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne";
import { EmptyState } from "components/EmptyState/EmptyState";
import { Margins } from "components/Margins/Margins";
@@ -1,20 +1,15 @@
import { Story } from "@storybook/react";
import { CliAuthPageView, CliAuthPageViewProps } from "./CliAuthPageView";
import { CliAuthPageView } from "./CliAuthPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof CliAuthPageView> = {
title: "pages/CliAuthPageView",
component: CliAuthPageView,
argTypes: {
sessionToken: { control: "text" },
},
args: {
sessionToken: "some-session-token",
},
};
const Template: Story<CliAuthPageViewProps> = (args) => (
<CliAuthPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof CliAuthPageView>;
export const Example = Template.bind({});
Example.args = {};
export const Example: Story = {};
@@ -1,4 +1,3 @@
import { ComponentMeta, Story } from "@storybook/react";
import {
MockTemplateExample,
MockTemplateVersionVariable1,
@@ -7,353 +6,353 @@ import {
MockTemplateVersionVariable4,
MockTemplateVersionVariable5,
} from "testHelpers/entities";
import {
CreateTemplateForm,
CreateTemplateFormProps,
} from "./CreateTemplateForm";
import { CreateTemplateForm } from "./CreateTemplateForm";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof CreateTemplateForm> = {
title: "components/CreateTemplateForm",
component: CreateTemplateForm,
args: {
isSubmitting: false,
allowDisableEveryoneAccess: true,
},
} as ComponentMeta<typeof CreateTemplateForm>;
const Template: Story<CreateTemplateFormProps> = (args) => (
<CreateTemplateForm {...args} />
);
export const Initial = Template.bind({});
Initial.args = {};
export const WithStarterTemplate = Template.bind({});
WithStarterTemplate.args = {
starterTemplate: MockTemplateExample,
};
export const WithVariables = Template.bind({});
WithVariables.args = {
variables: [
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
MockTemplateVersionVariable5,
],
export default meta;
type Story = StoryObj<typeof CreateTemplateForm>;
export const Initial: Story = {};
export const WithStarterTemplate: Story = {
args: {
starterTemplate: MockTemplateExample,
},
};
export const WithJobError = Template.bind({});
WithJobError.args = {
jobError:
"template import provision for start: recv import provision: plan terraform: terraform plan: exit status 1",
logs: [
{
id: 461061,
created_at: "2023-03-06T14:47:32.501Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Adding README.md...",
output: "",
},
{
id: 461062,
created_at: "2023-03-06T14:47:32.501Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Setting up",
output: "",
},
{
id: 461063,
created_at: "2023-03-06T14:47:32.528Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Parsing template parameters",
output: "",
},
{
id: 461064,
created_at: "2023-03-06T14:47:32.552Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461065,
created_at: "2023-03-06T14:47:32.633Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461066,
created_at: "2023-03-06T14:47:32.633Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "Initializing the backend...",
},
{
id: 461067,
created_at: "2023-03-06T14:47:32.71Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461068,
created_at: "2023-03-06T14:47:32.711Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "Initializing provider plugins...",
},
{
id: 461069,
created_at: "2023-03-06T14:47:32.712Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: '- Finding coder/coder versions matching "~\u003e 0.6.12"...',
},
{
id: 461070,
created_at: "2023-03-06T14:47:32.922Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: '- Finding hashicorp/aws versions matching "~\u003e 4.55"...',
},
{
id: 461071,
created_at: "2023-03-06T14:47:33.132Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "- Installing hashicorp/aws v4.57.0...",
},
{
id: 461072,
created_at: "2023-03-06T14:47:37.364Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "- Installed hashicorp/aws v4.57.0 (signed by HashiCorp)",
},
{
id: 461073,
created_at: "2023-03-06T14:47:38.142Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "- Installing coder/coder v0.6.15...",
},
{
id: 461074,
created_at: "2023-03-06T14:47:39.083Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"- Installed coder/coder v0.6.15 (signed by a HashiCorp partner, key ID 93C75807601AA0EC)",
},
{
id: 461075,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461076,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "Partner and community providers are signed by their developers.",
},
{
id: 461077,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"If you'd like to know more about provider signing, you can read about it here:",
},
{
id: 461078,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "https://www.terraform.io/docs/cli/plugins/signing.html",
},
{
id: 461079,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461080,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"Terraform has created a lock file .terraform.lock.hcl to record the provider",
},
{
id: 461081,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"selections it made above. Include this file in your version control repository",
},
{
id: 461082,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"so that Terraform can guarantee to make the same selections by default when",
},
{
id: 461083,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: 'you run "terraform init" in the future.',
},
{
id: 461084,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461085,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "Terraform has been successfully initialized!",
},
{
id: 461086,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461087,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
'You may now begin working with Terraform. Try running "terraform plan" to see',
},
{
id: 461088,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"any changes that are required for your infrastructure. All Terraform commands",
},
{
id: 461089,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "should now work.",
},
{
id: 461090,
created_at: "2023-03-06T14:47:39.397Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461091,
created_at: "2023-03-06T14:47:39.397Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"If you ever set or change modules or backend configuration for Terraform,",
},
{
id: 461092,
created_at: "2023-03-06T14:47:39.397Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"rerun this command to reinitialize your working directory. If you forget, other",
},
{
id: 461093,
created_at: "2023-03-06T14:47:39.397Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "commands will detect it and remind you to do so if necessary.",
},
{
id: 461094,
created_at: "2023-03-06T14:47:39.431Z",
log_source: "provisioner",
log_level: "info",
stage: "Detecting persistent resources",
output: "Terraform 1.1.9",
},
{
id: 461095,
created_at: "2023-03-06T14:47:43.759Z",
log_source: "provisioner",
log_level: "error",
stage: "Detecting persistent resources",
output:
"Error: configuring Terraform AWS Provider: no valid credential sources for Terraform AWS Provider found.\n\nPlease see https://registry.terraform.io/providers/hashicorp/aws\nfor more information about providing credentials.\n\nError: failed to refresh cached credentials, no EC2 IMDS role found, operation error ec2imds: GetMetadata, http response error StatusCode: 404, request to EC2 IMDS failed\n",
},
{
id: 461096,
created_at: "2023-03-06T14:47:43.759Z",
log_source: "provisioner",
log_level: "error",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461097,
created_at: "2023-03-06T14:47:43.777Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Cleaning Up",
output: "",
},
],
export const WithVariables: Story = {
args: {
variables: [
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
MockTemplateVersionVariable5,
],
},
};
export const WithJobError: Story = {
args: {
jobError:
"template import provision for start: recv import provision: plan terraform: terraform plan: exit status 1",
logs: [
{
id: 461061,
created_at: "2023-03-06T14:47:32.501Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Adding README.md...",
output: "",
},
{
id: 461062,
created_at: "2023-03-06T14:47:32.501Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Setting up",
output: "",
},
{
id: 461063,
created_at: "2023-03-06T14:47:32.528Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Parsing template parameters",
output: "",
},
{
id: 461064,
created_at: "2023-03-06T14:47:32.552Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461065,
created_at: "2023-03-06T14:47:32.633Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461066,
created_at: "2023-03-06T14:47:32.633Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "Initializing the backend...",
},
{
id: 461067,
created_at: "2023-03-06T14:47:32.71Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461068,
created_at: "2023-03-06T14:47:32.711Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "Initializing provider plugins...",
},
{
id: 461069,
created_at: "2023-03-06T14:47:32.712Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: '- Finding coder/coder versions matching "~\u003e 0.6.12"...',
},
{
id: 461070,
created_at: "2023-03-06T14:47:32.922Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: '- Finding hashicorp/aws versions matching "~\u003e 4.55"...',
},
{
id: 461071,
created_at: "2023-03-06T14:47:33.132Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "- Installing hashicorp/aws v4.57.0...",
},
{
id: 461072,
created_at: "2023-03-06T14:47:37.364Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "- Installed hashicorp/aws v4.57.0 (signed by HashiCorp)",
},
{
id: 461073,
created_at: "2023-03-06T14:47:38.142Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "- Installing coder/coder v0.6.15...",
},
{
id: 461074,
created_at: "2023-03-06T14:47:39.083Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"- Installed coder/coder v0.6.15 (signed by a HashiCorp partner, key ID 93C75807601AA0EC)",
},
{
id: 461075,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461076,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"Partner and community providers are signed by their developers.",
},
{
id: 461077,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"If you'd like to know more about provider signing, you can read about it here:",
},
{
id: 461078,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "https://www.terraform.io/docs/cli/plugins/signing.html",
},
{
id: 461079,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461080,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"Terraform has created a lock file .terraform.lock.hcl to record the provider",
},
{
id: 461081,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"selections it made above. Include this file in your version control repository",
},
{
id: 461082,
created_at: "2023-03-06T14:47:39.394Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"so that Terraform can guarantee to make the same selections by default when",
},
{
id: 461083,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: 'you run "terraform init" in the future.',
},
{
id: 461084,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461085,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "Terraform has been successfully initialized!",
},
{
id: 461086,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461087,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
'You may now begin working with Terraform. Try running "terraform plan" to see',
},
{
id: 461088,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"any changes that are required for your infrastructure. All Terraform commands",
},
{
id: 461089,
created_at: "2023-03-06T14:47:39.395Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "should now work.",
},
{
id: 461090,
created_at: "2023-03-06T14:47:39.397Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461091,
created_at: "2023-03-06T14:47:39.397Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"If you ever set or change modules or backend configuration for Terraform,",
},
{
id: 461092,
created_at: "2023-03-06T14:47:39.397Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output:
"rerun this command to reinitialize your working directory. If you forget, other",
},
{
id: 461093,
created_at: "2023-03-06T14:47:39.397Z",
log_source: "provisioner",
log_level: "debug",
stage: "Detecting persistent resources",
output: "commands will detect it and remind you to do so if necessary.",
},
{
id: 461094,
created_at: "2023-03-06T14:47:39.431Z",
log_source: "provisioner",
log_level: "info",
stage: "Detecting persistent resources",
output: "Terraform 1.1.9",
},
{
id: 461095,
created_at: "2023-03-06T14:47:43.759Z",
log_source: "provisioner",
log_level: "error",
stage: "Detecting persistent resources",
output:
"Error: configuring Terraform AWS Provider: no valid credential sources for Terraform AWS Provider found.\n\nPlease see https://registry.terraform.io/providers/hashicorp/aws\nfor more information about providing credentials.\n\nError: failed to refresh cached credentials, no EC2 IMDS role found, operation error ec2imds: GetMetadata, http response error StatusCode: 404, request to EC2 IMDS failed\n",
},
{
id: 461096,
created_at: "2023-03-06T14:47:43.759Z",
log_source: "provisioner",
log_level: "error",
stage: "Detecting persistent resources",
output: "",
},
{
id: 461097,
created_at: "2023-03-06T14:47:43.777Z",
log_source: "provisioner_daemon",
log_level: "info",
stage: "Cleaning Up",
output: "",
},
],
},
};
@@ -8,10 +8,7 @@ import {
TemplateVersionVariable,
} from "api/typesGenerated";
import { Stack } from "components/Stack/Stack";
import {
TemplateUpload,
TemplateUploadProps,
} from "pages/CreateTemplatePage/TemplateUpload";
import { TemplateUpload, TemplateUploadProps } from "./TemplateUpload";
import { useFormik } from "formik";
import { SelectedTemplate } from "pages/CreateWorkspacePage/SelectedTemplate";
import { FC, useEffect } from "react";
@@ -46,7 +43,7 @@ import { docs } from "utils/docs";
import {
AutostopRequirementDaysHelperText,
AutostopRequirementWeeksHelperText,
} from "pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/AutostopRequirementHelperText";
} from "pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText";
import MenuItem from "@mui/material/MenuItem";
const MAX_DESCRIPTION_CHAR_LIMIT = 128;
@@ -2,7 +2,7 @@ import {
renderWithAuth,
waitForLoaderToBeRemoved,
} from "testHelpers/renderHelpers";
import { CreateTokenPage } from "pages/CreateTokenPage/CreateTokenPage";
import { CreateTokenPage } from "./CreateTokenPage";
import * as API from "api/api";
import { screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
@@ -1,13 +1,11 @@
import { fireEvent, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { rest } from "msw";
import { Language as FormLanguage } from "./CreateUserForm";
import { Language as FooterLanguage } from "components/FormFooter/FormFooter";
import {
renderWithAuth,
waitForLoaderToBeRemoved,
} from "testHelpers/renderHelpers";
import { server } from "testHelpers/server";
import { Language as CreateUserLanguage } from "xServices/users/createUserXService";
import { CreateUserPage } from "./CreateUserPage";
@@ -45,37 +43,6 @@ const fillForm = async ({
};
describe("Create User Page", () => {
it("shows validation error message", async () => {
await renderCreateUserPage();
await fillForm({ email: "test" });
const errorMessage = await screen.findByText(FormLanguage.emailInvalid);
expect(errorMessage).toBeDefined();
});
it("shows API error message", async () => {
const fieldErrorMessage = "username already in use";
server.use(
rest.post("/api/v2/users", async (req, res, ctx) => {
return res(
ctx.status(400),
ctx.json({
message: "invalid field",
validations: [
{
detail: fieldErrorMessage,
field: "username",
},
],
}),
);
}),
);
await renderCreateUserPage();
await fillForm({});
const errorMessage = await screen.findByText(fieldErrorMessage);
expect(errorMessage).toBeDefined();
});
it("shows success notification and redirects to users page", async () => {
await renderCreateUserPage();
await fillForm({});
@@ -54,30 +54,6 @@ Object.defineProperty(window, "BroadcastChannel", {
});
describe("CreateWorkspacePage", () => {
it("renders", async () => {
jest
.spyOn(API, "getTemplateVersionRichParameters")
.mockResolvedValueOnce([MockTemplateVersionParameter1]);
renderCreateWorkspacePage();
const element = await screen.findByText(createWorkspaceText);
expect(element).toBeDefined();
});
it("renders with rich parameter", async () => {
jest
.spyOn(API, "getTemplateVersionRichParameters")
.mockResolvedValueOnce([MockTemplateVersionParameter1]);
renderCreateWorkspacePage();
const element = await screen.findByText(createWorkspaceText);
expect(element).toBeDefined();
const firstParameter = await screen.findByText(
MockTemplateVersionParameter1.description,
);
expect(firstParameter).toBeDefined();
});
it("succeeds with default owner", async () => {
jest
.spyOn(API, "getUsers")
@@ -10,7 +10,7 @@ import {
import { CreateWorkspacePageView } from "./CreateWorkspacePageView";
const meta: Meta<typeof CreateWorkspacePageView> = {
title: "components/Alert",
title: "pages/CreateWorkspacePageView",
component: CreateWorkspacePageView,
args: {
defaultName: "",
@@ -1,57 +1,66 @@
import { Story } from "@storybook/react";
import { GitAuth, GitAuthProps } from "./GitAuth";
import { GitAuth } from "./GitAuth";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof GitAuth> = {
title: "components/GitAuth",
component: GitAuth,
};
const Template: Story<GitAuthProps> = (args) => <GitAuth {...args} />;
export default meta;
type Story = StoryObj<typeof GitAuth>;
export const GithubNotAuthenticated = Template.bind({});
GithubNotAuthenticated.args = {
type: "github",
authenticated: false,
export const GithubNotAuthenticated: Story = {
args: {
type: "github",
authenticated: false,
},
};
export const GithubAuthenticated = Template.bind({});
GithubAuthenticated.args = {
type: "github",
authenticated: true,
export const GithubAuthenticated: Story = {
args: {
type: "github",
authenticated: true,
},
};
export const GitlabNotAuthenticated = Template.bind({});
GitlabNotAuthenticated.args = {
type: "gitlab",
authenticated: false,
export const GitlabNotAuthenticated: Story = {
args: {
type: "gitlab",
authenticated: false,
},
};
export const GitlabAuthenticated = Template.bind({});
GitlabAuthenticated.args = {
type: "gitlab",
authenticated: true,
export const GitlabAuthenticated: Story = {
args: {
type: "gitlab",
authenticated: true,
},
};
export const AzureDevOpsNotAuthenticated = Template.bind({});
AzureDevOpsNotAuthenticated.args = {
type: "azure-devops",
authenticated: false,
export const AzureDevOpsNotAuthenticated: Story = {
args: {
type: "azure-devops",
authenticated: false,
},
};
export const AzureDevOpsAuthenticated = Template.bind({});
AzureDevOpsAuthenticated.args = {
type: "azure-devops",
authenticated: true,
export const AzureDevOpsAuthenticated: Story = {
args: {
type: "azure-devops",
authenticated: true,
},
};
export const BitbucketNotAuthenticated = Template.bind({});
BitbucketNotAuthenticated.args = {
type: "bitbucket",
authenticated: false,
export const BitbucketNotAuthenticated: Story = {
args: {
type: "bitbucket",
authenticated: false,
},
};
export const BitbucketAuthenticated = Template.bind({});
BitbucketAuthenticated.args = {
type: "bitbucket",
authenticated: true,
export const BitbucketAuthenticated: Story = {
args: {
type: "bitbucket",
authenticated: true,
},
};
@@ -1,28 +1,29 @@
import { ComponentMeta, Story } from "@storybook/react";
import { MockTemplate } from "../../testHelpers/entities";
import { SelectedTemplate, SelectedTemplateProps } from "./SelectedTemplate";
import { SelectedTemplate } from "./SelectedTemplate";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof SelectedTemplate> = {
title: "components/SelectedTemplate",
component: SelectedTemplate,
} as ComponentMeta<typeof SelectedTemplate>;
};
const Template: Story<SelectedTemplateProps> = (args) => (
<SelectedTemplate {...args} />
);
export default meta;
type Story = StoryObj<typeof SelectedTemplate>;
export const WithIcon = Template.bind({});
WithIcon.args = {
template: {
...MockTemplate,
icon: "/icon/docker.png",
export const WithIcon: Story = {
args: {
template: {
...MockTemplate,
icon: "/icon/docker.png",
},
},
};
export const WithoutIcon = Template.bind({});
WithoutIcon.args = {
template: {
...MockTemplate,
icon: "",
export const WithoutIcon: Story = {
args: {
template: {
...MockTemplate,
icon: "",
},
},
};
@@ -1,10 +1,7 @@
import { ComponentMeta, Story } from "@storybook/react";
import {
AppearanceSettingsPageView,
AppearanceSettingsPageViewProps,
} from "./AppearanceSettingsPageView";
import { AppearanceSettingsPageView } from "./AppearanceSettingsPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof AppearanceSettingsPageView> = {
title: "pages/AppearanceSettingsPageView",
component: AppearanceSettingsPageView,
args: {
@@ -21,9 +18,9 @@ export default {
return undefined;
},
},
} as ComponentMeta<typeof AppearanceSettingsPageView>;
};
const Template: Story<AppearanceSettingsPageViewProps> = (args) => (
<AppearanceSettingsPageView {...args} />
);
export const Page = Template.bind({});
export default meta;
type Story = StoryObj<typeof AppearanceSettingsPageView>;
export const Page: Story = {};
@@ -1,10 +1,7 @@
import { ComponentMeta, Story } from "@storybook/react";
import {
GitAuthSettingsPageView,
GitAuthSettingsPageViewProps,
} from "./GitAuthSettingsPageView";
import { GitAuthSettingsPageView } from "./GitAuthSettingsPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof GitAuthSettingsPageView> = {
title: "pages/GitAuthSettingsPageView",
component: GitAuthSettingsPageView,
args: {
@@ -19,9 +16,9 @@ export default {
],
},
},
} as ComponentMeta<typeof GitAuthSettingsPageView>;
};
const Template: Story<GitAuthSettingsPageViewProps> = (args) => (
<GitAuthSettingsPageView {...args} />
);
export const Page = Template.bind({});
export default meta;
type Story = StoryObj<typeof GitAuthSettingsPageView>;
export const Page: Story = {};
@@ -1,60 +1,71 @@
import { ComponentMeta, Story } from "@storybook/react";
import {
NetworkSettingsPageView,
NetworkSettingsPageViewProps,
} from "./NetworkSettingsPageView";
import { DeploymentGroup } from "api/types";
import { NetworkSettingsPageView } from "./NetworkSettingsPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const group: DeploymentGroup = {
name: "Networking",
description: "",
children: [] as DeploymentGroup[],
};
const meta: Meta<typeof NetworkSettingsPageView> = {
title: "pages/NetworkSettingsPageView",
component: NetworkSettingsPageView,
args: {
options: [
{
name: "DERP Server Enable",
usage: "Whether to enable or disable the embedded DERP relay server.",
description:
"Whether to enable or disable the embedded DERP relay server.",
value: true,
group: {
name: "Networking",
},
group,
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
{
name: "DERP Server Region Name",
usage: "Region name that for the embedded DERP server.",
description: "Region name that for the embedded DERP server.",
value: "aws-east",
group: {
name: "Networking",
},
group,
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
{
name: "DERP Server STUN Addresses",
usage:
description:
"Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.",
value: ["stun.l.google.com:19302", "stun.l.google.com:19301"],
group: {
name: "Networking",
},
group,
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
{
name: "DERP Config URL",
usage:
description:
"URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/",
value: "https://coder.com",
group: {
name: "Networking",
},
group,
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
{
name: "Wildcard Access URL",
description: "",
value: "https://coder.com",
group: {
name: "Networking",
},
group,
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
],
},
} as ComponentMeta<typeof NetworkSettingsPageView>;
};
const Template: Story<NetworkSettingsPageViewProps> = (args) => (
<NetworkSettingsPageView {...args} />
);
export const Page = Template.bind({});
export default meta;
type Story = StoryObj<typeof NetworkSettingsPageView>;
export const Page: Story = {};
@@ -1,11 +1,14 @@
import { ComponentMeta, Story } from "@storybook/react";
import { DeploymentOption } from "api/types";
import {
SecuritySettingsPageView,
SecuritySettingsPageViewProps,
} from "./SecuritySettingsPageView";
import { DeploymentGroup, DeploymentOption } from "api/types";
import { SecuritySettingsPageView } from "./SecuritySettingsPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const group: DeploymentGroup = {
name: "Networking",
description: "",
children: [] as DeploymentGroup[],
};
const meta: Meta<typeof SecuritySettingsPageView> = {
title: "pages/SecuritySettingsPageView",
component: SecuritySettingsPageView,
args: {
@@ -14,50 +17,62 @@ export default {
name: "SSH Keygen Algorithm",
description: "something",
value: "1234",
group,
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
{
name: "Secure Auth Cookie",
description: "something",
value: "1234",
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
{
name: "Disable Owner Workspace Access",
description: "something",
value: false,
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
{
name: "TLS Version",
description: "something",
value: ["something"],
group: {
name: "TLS",
},
group: { ...group, name: "TLS" },
flag: "derp",
flag_shorthand: "d",
hidden: false,
},
],
featureAuditLogEnabled: true,
featureBrowserOnlyEnabled: true,
},
} as ComponentMeta<typeof SecuritySettingsPageView>;
};
const Template: Story<SecuritySettingsPageViewProps> = (args) => (
<SecuritySettingsPageView {...args} />
);
export const Page = Template.bind({});
export default meta;
type Story = StoryObj<typeof SecuritySettingsPageView>;
export const NoTLS = Template.bind({});
NoTLS.args = {
options: [
{
name: "SSH Keygen Algorithm",
value: "1234",
} as DeploymentOption,
{
name: "Disable Owner Workspace Access",
value: false,
} as DeploymentOption,
{
name: "Secure Auth Cookie",
value: "1234",
} as DeploymentOption,
],
export const Page: Story = {};
export const NoTLS = {
args: {
options: [
{
name: "SSH Keygen Algorithm",
value: "1234",
} as DeploymentOption,
{
name: "Disable Owner Workspace Access",
value: false,
} as DeploymentOption,
{
name: "Secure Auth Cookie",
value: "1234",
} as DeploymentOption,
],
},
};
@@ -1,10 +1,20 @@
import { ComponentMeta, Story } from "@storybook/react";
import {
UserAuthSettingsPageView,
UserAuthSettingsPageViewProps,
} from "./UserAuthSettingsPageView";
import { DeploymentGroup } from "api/types";
import { UserAuthSettingsPageView } from "./UserAuthSettingsPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const oidcGroup: DeploymentGroup = {
name: "OIDC",
description: "",
children: [] as DeploymentGroup[],
};
const ghGroup: DeploymentGroup = {
name: "GitHub",
description: "",
children: [] as DeploymentGroup[],
};
const meta: Meta<typeof UserAuthSettingsPageView> = {
title: "pages/UserAuthSettingsPageView",
component: UserAuthSettingsPageView,
args: {
@@ -13,91 +23,101 @@ export default {
name: "OIDC Client ID",
description: "Client ID to use for Login with OIDC.",
value: "1234",
group: {
name: "OIDC",
},
group: oidcGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OIDC Allow Signups",
description: "Whether new users can sign up with OIDC.",
value: true,
group: {
name: "OIDC",
},
group: oidcGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OIDC Email Domain",
description:
"Email domains that clients logging in with OIDC must match.",
value: "@coder.com",
group: {
name: "OIDC",
},
group: oidcGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OIDC Issuer URL",
description: "Issuer URL to use for Login with OIDC.",
value: "https://coder.com",
group: {
name: "OIDC",
},
group: oidcGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OIDC Scopes",
description: "Scopes to grant when authenticating with OIDC.",
value: ["idk"],
group: {
name: "OIDC",
},
group: oidcGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OAuth2 GitHub Client ID",
description: "Client ID for Login with GitHub.",
value: "1224",
group: {
name: "GitHub",
},
group: ghGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OAuth2 GitHub Allow Signups",
description: "Whether new users can sign up with GitHub.",
value: true,
group: {
name: "GitHub",
},
group: ghGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OAuth2 GitHub Enterprise Base URL",
description:
"Base URL of a GitHub Enterprise deployment to use for Login with GitHub.",
value: "https://google.com",
group: {
name: "GitHub",
},
group: ghGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OAuth2 GitHub Allowed Orgs",
description:
"Organizations the user must be a member of to Login with GitHub.",
value: true,
group: {
name: "GitHub",
},
group: ghGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
{
name: "OAuth2 GitHub Allowed Teams",
description:
"Teams inside organizations the user must be a member of to Login with GitHub. Structured as: <organization-name>/<team-slug>.",
value: true,
group: {
name: "GitHub",
},
group: ghGroup,
flag: "oidc",
flag_shorthand: "o",
hidden: false,
},
],
},
} as ComponentMeta<typeof UserAuthSettingsPageView>;
};
const Template: Story<UserAuthSettingsPageViewProps> = (args) => (
<UserAuthSettingsPageView {...args} />
);
export const Page = Template.bind({});
export default meta;
type Story = StoryObj<typeof UserAuthSettingsPageView>;
export const Page: Story = {};
@@ -1,17 +1,12 @@
import { Story } from "@storybook/react";
import {
CreateGroupPageView,
CreateGroupPageViewProps,
} from "./CreateGroupPageView";
import { CreateGroupPageView } from "./CreateGroupPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof CreateGroupPageView> = {
title: "pages/CreateGroupPageView",
component: CreateGroupPageView,
};
const Template: Story<CreateGroupPageViewProps> = (
args: CreateGroupPageViewProps,
) => <CreateGroupPageView {...args} />;
export default meta;
type Story = StoryObj<typeof CreateGroupPageView>;
export const Example = Template.bind({});
Example.args = {};
export const Example: Story = {};
@@ -1,47 +1,51 @@
import { Story } from "@storybook/react";
import { MockGroup } from "testHelpers/entities";
import { GroupsPageView, GroupsPageViewProps } from "./GroupsPageView";
import { GroupsPageView } from "./GroupsPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof GroupsPageView> = {
title: "pages/GroupsPageView",
component: GroupsPageView,
};
const Template: Story<GroupsPageViewProps> = (args: GroupsPageViewProps) => (
<GroupsPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof GroupsPageView>;
export const NotEnabled = Template.bind({});
NotEnabled.args = {
groups: [MockGroup],
canCreateGroup: true,
isTemplateRBACEnabled: false,
export const NotEnabled: Story = {
args: {
groups: [MockGroup],
canCreateGroup: true,
isTemplateRBACEnabled: false,
},
};
export const WithGroups = Template.bind({});
WithGroups.args = {
groups: [MockGroup],
canCreateGroup: true,
isTemplateRBACEnabled: true,
export const WithGroups: Story = {
args: {
groups: [MockGroup],
canCreateGroup: true,
isTemplateRBACEnabled: true,
},
};
export const WithDisplayGroup = Template.bind({});
WithGroups.args = {
groups: [{ ...MockGroup, name: "front-end" }],
canCreateGroup: true,
isTemplateRBACEnabled: true,
export const WithDisplayGroup: Story = {
args: {
groups: [{ ...MockGroup, name: "front-end" }],
canCreateGroup: true,
isTemplateRBACEnabled: true,
},
};
export const EmptyGroup = Template.bind({});
EmptyGroup.args = {
groups: [],
canCreateGroup: false,
isTemplateRBACEnabled: true,
export const EmptyGroup: Story = {
args: {
groups: [],
canCreateGroup: false,
isTemplateRBACEnabled: true,
},
};
export const EmptyGroupWithPermission = Template.bind({});
EmptyGroupWithPermission.args = {
groups: [],
canCreateGroup: true,
isTemplateRBACEnabled: true,
export const EmptyGroupWithPermission: Story = {
args: {
groups: [],
canCreateGroup: true,
isTemplateRBACEnabled: true,
},
};
@@ -1,21 +1,18 @@
import { Story } from "@storybook/react";
import { MockGroup } from "testHelpers/entities";
import {
SettingsGroupPageView,
SettingsGroupPageViewProps,
} from "./SettingsGroupPageView";
import { SettingsGroupPageView } from "./SettingsGroupPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof SettingsGroupPageView> = {
title: "pages/SettingsGroupPageView",
component: SettingsGroupPageView,
};
const Template: Story<SettingsGroupPageViewProps> = (
args: SettingsGroupPageViewProps,
) => <SettingsGroupPageView {...args} />;
export default meta;
type Story = StoryObj<typeof SettingsGroupPageView>;
export const Example = Template.bind({});
Example.args = {
group: MockGroup,
isLoading: false,
export const Example: Story = {
args: {
group: MockGroup,
isLoading: false,
},
};
+3 -33
View File
@@ -2,13 +2,13 @@ import { fireEvent, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { rest } from "msw";
import { createMemoryRouter } from "react-router-dom";
import { Language } from "./SignInForm/SignInForm";
import { Language } from "./SignInForm";
import {
render,
renderWithRouter,
waitForLoaderToBeRemoved,
} from "../../testHelpers/renderHelpers";
import { server } from "../../testHelpers/server";
} from "testHelpers/renderHelpers";
import { server } from "testHelpers/server";
import { LoginPage } from "./LoginPage";
import * as TypesGen from "api/typesGenerated";
import { i18n } from "i18n";
@@ -25,14 +25,6 @@ describe("LoginPage", () => {
);
});
it("renders the sign-in form", async () => {
// When
render(<LoginPage />);
// Then
await screen.findByText(Language.passwordSignIn);
});
it("shows an error message if SignIn fails", async () => {
// Given
const apiErrorMessage = "Something wrong happened";
@@ -59,28 +51,6 @@ describe("LoginPage", () => {
expect(errorMessage).toBeDefined();
});
it("shows github authentication when enabled", async () => {
const authMethods: TypesGen.AuthMethods = {
password: { enabled: true },
github: { enabled: true },
oidc: { enabled: true, signInText: "", iconUrl: "" },
};
// Given
server.use(
rest.get("/api/v2/users/authmethods", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(authMethods));
}),
);
// When
render(<LoginPage />);
// Then
expect(screen.queryByText(Language.passwordSignIn)).not.toBeInTheDocument();
await screen.findByText(Language.githubSignIn);
});
it("redirects to the setup page if there is no first user", async () => {
// Given
server.use(
@@ -1,59 +1,62 @@
import { action } from "@storybook/addon-actions";
import { ComponentMeta, Story } from "@storybook/react";
import { MockAuthMethods } from "testHelpers/entities";
import { LoginPageView, LoginPageViewProps } from "./LoginPageView";
import { LoginPageView } from "./LoginPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof LoginPageView> = {
title: "pages/LoginPageView",
component: LoginPageView,
} as ComponentMeta<typeof LoginPageView>;
};
const Template: Story<LoginPageViewProps> = (args) => (
<LoginPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof LoginPageView>;
export const Example = Template.bind({});
Example.args = {
isLoading: false,
onSignIn: action("onSignIn"),
context: {
data: {
authMethods: MockAuthMethods,
hasFirstUser: false,
export const Example: Story = {
args: {
isLoading: false,
onSignIn: action("onSignIn"),
context: {
data: {
authMethods: MockAuthMethods,
hasFirstUser: false,
},
},
},
};
const err = new Error("Username or email are wrong.");
export const AuthError = Template.bind({});
AuthError.args = {
isLoading: false,
onSignIn: action("onSignIn"),
context: {
error: err,
data: {
authMethods: MockAuthMethods,
hasFirstUser: false,
export const AuthError: Story = {
args: {
isLoading: false,
onSignIn: action("onSignIn"),
context: {
error: err,
data: {
authMethods: MockAuthMethods,
hasFirstUser: false,
},
},
},
};
export const LoadingInitialData = Template.bind({});
LoadingInitialData.args = {
isLoading: true,
onSignIn: action("onSignIn"),
context: {},
export const LoadingInitialData: Story = {
args: {
isLoading: true,
onSignIn: action("onSignIn"),
context: {},
},
};
export const SigningIn = Template.bind({});
SigningIn.args = {
isSigningIn: true,
onSignIn: action("onSignIn"),
context: {
data: {
authMethods: MockAuthMethods,
hasFirstUser: false,
export const SigningIn: Story = {
args: {
isSigningIn: true,
onSignIn: action("onSignIn"),
context: {
data: {
authMethods: MockAuthMethods,
hasFirstUser: false,
},
},
},
};
+1 -1
View File
@@ -3,7 +3,7 @@ import { FullScreenLoader } from "components/Loader/FullScreenLoader";
import { FC } from "react";
import { useLocation } from "react-router-dom";
import { AuthContext, UnauthenticatedData } from "xServices/auth/authXService";
import { SignInForm } from "pages/LoginPage/SignInForm/SignInForm";
import { SignInForm } from "./SignInForm";
import { retrieveRedirect } from "utils/redirect";
import { CoderIcon } from "components/Icons/CoderIcon";
@@ -4,7 +4,7 @@ import GitHubIcon from "@mui/icons-material/GitHub";
import KeyIcon from "@mui/icons-material/VpnKey";
import Box from "@mui/material/Box";
import { Language } from "./SignInForm";
import { AuthMethods } from "../../../api/typesGenerated";
import { AuthMethods } from "api/typesGenerated";
import { FC } from "react";
import { makeStyles } from "@mui/styles";
@@ -1,7 +1,7 @@
import { Stack } from "../../../components/Stack/Stack";
import { Stack } from "components/Stack/Stack";
import TextField from "@mui/material/TextField";
import { getFormHelpers, onChangeTrimmed } from "../../../utils/formUtils";
import { LoadingButton } from "../../../components/LoadingButton/LoadingButton";
import { getFormHelpers, onChangeTrimmed } from "utils/formUtils";
import { LoadingButton } from "components/LoadingButton/LoadingButton";
import { Language } from "./SignInForm";
import { FormikContextType, FormikTouched, useFormik } from "formik";
import * as Yup from "yup";
@@ -0,0 +1,94 @@
import { mockApiError } from "testHelpers/entities";
import { SignInForm } from "./SignInForm";
import type { Meta, StoryObj } from "@storybook/react";
const meta: Meta<typeof SignInForm> = {
title: "components/SignInForm",
component: SignInForm,
args: {
isSigningIn: false,
},
};
export default meta;
type Story = StoryObj<typeof SignInForm>;
export const SignedOut: Story = {};
export const SigningIn: Story = {
args: {
isSigningIn: true,
authMethods: {
password: { enabled: true },
github: { enabled: true },
oidc: { enabled: false, signInText: "", iconUrl: "" },
},
},
};
export const WithError: Story = {
args: {
error: mockApiError({
message: "Email or password was invalid",
validations: [
{
field: "password",
detail: "Password is invalid.",
},
],
}),
initialTouched: {
password: true,
},
},
};
export const WithGithub: Story = {
args: {
authMethods: {
password: { enabled: true },
github: { enabled: true },
oidc: { enabled: false, signInText: "", iconUrl: "" },
},
},
};
export const WithOIDC: Story = {
args: {
authMethods: {
password: { enabled: true },
github: { enabled: false },
oidc: { enabled: true, signInText: "", iconUrl: "" },
},
},
};
export const WithOIDCWithoutPassword: Story = {
args: {
authMethods: {
password: { enabled: false },
github: { enabled: false },
oidc: { enabled: true, signInText: "", iconUrl: "" },
},
},
};
export const WithoutAny: Story = {
args: {
authMethods: {
password: { enabled: false },
github: { enabled: false },
oidc: { enabled: false, signInText: "", iconUrl: "" },
},
},
};
export const WithGithubAndOIDC: Story = {
args: {
authMethods: {
password: { enabled: true },
github: { enabled: true },
oidc: { enabled: true, signInText: "", iconUrl: "" },
},
},
};
@@ -1,9 +1,9 @@
import { makeStyles } from "@mui/styles";
import { FormikTouched } from "formik";
import { FC, useState } from "react";
import { AuthMethods } from "../../../api/typesGenerated";
import { AuthMethods } from "api/typesGenerated";
import { useTranslation } from "react-i18next";
import { Maybe } from "../../../components/Conditionals/Maybe";
import { Maybe } from "components/Conditionals/Maybe";
import { PasswordSignInForm } from "./PasswordSignInForm";
import { OAuthSignInForm } from "./OAuthSignInForm";
import { BuiltInAuthFormValues } from "./SignInForm.types";
@@ -1,102 +0,0 @@
import { Story } from "@storybook/react";
import { mockApiError } from "testHelpers/entities";
import { SignInForm, SignInFormProps } from "./SignInForm";
export default {
title: "components/SignInForm",
component: SignInForm,
argTypes: {
isLoading: "boolean",
onSubmit: { action: "Submit" },
},
};
const Template: Story<SignInFormProps> = (args: SignInFormProps) => (
<SignInForm {...args} />
);
export const SignedOut = Template.bind({});
SignedOut.args = {
isSigningIn: false,
onSubmit: () => {
return Promise.resolve();
},
};
export const SigningIn = Template.bind({});
SigningIn.args = {
...SignedOut.args,
isSigningIn: true,
authMethods: {
password: { enabled: true },
github: { enabled: true },
oidc: { enabled: false, signInText: "", iconUrl: "" },
},
};
export const WithError = Template.bind({});
WithError.args = {
...SignedOut.args,
error: mockApiError({
message: "Email or password was invalid",
validations: [
{
field: "password",
detail: "Password is invalid.",
},
],
}),
initialTouched: {
password: true,
},
};
export const WithGithub = Template.bind({});
WithGithub.args = {
...SignedOut.args,
authMethods: {
password: { enabled: true },
github: { enabled: true },
oidc: { enabled: false, signInText: "", iconUrl: "" },
},
};
export const WithOIDC = Template.bind({});
WithOIDC.args = {
...SignedOut.args,
authMethods: {
password: { enabled: true },
github: { enabled: false },
oidc: { enabled: true, signInText: "", iconUrl: "" },
},
};
export const WithOIDCWithoutPassword = Template.bind({});
WithOIDCWithoutPassword.args = {
...SignedOut.args,
authMethods: {
password: { enabled: false },
github: { enabled: false },
oidc: { enabled: true, signInText: "", iconUrl: "" },
},
};
export const WithoutAny = Template.bind({});
WithoutAny.args = {
...SignedOut.args,
authMethods: {
password: { enabled: false },
github: { enabled: false },
oidc: { enabled: false, signInText: "", iconUrl: "" },
},
};
export const WithGithubAndOIDC = Template.bind({});
WithGithubAndOIDC.args = {
...SignedOut.args,
authMethods: {
password: { enabled: true },
github: { enabled: true },
oidc: { enabled: true, signInText: "", iconUrl: "" },
},
};
@@ -1,32 +1,27 @@
import { action } from "@storybook/addon-actions";
import { Story } from "@storybook/react";
import { SetupPageView, SetupPageViewProps } from "./SetupPageView";
import { SetupPageView } from "./SetupPageView";
import { mockApiError } from "testHelpers/entities";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof SetupPageView> = {
title: "pages/SetupPageView",
component: SetupPageView,
};
const Template: Story<SetupPageViewProps> = (args: SetupPageViewProps) => (
<SetupPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof SetupPageView>;
export const Ready = Template.bind({});
Ready.args = {
onSubmit: action("submit"),
export const Ready: Story = {};
export const FormError: Story = {
args: {
error: mockApiError({
validations: [{ field: "username", detail: "Username taken" }],
}),
},
};
export const FormError = Template.bind({});
FormError.args = {
onSubmit: action("submit"),
error: mockApiError({
validations: [{ field: "username", detail: "Username taken" }],
}),
};
export const Loading = Template.bind({});
Loading.args = {
onSubmit: action("submit"),
isLoading: true,
export const Loading: Story = {
args: {
isLoading: true,
},
};
@@ -1,20 +0,0 @@
import { screen } from "@testing-library/react";
import { MockTemplateExample } from "testHelpers/entities";
import {
renderWithAuth,
waitForLoaderToBeRemoved,
} from "testHelpers/renderHelpers";
import StarterTemplatePage from "./StarterTemplatePage";
jest.mock("remark-gfm", () => jest.fn());
describe("StarterTemplatePage", () => {
it("shows the starter template", async () => {
renderWithAuth(<StarterTemplatePage />, {
route: `/starter-templates/${MockTemplateExample.id}`,
path: "/starter-templates/:exampleId",
});
await waitForLoaderToBeRemoved();
expect(screen.getByText(MockTemplateExample.name)).toBeInTheDocument();
});
});
@@ -1,41 +1,39 @@
import { Story } from "@storybook/react";
import {
mockApiError,
MockOrganization,
MockTemplateExample,
} from "testHelpers/entities";
import {
StarterTemplatePageView,
StarterTemplatePageViewProps,
} from "./StarterTemplatePageView";
import { StarterTemplatePageView } from "./StarterTemplatePageView";
export default {
import type { Meta, StoryObj } from "@storybook/react";
const meta: Meta<typeof StarterTemplatePageView> = {
title: "pages/StarterTemplatePageView",
component: StarterTemplatePageView,
};
const Template: Story<StarterTemplatePageViewProps> = (args) => (
<StarterTemplatePageView {...args} />
);
export default meta;
type Story = StoryObj<typeof StarterTemplatePageView>;
export const Default = Template.bind({});
Default.args = {
context: {
exampleId: MockTemplateExample.id,
organizationId: MockOrganization.id,
error: undefined,
starterTemplate: MockTemplateExample,
export const Default: Story = {
args: {
context: {
exampleId: MockTemplateExample.id,
organizationId: MockOrganization.id,
error: undefined,
starterTemplate: MockTemplateExample,
},
},
};
export const Error = Template.bind({});
Error.args = {
context: {
exampleId: MockTemplateExample.id,
organizationId: MockOrganization.id,
error: mockApiError({
message: `Example ${MockTemplateExample.id} not found.`,
}),
starterTemplate: undefined,
export const Error: Story = {
args: {
context: {
exampleId: MockTemplateExample.id,
organizationId: MockOrganization.id,
error: mockApiError({
message: `Example ${MockTemplateExample.id} not found.`,
}),
starterTemplate: undefined,
},
},
};
@@ -1,22 +0,0 @@
import { screen } from "@testing-library/react";
import {
MockTemplateExample,
MockTemplateExample2,
} from "testHelpers/entities";
import {
renderWithAuth,
waitForLoaderToBeRemoved,
} from "testHelpers/renderHelpers";
import StarterTemplatesPage from "./StarterTemplatesPage";
describe("StarterTemplatesPage", () => {
it("shows the starter template", async () => {
renderWithAuth(<StarterTemplatesPage />, {
route: `/starter-templates`,
path: "/starter-templates",
});
await waitForLoaderToBeRemoved();
expect(screen.getByText(MockTemplateExample.name)).toBeInTheDocument();
expect(screen.getByText(MockTemplateExample2.name)).toBeInTheDocument();
});
});
@@ -1,4 +1,3 @@
import { Story } from "@storybook/react";
import {
mockApiError,
MockOrganization,
@@ -6,39 +5,38 @@ import {
MockTemplateExample2,
} from "testHelpers/entities";
import { getTemplatesByTag } from "utils/starterTemplates";
import {
StarterTemplatesPageView,
StarterTemplatesPageViewProps,
} from "./StarterTemplatesPageView";
import { StarterTemplatesPageView } from "./StarterTemplatesPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof StarterTemplatesPageView> = {
title: "pages/StarterTemplatesPageView",
component: StarterTemplatesPageView,
};
const Template: Story<StarterTemplatesPageViewProps> = (args) => (
<StarterTemplatesPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof StarterTemplatesPageView>;
export const Default = Template.bind({});
Default.args = {
context: {
organizationId: MockOrganization.id,
error: undefined,
starterTemplatesByTag: getTemplatesByTag([
MockTemplateExample,
MockTemplateExample2,
]),
export const Default: Story = {
args: {
context: {
organizationId: MockOrganization.id,
error: undefined,
starterTemplatesByTag: getTemplatesByTag([
MockTemplateExample,
MockTemplateExample2,
]),
},
},
};
export const Error = Template.bind({});
Error.args = {
context: {
organizationId: MockOrganization.id,
error: mockApiError({
message: "Error on loading the template examples",
}),
starterTemplatesByTag: undefined,
export const Error: Story = {
args: {
context: {
organizationId: MockOrganization.id,
error: mockApiError({
message: "Error on loading the template examples",
}),
starterTemplatesByTag: undefined,
},
},
};
@@ -1,31 +0,0 @@
import { screen } from "@testing-library/react";
import { TemplateLayout } from "components/TemplateLayout/TemplateLayout";
import { ResizeObserver } from "resize-observer";
import { renderWithAuth } from "testHelpers/renderHelpers";
import TemplateDocsPage from "./TemplateDocsPage";
jest.mock("remark-gfm", () => jest.fn());
const TEMPLATE_NAME = "coder-ts";
Object.defineProperty(window, "ResizeObserver", {
value: ResizeObserver,
});
const renderPage = () =>
renderWithAuth(
<TemplateLayout>
<TemplateDocsPage />
</TemplateLayout>,
{
route: `/templates/${TEMPLATE_NAME}/docs`,
path: "/templates/:template/docs",
},
);
describe("TemplateSummaryPage", () => {
it("shows the template readme", async () => {
renderPage();
await screen.findByTestId("markdown");
});
});
@@ -1,57 +1,61 @@
import { Story } from "@storybook/react";
import { MockTemplate, MockTemplateVersion } from "testHelpers/entities";
import { TemplateStats, TemplateStatsProps } from "./TemplateStats";
import { TemplateStats } from "./TemplateStats";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof TemplateStats> = {
title: "components/TemplateStats",
component: TemplateStats,
};
const Template: Story<TemplateStatsProps> = (args) => (
<TemplateStats {...args} />
);
export default meta;
type Story = StoryObj<typeof TemplateStats>;
export const Example = Template.bind({});
Example.args = {
template: MockTemplate,
activeVersion: MockTemplateVersion,
};
export const UsedByMany = Template.bind({});
UsedByMany.args = {
template: {
...MockTemplate,
active_user_count: 15,
},
activeVersion: MockTemplateVersion,
};
export const ActiveUsersNotLoaded = Template.bind({});
ActiveUsersNotLoaded.args = {
template: {
...MockTemplate,
active_user_count: -1,
},
activeVersion: MockTemplateVersion,
};
export const LongTemplateVersion = Template.bind({});
LongTemplateVersion.args = {
template: MockTemplate,
activeVersion: {
...MockTemplateVersion,
name: "thisisareallyreallylongnamefortesting",
export const Example: Story = {
args: {
template: MockTemplate,
activeVersion: MockTemplateVersion,
},
};
LongTemplateVersion.parameters = {
chromatic: { viewports: [960] },
export const UsedByMany: Story = {
args: {
template: {
...MockTemplate,
active_user_count: 15,
},
activeVersion: MockTemplateVersion,
},
};
export const SmallViewport = Template.bind({});
SmallViewport.args = {
template: MockTemplate,
activeVersion: MockTemplateVersion,
export const ActiveUsersNotLoaded: Story = {
args: {
template: {
...MockTemplate,
active_user_count: -1,
},
activeVersion: MockTemplateVersion,
},
};
SmallViewport.parameters = {
chromatic: { viewports: [600] },
export const LongTemplateVersion: Story = {
args: {
template: MockTemplate,
activeVersion: {
...MockTemplateVersion,
name: "thisisareallyreallylongnamefortesting",
},
},
parameters: {
chromatic: { viewports: [960] },
},
};
export const SmallViewport: Story = {
args: {
template: MockTemplate,
activeVersion: MockTemplateVersion,
},
parameters: {
chromatic: { viewports: [600] },
},
};
@@ -1,53 +0,0 @@
import { screen } from "@testing-library/react";
import { TemplateLayout } from "components/TemplateLayout/TemplateLayout";
import { rest } from "msw";
import { ResizeObserver } from "resize-observer";
import {
MockTemplate,
MockTemplateVersion,
MockMemberPermissions,
} from "testHelpers/entities";
import { renderWithAuth } from "testHelpers/renderHelpers";
import { server } from "testHelpers/server";
import * as CreateDayString from "utils/createDayString";
import { TemplateSummaryPage } from "./TemplateSummaryPage";
jest.mock("remark-gfm", () => jest.fn());
Object.defineProperty(window, "ResizeObserver", {
value: ResizeObserver,
});
const renderPage = () =>
renderWithAuth(
<TemplateLayout>
<TemplateSummaryPage />
</TemplateLayout>,
{
route: `/templates/${MockTemplate.id}`,
path: "/templates/:template",
},
);
describe("TemplateSummaryPage", () => {
it("shows the template name and resources", async () => {
// Mocking the dayjs module within the createDayString file
const mock = jest.spyOn(CreateDayString, "createDayString");
mock.mockImplementation(() => "a minute ago");
renderPage();
await screen.findByText(MockTemplate.display_name);
screen.queryAllByText(`${MockTemplateVersion.name}`).length;
});
it("does not allow a member to delete a template", () => {
// get member-level permissions
server.use(
rest.post("/api/v2/authcheck", async (req, res, ctx) => {
return res(ctx.status(200), ctx.json(MockMemberPermissions));
}),
);
renderPage();
const dropdownButton = screen.queryByLabelText("open-dropdown");
expect(dropdownButton).toBe(null);
});
});
@@ -1,48 +1,50 @@
import { action } from "@storybook/addon-actions";
import { ComponentMeta, Story } from "@storybook/react";
import { MockTemplateVersion } from "testHelpers/entities";
import { VersionsTable, VersionsTableProps } from "./VersionsTable";
import { VersionsTable } from "./VersionsTable";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof VersionsTable> = {
title: "components/VersionsTable",
component: VersionsTable,
} as ComponentMeta<typeof VersionsTable>;
const Template: Story<VersionsTableProps> = (args) => (
<VersionsTable {...args} />
);
export const Example = Template.bind({});
Example.args = {
activeVersionId: MockTemplateVersion.id,
versions: [
{
...MockTemplateVersion,
id: "2",
name: "test-template-version-2",
created_at: "2022-05-18T18:39:01.382927298Z",
},
MockTemplateVersion,
],
onPromoteClick: undefined,
};
export const CanPromote = Template.bind({});
CanPromote.args = {
activeVersionId: MockTemplateVersion.id,
onPromoteClick: action("onPromoteClick"),
versions: [
{
...MockTemplateVersion,
id: "2",
name: "test-template-version-2",
created_at: "2022-05-18T18:39:01.382927298Z",
},
MockTemplateVersion,
],
export default meta;
type Story = StoryObj<typeof VersionsTable>;
export const Example: Story = {
args: {
activeVersionId: MockTemplateVersion.id,
versions: [
{
...MockTemplateVersion,
id: "2",
name: "test-template-version-2",
created_at: "2022-05-18T18:39:01.382927298Z",
},
MockTemplateVersion,
],
onPromoteClick: undefined,
},
};
export const Empty = Template.bind({});
Empty.args = {
versions: [],
export const CanPromote: Story = {
args: {
activeVersionId: MockTemplateVersion.id,
onPromoteClick: action("onPromoteClick"),
versions: [
{
...MockTemplateVersion,
id: "2",
name: "test-template-version-2",
created_at: "2022-05-18T18:39:01.382927298Z",
},
MockTemplateVersion,
],
},
};
export const Empty: Story = {
args: {
versions: [],
},
};
@@ -1,40 +1,33 @@
import { action } from "@storybook/addon-actions";
import { Story } from "@storybook/react";
import { mockApiError, MockTemplate } from "testHelpers/entities";
import {
TemplateSettingsPageView,
TemplateSettingsPageViewProps,
} from "./TemplateSettingsPageView";
import { TemplateSettingsPageView } from "./TemplateSettingsPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof TemplateSettingsPageView> = {
title: "pages/TemplateSettingsPageView",
component: TemplateSettingsPageView,
args: {
template: MockTemplate,
onSubmit: action("onSubmit"),
onCancel: action("cancel"),
},
};
const Template: Story<TemplateSettingsPageViewProps> = (args) => (
<TemplateSettingsPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof TemplateSettingsPageView>;
export const Example = Template.bind({});
Example.args = {};
export const Example: Story = {};
export const SaveTemplateSettingsError = Template.bind({});
SaveTemplateSettingsError.args = {
submitError: mockApiError({
message: 'Template "test" already exists.',
validations: [
{
field: "name",
detail: "This value is already in use and should be unique.",
},
],
}),
initialTouched: {
allow_user_cancel_workspace_jobs: true,
export const SaveTemplateSettingsError: Story = {
args: {
submitError: mockApiError({
message: 'Template "test" already exists.',
validations: [
{
field: "name",
detail: "This value is already in use and should be unique.",
},
],
}),
initialTouched: {
allow_user_cancel_workspace_jobs: true,
},
},
};
@@ -1,38 +1,37 @@
import { Story } from "@storybook/react";
import {
MockOrganization,
MockTemplateACL,
MockTemplateACLEmpty,
} from "testHelpers/entities";
import {
TemplatePermissionsPageView,
TemplatePermissionsPageViewProps,
} from "./TemplatePermissionsPageView";
import { TemplatePermissionsPageView } from "./TemplatePermissionsPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof TemplatePermissionsPageView> = {
title: "pages/TemplatePermissionsPageView",
component: TemplatePermissionsPageView,
};
const Template: Story<TemplatePermissionsPageViewProps> = (
args: TemplatePermissionsPageViewProps,
) => <TemplatePermissionsPageView {...args} />;
export default meta;
type Story = StoryObj<typeof TemplatePermissionsPageView>;
export const Empty = Template.bind({});
Empty.args = {
templateACL: MockTemplateACLEmpty,
canUpdatePermissions: false,
export const Empty: Story = {
args: {
templateACL: MockTemplateACLEmpty,
canUpdatePermissions: false,
},
};
export const WithTemplateACL = Template.bind({});
WithTemplateACL.args = {
templateACL: MockTemplateACL,
canUpdatePermissions: false,
export const WithTemplateACL: Story = {
args: {
templateACL: MockTemplateACL,
canUpdatePermissions: false,
},
};
export const WithUpdatePermissions = Template.bind({});
WithUpdatePermissions.args = {
templateACL: MockTemplateACL,
canUpdatePermissions: true,
organizationId: MockOrganization.id,
export const WithUpdatePermissions: Story = {
args: {
templateACL: MockTemplateACL,
canUpdatePermissions: true,
organizationId: MockOrganization.id,
},
};
@@ -10,10 +10,7 @@ import {
renderWithTemplateSettingsLayout,
waitForLoaderToBeRemoved,
} from "testHelpers/renderHelpers";
import {
TemplateScheduleFormValues,
getValidationSchema,
} from "./TemplateScheduleForm/formHelpers";
import { TemplateScheduleFormValues, getValidationSchema } from "./formHelpers";
import TemplateSchedulePage from "./TemplateSchedulePage";
import i18next from "i18next";
@@ -1,6 +1,6 @@
import { Template, UpdateTemplateMeta } from "api/typesGenerated";
import { ComponentProps, FC } from "react";
import { TemplateScheduleForm } from "./TemplateScheduleForm/TemplateScheduleForm";
import { TemplateScheduleForm } from "./TemplateScheduleForm";
import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader";
import { makeStyles } from "@mui/styles";
@@ -1,5 +1,3 @@
import { action } from "@storybook/addon-actions";
import { Story } from "@storybook/react";
import {
mockApiError,
MockTemplateVersion,
@@ -9,87 +7,77 @@ import {
MockTemplateVersionVariable4,
MockTemplateVersionVariable5,
} from "testHelpers/entities";
import {
TemplateVariablesPageView,
TemplateVariablesPageViewProps,
} from "./TemplateVariablesPageView";
import { TemplateVariablesPageView } from "./TemplateVariablesPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof TemplateVariablesPageView> = {
title: "pages/TemplateVariablesPageView",
component: TemplateVariablesPageView,
};
const TemplateVariables: Story<TemplateVariablesPageViewProps> = (args) => (
<TemplateVariablesPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof TemplateVariablesPageView>;
export const Loading = TemplateVariables.bind({});
Loading.args = {
onSubmit: action("onSubmit"),
onCancel: action("cancel"),
};
export const Loading: Story = {};
export const Basic = TemplateVariables.bind({});
Basic.args = {
templateVersion: MockTemplateVersion,
templateVariables: [
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
],
onSubmit: action("onSubmit"),
onCancel: action("cancel"),
export const Basic: Story = {
args: {
templateVersion: MockTemplateVersion,
templateVariables: [
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
],
},
};
// This example isn't fully supported. As "user_variable_values" is an array,
// FormikTouched can't properly handle this.
// See: https://github.com/jaredpalmer/formik/issues/2022
export const RequiredVariable = TemplateVariables.bind({});
RequiredVariable.args = {
templateVersion: MockTemplateVersion,
templateVariables: [
MockTemplateVersionVariable4,
MockTemplateVersionVariable5,
],
onSubmit: action("onSubmit"),
onCancel: action("cancel"),
initialTouched: {
user_variable_values: true,
export const RequiredVariable: Story = {
args: {
templateVersion: MockTemplateVersion,
templateVariables: [
MockTemplateVersionVariable4,
MockTemplateVersionVariable5,
],
initialTouched: {
user_variable_values: true,
},
},
};
export const WithUpdateTemplateError = TemplateVariables.bind({});
WithUpdateTemplateError.args = {
templateVersion: MockTemplateVersion,
templateVariables: [
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
],
errors: {
updateTemplateError: mockApiError({
message: "Something went wrong.",
}),
export const WithUpdateTemplateError: Story = {
args: {
templateVersion: MockTemplateVersion,
templateVariables: [
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
],
errors: {
updateTemplateError: mockApiError({
message: "Something went wrong.",
}),
},
},
onSubmit: action("onSubmit"),
onCancel: action("cancel"),
};
export const WithJobError = TemplateVariables.bind({});
WithJobError.args = {
templateVersion: MockTemplateVersion,
templateVariables: [
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
],
errors: {
jobError:
"template import provision for start: recv import provision: plan terraform: terraform plan: exit status 1",
export const WithJobError: Story = {
args: {
templateVersion: MockTemplateVersion,
templateVariables: [
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
],
errors: {
jobError:
"template import provision for start: recv import provision: plan terraform: terraform plan: exit status 1",
},
},
onSubmit: action("onSubmit"),
onCancel: action("cancel"),
};
@@ -7,7 +7,7 @@ import {
MockTemplateVersion,
MockWorkspaceBuildLogs,
} from "testHelpers/entities";
import { Language } from "./TemplateVersionEditor/PublishTemplateVersionDialog";
import { Language } from "./PublishTemplateVersionDialog";
// For some reason this component in Jest is throwing a MUI style warning so,
// since we don't need it for this test, we can mock it out
@@ -1,5 +1,5 @@
import { useMachine } from "@xstate/react";
import { TemplateVersionEditor } from "pages/TemplateVersionEditorPage/TemplateVersionEditor/TemplateVersionEditor";
import { TemplateVersionEditor } from "./TemplateVersionEditor";
import { useOrganizationId } from "hooks/useOrganizationId";
import { usePermissions } from "hooks/usePermissions";
import { FC } from "react";
@@ -1,5 +1,4 @@
import { action } from "@storybook/addon-actions";
import { Story } from "@storybook/react";
import { UseTabResult } from "hooks/useTab";
import {
mockApiError,
@@ -11,15 +10,7 @@ import {
TemplateVersionPageView,
TemplateVersionPageViewProps,
} from "./TemplateVersionPageView";
export default {
title: "pages/TemplateVersionPageView",
component: TemplateVersionPageView,
};
const Template: Story<TemplateVersionPageViewProps> = (args) => (
<TemplateVersionPageView {...args} />
);
import type { Meta, StoryObj } from "@storybook/react";
const tab: UseTabResult = {
value: "0",
@@ -53,18 +44,26 @@ const defaultArgs: TemplateVersionPageViewProps = {
},
};
export const Default = Template.bind({});
Default.args = defaultArgs;
const meta: Meta<typeof TemplateVersionPageView> = {
title: "pages/TemplateVersionPageView",
component: TemplateVersionPageView,
args: defaultArgs,
};
export const Error = Template.bind({});
Error.args = {
...defaultArgs,
context: {
...defaultArgs.context,
currentVersion: undefined,
currentFiles: undefined,
error: mockApiError({
message: "Error on loading the template version",
}),
export default meta;
type Story = StoryObj<typeof TemplateVersionPageView>;
export const Default: Story = {};
export const Error: Story = {
args: {
context: {
...defaultArgs.context,
currentVersion: undefined,
currentFiles: undefined,
error: mockApiError({
message: "Error on loading the template version",
}),
},
},
};
@@ -1,91 +0,0 @@
import { screen } from "@testing-library/react";
import { rest } from "msw";
import * as CreateDayString from "utils/createDayString";
import { MockTemplate } from "../../testHelpers/entities";
import { renderWithAuth } from "../../testHelpers/renderHelpers";
import { server } from "../../testHelpers/server";
import { TemplatesPage } from "./TemplatesPage";
import i18next from "i18next";
const { t } = i18next;
describe("TemplatesPage", () => {
beforeEach(() => {
// Mocking the dayjs module within the createDayString file
const mock = jest.spyOn(CreateDayString, "createDayString");
mock.mockImplementation(() => "a minute ago");
});
it("renders an empty templates page", async () => {
// Given
server.use(
rest.get(
"/api/v2/organizations/:organizationId/templates",
(req, res, ctx) => {
return res(ctx.status(200), ctx.json([]));
},
),
rest.post("/api/v2/authcheck", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
createTemplates: true,
}),
);
}),
);
// When
renderWithAuth(<TemplatesPage />, {
route: `/templates`,
path: "/templates",
});
// Then
const emptyMessage = t("empty.message", {
ns: "templatesPage",
});
await screen.findByText(emptyMessage);
});
it("renders a filled templates page", async () => {
// When
renderWithAuth(<TemplatesPage />, {
route: `/templates`,
path: "/templates",
});
// Then
await screen.findByText(MockTemplate.display_name);
});
it("shows empty view without permissions to create", async () => {
server.use(
rest.get(
"/api/v2/organizations/:organizationId/templates",
(req, res, ctx) => {
return res(ctx.status(200), ctx.json([]));
},
),
rest.post("/api/v2/authcheck", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
createTemplates: false,
}),
);
}),
);
// When
renderWithAuth(<TemplatesPage />, {
route: `/templates`,
path: "/templates",
});
// Then
const emptyMessage = t("empty.descriptionWithoutPermissions", {
ns: "templatesPage",
});
await screen.findByText(emptyMessage);
});
});
@@ -1,4 +1,3 @@
import { ComponentMeta, Story } from "@storybook/react";
import {
mockApiError,
MockOrganization,
@@ -7,92 +6,97 @@ import {
MockTemplateExample,
MockTemplateExample2,
} from "../../testHelpers/entities";
import { TemplatesPageView, TemplatesPageViewProps } from "./TemplatesPageView";
import { TemplatesPageView } from "./TemplatesPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof TemplatesPageView> = {
title: "pages/TemplatesPageView",
component: TemplatesPageView,
} as ComponentMeta<typeof TemplatesPageView>;
const Template: Story<TemplatesPageViewProps> = (args) => (
<TemplatesPageView {...args} />
);
export const WithTemplates = Template.bind({});
WithTemplates.args = {
context: {
organizationId: MockOrganization.id,
permissions: MockPermissions,
error: undefined,
templates: [
MockTemplate,
{
...MockTemplate,
active_user_count: -1,
description: "🚀 Some new template that has no activity data",
icon: "/icon/goland.svg",
},
{
...MockTemplate,
active_user_count: 150,
description: "😮 Wow, this one has a bunch of usage!",
icon: "",
},
{
...MockTemplate,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
},
],
examples: [],
},
};
export const WithTemplatesSmallViewPort = Template.bind({});
WithTemplatesSmallViewPort.args = {
...WithTemplates.args,
};
WithTemplatesSmallViewPort.parameters = {
chromatic: { viewports: [600] },
};
export default meta;
type Story = StoryObj<typeof TemplatesPageView>;
export const EmptyCanCreate = Template.bind({});
EmptyCanCreate.args = {
context: {
organizationId: MockOrganization.id,
permissions: MockPermissions,
error: undefined,
templates: [],
examples: [MockTemplateExample, MockTemplateExample2],
},
};
export const EmptyCannotCreate = Template.bind({});
EmptyCannotCreate.args = {
context: {
organizationId: MockOrganization.id,
permissions: {
...MockPermissions,
createTemplates: false,
export const WithTemplates: Story = {
args: {
context: {
organizationId: MockOrganization.id,
permissions: MockPermissions,
error: undefined,
templates: [
MockTemplate,
{
...MockTemplate,
active_user_count: -1,
description: "🚀 Some new template that has no activity data",
icon: "/icon/goland.svg",
},
{
...MockTemplate,
active_user_count: 150,
description: "😮 Wow, this one has a bunch of usage!",
icon: "",
},
{
...MockTemplate,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ",
},
],
examples: [],
},
error: undefined,
templates: [],
examples: [MockTemplateExample, MockTemplateExample2],
},
};
export const Error = Template.bind({});
Error.args = {
context: {
organizationId: MockOrganization.id,
permissions: {
...MockPermissions,
createTemplates: false,
export const WithTemplatesSmallViewPort: Story = {
args: {
...WithTemplates.args,
},
parameters: {
chromatic: { viewports: [600] },
},
};
export const EmptyCanCreate: Story = {
args: {
context: {
organizationId: MockOrganization.id,
permissions: MockPermissions,
error: undefined,
templates: [],
examples: [MockTemplateExample, MockTemplateExample2],
},
error: mockApiError({
message: "Something went wrong fetching templates.",
}),
templates: undefined,
examples: undefined,
},
};
export const EmptyCannotCreate: Story = {
args: {
context: {
organizationId: MockOrganization.id,
permissions: {
...MockPermissions,
createTemplates: false,
},
error: undefined,
templates: [],
examples: [MockTemplateExample, MockTemplateExample2],
},
},
};
export const Error: Story = {
args: {
context: {
organizationId: MockOrganization.id,
permissions: {
...MockPermissions,
createTemplates: false,
},
error: mockApiError({
message: "Something went wrong fetching templates.",
}),
templates: undefined,
examples: undefined,
},
},
};
@@ -1,51 +1,43 @@
import { Story } from "@storybook/react";
import { AccountForm, AccountFormProps } from "./AccountForm";
import type { Meta, StoryObj } from "@storybook/react";
import { AccountForm } from "./AccountForm";
import { mockApiError } from "testHelpers/entities";
export default {
const meta: Meta<typeof AccountForm> = {
title: "components/AccountForm",
component: AccountForm,
argTypes: {
onSubmit: { action: "Submit" },
args: {
email: "test-user@org.com",
isLoading: false,
initialValues: {
username: "test-user",
},
updateProfileError: undefined,
},
};
const Template: Story<AccountFormProps> = (args: AccountFormProps) => (
<AccountForm {...args} />
);
export default meta;
type Story = StoryObj<typeof AccountForm>;
export const Example = Template.bind({});
Example.args = {
email: "test-user@org.com",
isLoading: false,
initialValues: {
username: "test-user",
},
updateProfileError: undefined,
onSubmit: () => {
return Promise.resolve();
export const Example: Story = {};
export const Loading: Story = {
args: {
isLoading: true,
},
};
export const Loading = Template.bind({});
Loading.args = {
...Example.args,
isLoading: true,
};
export const WithError = Template.bind({});
WithError.args = {
...Example.args,
updateProfileError: mockApiError({
message: "Username is invalid",
validations: [
{
field: "username",
detail: "Username is too long.",
},
],
}),
initialTouched: {
username: true,
export const WithError: Story = {
args: {
updateProfileError: mockApiError({
message: "Username is invalid",
validations: [
{
field: "username",
detail: "Username is too long.",
},
],
}),
initialTouched: {
username: true,
},
},
};
@@ -1,53 +1,46 @@
import { Story } from "@storybook/react";
import { mockApiError } from "testHelpers/entities";
import { SSHKeysPageView, SSHKeysPageViewProps } from "./SSHKeysPageView";
import { SSHKeysPageView } from "./SSHKeysPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "components/SSHKeysPageView",
const meta: Meta<typeof SSHKeysPageView> = {
title: "pages/SSHKeysPageView",
component: SSHKeysPageView,
argTypes: {
onRegenerateClick: { action: "Submit" },
args: {
isLoading: false,
hasLoaded: true,
sshKey: {
user_id: "test-user-id",
created_at: "2022-07-28T07:45:50.795918897Z",
updated_at: "2022-07-28T07:45:50.795919142Z",
public_key: "SSH-Key",
},
},
};
const Template: Story<SSHKeysPageViewProps> = (args: SSHKeysPageViewProps) => (
<SSHKeysPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof SSHKeysPageView>;
export const Example = Template.bind({});
Example.args = {
isLoading: false,
hasLoaded: true,
sshKey: {
user_id: "test-user-id",
created_at: "2022-07-28T07:45:50.795918897Z",
updated_at: "2022-07-28T07:45:50.795919142Z",
public_key: "SSH-Key",
},
onRegenerateClick: () => {
return Promise.resolve();
export const Example: Story = {};
export const Loading: Story = {
args: {
isLoading: true,
},
};
export const Loading = Template.bind({});
Loading.args = {
...Example.args,
isLoading: true,
export const WithGetSSHKeyError: Story = {
args: {
hasLoaded: false,
getSSHKeyError: mockApiError({
message: "Failed to get SSH key",
}),
},
};
export const WithGetSSHKeyError = Template.bind({});
WithGetSSHKeyError.args = {
...Example.args,
hasLoaded: false,
getSSHKeyError: mockApiError({
message: "Failed to get SSH key",
}),
};
export const WithRegenerateSSHKeyError = Template.bind({});
WithRegenerateSSHKeyError.args = {
...Example.args,
regenerateSSHKeyError: mockApiError({
message: "Failed to regenerate SSH key",
}),
export const WithRegenerateSSHKeyError: Story = {
args: {
regenerateSSHKeyError: mockApiError({
message: "Failed to regenerate SSH key",
}),
},
};
@@ -1,43 +1,40 @@
import { Story } from "@storybook/react";
import { SecurityForm, SecurityFormProps } from "./SettingsSecurityForm";
import type { Meta, StoryObj } from "@storybook/react";
import { SecurityForm } from "./SettingsSecurityForm";
import { mockApiError } from "testHelpers/entities";
export default {
title: "components/SettingsSecurityForm",
const meta: Meta<typeof SecurityForm> = {
title: "components/SecurityForm",
component: SecurityForm,
argTypes: {
onSubmit: { action: "Submit" },
args: {
isLoading: false,
},
};
const Template: Story<SecurityFormProps> = (args: SecurityFormProps) => (
<SecurityForm {...args} />
);
export default meta;
type Story = StoryObj<typeof SecurityForm>;
export const Example = Template.bind({});
Example.args = {
isLoading: false,
onSubmit: () => {
return Promise.resolve();
export const Example: Story = {
args: {
isLoading: false,
},
};
export const Loading = Template.bind({});
Loading.args = {
...Example.args,
isLoading: true,
export const Loading: Story = {
args: {
isLoading: true,
},
};
export const WithError = Template.bind({});
WithError.args = {
...Example.args,
error: mockApiError({
message: "Old password is incorrect",
validations: [
{
field: "old_password",
detail: "Old password is incorrect.",
},
],
}),
export const WithError: Story = {
args: {
error: mockApiError({
message: "Old password is incorrect",
validations: [
{
field: "old_password",
detail: "Old password is incorrect.",
},
],
}),
},
};
@@ -1,9 +1,6 @@
import { Story } from "@storybook/react";
import type { Meta, StoryObj } from "@storybook/react";
import { MockToken } from "testHelpers/entities";
import {
ConfirmDeleteDialog,
ConfirmDeleteDialogProps,
} from "./ConfirmDeleteDialog";
import { ConfirmDeleteDialog } from "./ConfirmDeleteDialog";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
@@ -16,24 +13,27 @@ const queryClient = new QueryClient({
},
});
export default {
const meta: Meta<typeof ConfirmDeleteDialog> = {
title: "components/ConfirmDeleteDialog",
component: ConfirmDeleteDialog,
decorators: [
(Story) => (
<QueryClientProvider client={queryClient}>
<Story />
</QueryClientProvider>
),
],
};
const Template: Story<ConfirmDeleteDialogProps> = (
args: ConfirmDeleteDialogProps,
) => (
<QueryClientProvider client={queryClient}>
<ConfirmDeleteDialog {...args} />
</QueryClientProvider>
);
export default meta;
type Story = StoryObj<typeof ConfirmDeleteDialog>;
export const DeleteDialog = Template.bind({});
DeleteDialog.args = {
queryKey: ["tokens"],
token: MockToken,
setToken: () => {
return null;
export const DeleteDialog: Story = {
args: {
queryKey: ["tokens"],
token: MockToken,
setToken: () => {
return null;
},
},
};
@@ -1,56 +1,52 @@
import { Story } from "@storybook/react";
import { mockApiError, MockTokens } from "testHelpers/entities";
import { TokensPageView, TokensPageViewProps } from "./TokensPageView";
import { TokensPageView } from "./TokensPageView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
title: "components/TokensPageView",
const meta: Meta<typeof TokensPageView> = {
title: "pages/TokensPageView",
component: TokensPageView,
args: {
onRegenerateClick: { action: "Submit" },
isLoading: false,
hasLoaded: true,
tokens: MockTokens,
onDelete: () => {
return Promise.resolve();
},
},
};
const Template: Story<TokensPageViewProps> = (args: TokensPageViewProps) => (
<TokensPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof TokensPageView>;
export const Example = Template.bind({});
Example.args = {
isLoading: false,
hasLoaded: true,
tokens: MockTokens,
onDelete: () => {
return Promise.resolve();
export const Example: Story = {};
export const Loading: Story = {
args: {
isLoading: true,
hasLoaded: false,
},
};
export const Loading = Template.bind({});
Loading.args = {
...Example.args,
isLoading: true,
hasLoaded: false,
export const Empty: Story = {
args: {
tokens: [],
},
};
export const Empty = Template.bind({});
Empty.args = {
...Example.args,
tokens: [],
export const WithGetTokensError: Story = {
args: {
hasLoaded: false,
getTokensError: mockApiError({
message: "Failed to get tokens.",
}),
},
};
export const WithGetTokensError = Template.bind({});
WithGetTokensError.args = {
...Example.args,
hasLoaded: false,
getTokensError: mockApiError({
message: "Failed to get tokens.",
}),
};
export const WithDeleteTokenError = Template.bind({});
WithDeleteTokenError.args = {
...Example.args,
hasLoaded: false,
deleteTokenError: mockApiError({
message: "Failed to delete token.",
}),
export const WithDeleteTokenError: Story = {
args: {
hasLoaded: false,
deleteTokenError: mockApiError({
message: "Failed to delete token.",
}),
},
};
@@ -1,4 +1,3 @@
import { Story } from "@storybook/react";
import {
mockApiError,
MockWorkspaceProxies,
@@ -6,68 +5,68 @@ import {
MockHealthyWildWorkspaceProxy,
MockProxyLatencies,
} from "testHelpers/entities";
import {
WorkspaceProxyView,
WorkspaceProxyViewProps,
} from "./WorkspaceProxyView";
import { WorkspaceProxyView } from "./WorkspaceProxyView";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof WorkspaceProxyView> = {
title: "components/WorkspaceProxyView",
component: WorkspaceProxyView,
};
export default meta;
type Story = StoryObj<typeof WorkspaceProxyView>;
export const PrimarySelected: Story = {
args: {
onRegenerateClick: { action: "Submit" },
isLoading: false,
hasLoaded: true,
proxies: MockWorkspaceProxies,
proxyLatencies: MockProxyLatencies,
preferredProxy: MockPrimaryWorkspaceProxy,
},
};
const Template: Story<WorkspaceProxyViewProps> = (
args: WorkspaceProxyViewProps,
) => <WorkspaceProxyView {...args} />;
export const PrimarySelected = Template.bind({});
PrimarySelected.args = {
isLoading: false,
hasLoaded: true,
proxies: MockWorkspaceProxies,
proxyLatencies: MockProxyLatencies,
preferredProxy: MockPrimaryWorkspaceProxy,
export const Example: Story = {
args: {
isLoading: false,
hasLoaded: true,
proxies: MockWorkspaceProxies,
proxyLatencies: MockProxyLatencies,
preferredProxy: MockHealthyWildWorkspaceProxy,
},
};
export const Example = Template.bind({});
Example.args = {
isLoading: false,
hasLoaded: true,
proxies: MockWorkspaceProxies,
proxyLatencies: MockProxyLatencies,
preferredProxy: MockHealthyWildWorkspaceProxy,
export const Loading: Story = {
args: {
...Example.args,
isLoading: true,
hasLoaded: false,
},
};
export const Loading = Template.bind({});
Loading.args = {
...Example.args,
isLoading: true,
hasLoaded: false,
export const Empty: Story = {
args: {
...Example.args,
proxies: [],
},
};
export const Empty = Template.bind({});
Empty.args = {
...Example.args,
proxies: [],
export const WithProxiesError: Story = {
args: {
...Example.args,
hasLoaded: false,
getWorkspaceProxiesError: mockApiError({
message: "Failed to get proxies.",
}),
},
};
export const WithProxiesError = Template.bind({});
WithProxiesError.args = {
...Example.args,
hasLoaded: false,
getWorkspaceProxiesError: mockApiError({
message: "Failed to get proxies.",
}),
};
export const WithSelectProxyError = Template.bind({});
WithSelectProxyError.args = {
...Example.args,
hasLoaded: false,
selectProxyError: mockApiError({
message: "Failed to select proxy.",
}),
export const WithSelectProxyError: Story = {
args: {
...Example.args,
hasLoaded: false,
selectProxyError: mockApiError({
message: "Failed to select proxy.",
}),
},
};
@@ -1,28 +1,20 @@
import { action } from "@storybook/addon-actions";
import { Story } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import { MockUser } from "testHelpers/entities";
import {
ResetPasswordDialog,
ResetPasswordDialogProps,
} from "./ResetPasswordDialog";
import { ResetPasswordDialog } from "./ResetPasswordDialog";
export default {
const meta: Meta<typeof ResetPasswordDialog> = {
title: "components/Dialogs/ResetPasswordDialog",
component: ResetPasswordDialog,
argTypes: {
onClose: { action: "onClose", defaultValue: action("onClose") },
onConfirm: { action: "onConfirm", defaultValue: action("onConfirm") },
};
export default meta;
type Story = StoryObj<typeof ResetPasswordDialog>;
export const Example: Story = {
args: {
open: true,
user: MockUser,
newPassword: "somerandomstringhere",
},
};
const Template: Story<ResetPasswordDialogProps> = (
args: ResetPasswordDialogProps,
) => <ResetPasswordDialog {...args} />;
export const Example = Template.bind({});
Example.args = {
open: true,
user: MockUser,
newPassword: "somerandomstringhere",
};
@@ -179,12 +179,6 @@ const updateUserRole = async (setupActionSpies: () => void, role: Role) => {
};
describe("UsersPage", () => {
it("shows users", async () => {
renderPage();
const users = await screen.findAllByText(/.*@coder.com/);
expect(users.length).toEqual(3);
});
describe("suspend user", () => {
describe("when it is success", () => {
it("shows a success message and refresh the page", async () => {
@@ -1,42 +1,41 @@
import { ComponentMeta, Story } from "@storybook/react";
import {
MockOwnerRole,
MockSiteRoles,
MockUserAdminRole,
} from "testHelpers/entities";
import { EditRolesButtonProps, EditRolesButton } from "./EditRolesButton";
import { EditRolesButton } from "./EditRolesButton";
import type { Meta, StoryObj } from "@storybook/react";
export default {
const meta: Meta<typeof EditRolesButton> = {
title: "components/EditRolesButton",
component: EditRolesButton,
argTypes: {
defaultIsOpen: {
defaultValue: true,
},
args: {
defaultIsOpen: true,
},
} as ComponentMeta<typeof EditRolesButton>;
const Template: Story<EditRolesButtonProps> = (args) => (
<EditRolesButton {...args} />
);
export const Open = Template.bind({});
Open.args = {
roles: MockSiteRoles,
selectedRoles: [MockUserAdminRole, MockOwnerRole],
};
Open.parameters = {
chromatic: { delay: 300 },
};
export const Loading = Template.bind({});
Loading.args = {
isLoading: true,
roles: MockSiteRoles,
selectedRoles: [MockUserAdminRole, MockOwnerRole],
userLoginType: "password",
oidcRoleSync: false,
export default meta;
type Story = StoryObj<typeof EditRolesButton>;
export const Open: Story = {
args: {
roles: MockSiteRoles,
selectedRoles: [MockUserAdminRole, MockOwnerRole],
},
parameters: {
chromatic: { delay: 300 },
},
};
Loading.parameters = {
chromatic: { delay: 300 },
export const Loading: Story = {
args: {
isLoading: true,
roles: MockSiteRoles,
selectedRoles: [MockUserAdminRole, MockOwnerRole],
userLoginType: "password",
oidcRoleSync: false,
},
parameters: {
chromatic: { delay: 300 },
},
};
@@ -1,20 +1,23 @@
import { ComponentMeta, Story } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import { MockBuilds } from "testHelpers/entities";
import { BuildsTable, BuildsTableProps } from "./BuildsTable";
import { BuildsTable } from "./BuildsTable";
export default {
const meta: Meta<typeof BuildsTable> = {
title: "components/BuildsTable",
component: BuildsTable,
} as ComponentMeta<typeof BuildsTable>;
const Template: Story<BuildsTableProps> = (args) => <BuildsTable {...args} />;
export const Example = Template.bind({});
Example.args = {
builds: MockBuilds,
};
export const Empty = Template.bind({});
Empty.args = {
builds: [],
export default meta;
type Story = StoryObj<typeof BuildsTable>;
export const Example: Story = {
args: {
builds: MockBuilds,
},
};
export const Empty: Story = {
args: {
builds: [],
},
};
@@ -61,6 +61,7 @@ const meta: Meta<typeof Workspace> = {
}),
],
};
export default meta;
type Story = StoryObj<typeof Workspace>;
@@ -1,91 +1,81 @@
import { action } from "@storybook/addon-actions";
import { Story } from "@storybook/react";
import * as Mocks from "../../../testHelpers/entities";
import { WorkspaceActions, WorkspaceActionsProps } from "./WorkspaceActions";
import { Meta, StoryObj } from "@storybook/react";
import * as Mocks from "testHelpers/entities";
import { WorkspaceActions } from "./WorkspaceActions";
export default {
const meta: Meta<typeof WorkspaceActions> = {
title: "components/WorkspaceActions",
component: WorkspaceActions,
args: {
isUpdating: false,
},
};
const Template: Story<WorkspaceActionsProps> = (args) => (
<WorkspaceActions {...args} />
);
export default meta;
type Story = StoryObj<typeof WorkspaceActions>;
const defaultArgs = {
handleStart: action("start"),
handleStop: action("stop"),
handleRestart: action("restart"),
handleDelete: action("delete"),
handleUpdate: action("update"),
handleCancel: action("cancel"),
isOutdated: false,
isUpdating: false,
export const Starting: Story = {
args: {
workspace: Mocks.MockStartingWorkspace,
},
};
export const Starting = Template.bind({});
Starting.args = {
...defaultArgs,
workspace: Mocks.MockStartingWorkspace,
export const Running: Story = {
args: {
workspace: Mocks.MockWorkspace,
},
};
export const Running = Template.bind({});
Running.args = {
...defaultArgs,
workspace: Mocks.MockWorkspace,
export const Stopping: Story = {
args: {
workspace: Mocks.MockStoppingWorkspace,
},
};
export const Stopping = Template.bind({});
Stopping.args = {
...defaultArgs,
workspace: Mocks.MockStoppingWorkspace,
export const Stopped: Story = {
args: {
workspace: Mocks.MockStoppedWorkspace,
},
};
export const Stopped = Template.bind({});
Stopped.args = {
...defaultArgs,
workspace: Mocks.MockStoppedWorkspace,
export const Canceling: Story = {
args: {
workspace: Mocks.MockCancelingWorkspace,
},
};
export const Canceling = Template.bind({});
Canceling.args = {
...defaultArgs,
workspace: Mocks.MockCancelingWorkspace,
export const Canceled: Story = {
args: {
workspace: Mocks.MockCanceledWorkspace,
},
};
export const Canceled = Template.bind({});
Canceled.args = {
...defaultArgs,
workspace: Mocks.MockCanceledWorkspace,
export const Deleting: Story = {
args: {
workspace: Mocks.MockDeletingWorkspace,
},
};
export const Deleting = Template.bind({});
Deleting.args = {
...defaultArgs,
workspace: Mocks.MockDeletingWorkspace,
export const Deleted: Story = {
args: {
workspace: Mocks.MockDeletedWorkspace,
},
};
export const Deleted = Template.bind({});
Deleted.args = {
...defaultArgs,
workspace: Mocks.MockDeletedWorkspace,
export const Outdated: Story = {
args: {
workspace: Mocks.MockOutdatedWorkspace,
},
};
export const Outdated = Template.bind({});
Outdated.args = {
...defaultArgs,
workspace: Mocks.MockOutdatedWorkspace,
export const Failed: Story = {
args: {
workspace: Mocks.MockFailedWorkspace,
},
};
export const Failed = Template.bind({});
Failed.args = {
...defaultArgs,
workspace: Mocks.MockFailedWorkspace,
};
export const Updating = Template.bind({});
Updating.args = {
...defaultArgs,
isUpdating: true,
workspace: Mocks.MockOutdatedWorkspace,
export const Updating: Story = {
args: {
isUpdating: true,
workspace: Mocks.MockOutdatedWorkspace,
},
};
@@ -1,75 +1,71 @@
import { ComponentMeta, Story } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import dayjs from "dayjs";
import {
MockStartingWorkspace,
MockWorkspaceBuild,
MockProvisionerJob,
} from "testHelpers/entities";
import {
WorkspaceBuildProgress,
WorkspaceBuildProgressProps,
} from "./WorkspaceBuildProgress";
import { WorkspaceBuildProgress } from "./WorkspaceBuildProgress";
export default {
const meta: Meta<typeof WorkspaceBuildProgress> = {
title: "components/WorkspaceBuildProgress",
component: WorkspaceBuildProgress,
} as ComponentMeta<typeof WorkspaceBuildProgress>;
const Template: Story<WorkspaceBuildProgressProps> = (args) => (
<WorkspaceBuildProgress {...args} />
);
export const Starting = Template.bind({});
Starting.args = {
transitionStats: {
P50: 10000,
P95: 10010,
},
workspace: {
...MockStartingWorkspace,
latest_build: {
...MockWorkspaceBuild,
status: "starting",
job: {
...MockProvisionerJob,
started_at: dayjs().add(-5, "second").format(),
status: "running",
args: {
transitionStats: {
P50: 10000,
P95: 10010,
},
workspace: {
...MockStartingWorkspace,
latest_build: {
...MockWorkspaceBuild,
status: "starting",
job: {
...MockProvisionerJob,
started_at: dayjs().add(-5, "second").format(),
status: "running",
},
},
},
},
};
export default meta;
type Story = StoryObj<typeof WorkspaceBuildProgress>;
export const Starting: Story = {};
// When the transition stats are returning null, the progress bar should not be
// displayed
export const StartingUnknown = Template.bind({});
StartingUnknown.args = {
...Starting.args,
transitionStats: {
// HACK: the codersdk type generator doesn't support null values, but this
// can be null when the template is new.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Read comment above
// @ts-ignore-error
P50: null,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Read comment above
// @ts-ignore-error
P95: null,
export const StartingUnknown: Story = {
args: {
transitionStats: {
// HACK: the codersdk type generator doesn't support null values, but this
// can be null when the template is new.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Read comment above
// @ts-ignore-error
P50: null,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Read comment above
// @ts-ignore-error
P95: null,
},
},
};
export const StartingPassedEstimate = Template.bind({});
StartingPassedEstimate.args = {
...Starting.args,
transitionStats: { P50: 1000, P95: 1000 },
export const StartingPassedEstimate: Story = {
args: {
transitionStats: { P50: 1000, P95: 1000 },
},
};
export const StartingHighVariaton = Template.bind({});
StartingHighVariaton.args = {
...Starting.args,
transitionStats: { P50: 10000, P95: 20000 },
export const StartingHighVariaton: Story = {
args: {
transitionStats: { P50: 10000, P95: 20000 },
},
};
export const StartingZeroEstimate = Template.bind({});
StartingZeroEstimate.args = {
...Starting.args,
transitionStats: { P50: 0, P95: 0 },
export const StartingZeroEstimate: Story = {
args: {
transitionStats: { P50: 0, P95: 0 },
},
};
@@ -1,20 +1,12 @@
import { action } from "@storybook/addon-actions";
import { Story } from "@storybook/react";
import {
WorkspaceDeletedBanner,
WorkspaceDeletedBannerProps,
} from "./WorkspaceDeletedBanner";
import { Meta, StoryObj } from "@storybook/react";
import { WorkspaceDeletedBanner } from "./WorkspaceDeletedBanner";
export default {
const meta: Meta<typeof WorkspaceDeletedBanner> = {
title: "components/WorkspaceDeletedBanner",
component: WorkspaceDeletedBanner,
};
const Template: Story<WorkspaceDeletedBannerProps> = (args) => (
<WorkspaceDeletedBanner {...args} />
);
export default meta;
type Story = StoryObj<typeof WorkspaceDeletedBanner>;
export const Example = Template.bind({});
Example.args = {
handleClick: action("extend"),
};
export const Example: Story = {};
@@ -1,4 +1,4 @@
import { Story } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import {
MockWorkspace,
MockAppearance,
@@ -6,14 +6,9 @@ import {
MockEntitlementsWithScheduling,
MockExperiments,
} from "testHelpers/entities";
import { WorkspaceStats, WorkspaceStatsProps } from "./WorkspaceStats";
import { WorkspaceStats } from "./WorkspaceStats";
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
export default {
title: "components/WorkspaceStats",
component: WorkspaceStats,
};
const MockedAppearance = {
config: MockAppearance,
preview: false,
@@ -21,28 +16,39 @@ const MockedAppearance = {
save: () => null,
};
const Template: Story<WorkspaceStatsProps> = (args) => (
<DashboardProviderContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
experiments: MockExperiments,
appearance: MockedAppearance,
}}
>
<WorkspaceStats {...args} />
</DashboardProviderContext.Provider>
);
export const Example = Template.bind({});
Example.args = {
workspace: MockWorkspace,
const meta: Meta<typeof WorkspaceStats> = {
title: "components/WorkspaceStats",
component: WorkspaceStats,
decorators: [
(Story) => (
<DashboardProviderContext.Provider
value={{
buildInfo: MockBuildInfo,
entitlements: MockEntitlementsWithScheduling,
experiments: MockExperiments,
appearance: MockedAppearance,
}}
>
<Story />
</DashboardProviderContext.Provider>
),
],
};
export const Outdated = Template.bind({});
Outdated.args = {
workspace: {
...MockWorkspace,
outdated: true,
export default meta;
type Story = StoryObj<typeof WorkspaceStats>;
export const Example: Story = {
args: {
workspace: MockWorkspace,
},
};
export const Outdated: Story = {
args: {
workspace: {
...MockWorkspace,
outdated: true,
},
},
};
@@ -1,9 +1,5 @@
import { ComponentMeta, Story } from "@storybook/react";
import {
WorkspaceParametersPageView,
WorkspaceParametersPageViewProps,
} from "./WorkspaceParametersPage";
import { action } from "@storybook/addon-actions";
import { Meta, StoryObj } from "@storybook/react";
import { WorkspaceParametersPageView } from "./WorkspaceParametersPage";
import {
MockWorkspaceBuildParameter1,
MockWorkspaceBuildParameter2,
@@ -13,13 +9,13 @@ import {
MockWorkspaceBuildParameter3,
} from "testHelpers/entities";
export default {
const meta: Meta<typeof WorkspaceParametersPageView> = {
title: "pages/WorkspaceParametersPageView",
component: WorkspaceParametersPageView,
args: {
submitError: undefined,
isSubmitting: false,
onCancel: action("cancel"),
data: {
buildParameters: [
MockWorkspaceBuildParameter1,
@@ -36,11 +32,9 @@ export default {
],
},
},
} as ComponentMeta<typeof WorkspaceParametersPageView>;
};
const Template: Story<WorkspaceParametersPageViewProps> = (args) => (
<WorkspaceParametersPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof WorkspaceParametersPageView>;
export const Example = Template.bind({});
Example.args = {};
export const Example: Story = {};
@@ -1,4 +1,4 @@
import { Story } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import dayjs from "dayjs";
import advancedFormat from "dayjs/plugin/advancedFormat";
import timezone from "dayjs/plugin/timezone";
@@ -9,31 +9,19 @@ import {
} from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/schedule";
import { emptyTTL } from "pages/WorkspaceSettingsPage/WorkspaceSchedulePage/ttl";
import { mockApiError } from "testHelpers/entities";
import {
WorkspaceScheduleForm,
WorkspaceScheduleFormProps,
} from "./WorkspaceScheduleForm";
import { WorkspaceScheduleForm } from "./WorkspaceScheduleForm";
dayjs.extend(advancedFormat);
dayjs.extend(utc);
dayjs.extend(timezone);
export default {
const meta: Meta<typeof WorkspaceScheduleForm> = {
title: "components/WorkspaceScheduleForm",
component: WorkspaceScheduleForm,
argTypes: {
onCancel: {
action: "onCancel",
},
onSubmit: {
action: "onSubmit",
},
},
};
const Template: Story<WorkspaceScheduleFormProps> = (args) => (
<WorkspaceScheduleForm {...args} />
);
export default meta;
type Story = StoryObj<typeof WorkspaceScheduleForm>;
const defaultInitialValues = {
autostartEnabled: true,
@@ -42,53 +30,62 @@ const defaultInitialValues = {
ttl: 24,
};
export const AllDisabled = Template.bind({});
AllDisabled.args = {
initialValues: {
autostartEnabled: false,
...emptySchedule,
autostopEnabled: false,
ttl: emptyTTL,
export const AllDisabled: Story = {
args: {
initialValues: {
autostartEnabled: false,
...emptySchedule,
autostopEnabled: false,
ttl: emptyTTL,
},
},
};
export const Autostart = Template.bind({});
Autostart.args = {
initialValues: {
autostartEnabled: true,
...defaultSchedule(),
autostopEnabled: false,
ttl: emptyTTL,
export const Autostart: Story = {
args: {
initialValues: {
autostartEnabled: true,
...defaultSchedule(),
autostopEnabled: false,
ttl: emptyTTL,
},
},
};
export const WorkspaceWillShutdownInTwoHours = Template.bind({});
WorkspaceWillShutdownInTwoHours.args = {
initialValues: { ...defaultInitialValues, ttl: 2 },
export const WorkspaceWillShutdownInTwoHours: Story = {
args: {
initialValues: { ...defaultInitialValues, ttl: 2 },
},
};
export const WorkspaceWillShutdownInADay = Template.bind({});
WorkspaceWillShutdownInADay.args = {
initialValues: { ...defaultInitialValues, ttl: 24 },
export const WorkspaceWillShutdownInADay: Story = {
args: {
initialValues: { ...defaultInitialValues, ttl: 24 },
},
};
export const WorkspaceWillShutdownInTwoDays = Template.bind({});
WorkspaceWillShutdownInTwoDays.args = {
initialValues: { ...defaultInitialValues, ttl: 48 },
export const WorkspaceWillShutdownInTwoDays: Story = {
args: {
initialValues: { ...defaultInitialValues, ttl: 48 },
},
};
export const WithError = Template.bind({});
WithError.args = {
initialValues: { ...defaultInitialValues, ttl: 100 },
initialTouched: { ttl: true },
submitScheduleError: mockApiError({
message: "Something went wrong.",
validations: [{ field: "ttl_ms", detail: "Invalid time until shutdown." }],
}),
export const WithError: Story = {
args: {
initialValues: { ...defaultInitialValues, ttl: 100 },
initialTouched: { ttl: true },
submitScheduleError: mockApiError({
message: "Something went wrong.",
validations: [
{ field: "ttl_ms", detail: "Invalid time until shutdown." },
],
}),
},
};
export const Loading = Template.bind({});
Loading.args = {
initialValues: defaultInitialValues,
isLoading: true,
export const Loading: Story = {
args: {
initialValues: defaultInitialValues,
isLoading: true,
},
};
@@ -1,25 +1,18 @@
import { ComponentMeta, Story } from "@storybook/react";
import { Meta, StoryObj } from "@storybook/react";
import { MockWorkspace } from "testHelpers/entities";
import {
WorkspaceSettingsPageView,
WorkspaceSettingsPageViewProps,
} from "./WorkspaceSettingsPageView";
import { action } from "@storybook/addon-actions";
import { WorkspaceSettingsPageView } from "./WorkspaceSettingsPageView";
export default {
const meta: Meta<typeof WorkspaceSettingsPageView> = {
title: "pages/WorkspaceSettingsPageView",
component: WorkspaceSettingsPageView,
args: {
error: undefined,
isSubmitting: false,
workspace: MockWorkspace,
onCancel: action("cancel"),
},
} as ComponentMeta<typeof WorkspaceSettingsPageView>;
};
const Template: Story<WorkspaceSettingsPageViewProps> = (args) => (
<WorkspaceSettingsPageView {...args} />
);
export default meta;
type Story = StoryObj<typeof WorkspaceSettingsPageView>;
export const Example = Template.bind({});
Example.args = {};
export const Example: Story = {};
-8
View File
@@ -1,8 +0,0 @@
import { FC } from "react";
import { Navigate } from "react-router-dom";
const IndexPage: FC = () => {
return <Navigate to="/workspaces" replace />;
};
export default IndexPage;
@@ -23,7 +23,7 @@ import { displayError } from "components/GlobalSnackbar/utils";
import {
TemplateAutostopRequirementDaysValue,
calculateAutostopRequirementDaysValue,
} from "pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm/AutostopRequirementHelperText";
} from "pages/TemplateSettingsPage/TemplateSchedulePage/AutostopRequirementHelperText";
import { delay } from "utils/delay";
import { assign, createMachine } from "xstate";