Skip to content

Commit 45be26c

Browse files
committed
Add tests
1 parent 6d3148b commit 45be26c

File tree

7 files changed

+323
-179
lines changed

7 files changed

+323
-179
lines changed

site/.storybook/preview.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ function withQuery(Story, { parameters }) {
7878
defaultOptions: {
7979
queries: {
8080
staleTime: Infinity,
81+
retry: false,
8182
},
8283
},
8384
});

site/src/pages/WorkspacePage/Workspace.stories.tsx

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import EventSource from "eventsourcemock";
99
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
1010
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
1111
import { WorkspaceBuildLogsSection } from "pages/WorkspacePage/WorkspaceBuildLogsSection";
12-
import { getWorkspaceResolveAutostartQueryKey } from "api/queries/workspaceQuota";
1312
import { WorkspacePermissions } from "./permissions";
1413

1514
const MockedAppearance = {
@@ -185,58 +184,6 @@ export const Canceled: Story = {
185184
},
186185
};
187186

188-
export const Outdated: Story = {
189-
args: {
190-
...Running.args,
191-
workspace: Mocks.MockOutdatedWorkspace,
192-
},
193-
};
194-
195-
export const CantAutostart: Story = {
196-
args: {
197-
...Running.args,
198-
workspace: Mocks.MockOutdatedRunningWorkspaceRequireActiveVersion,
199-
},
200-
parameters: {
201-
queries: [
202-
{
203-
key: getWorkspaceResolveAutostartQueryKey(
204-
Mocks.MockOutdatedRunningWorkspaceRequireActiveVersion.id,
205-
),
206-
data: {
207-
parameter_mismatch: false,
208-
},
209-
},
210-
],
211-
},
212-
};
213-
214-
export const Deprecated: Story = {
215-
args: {
216-
...Running.args,
217-
template: {
218-
...Mocks.MockTemplate,
219-
deprecated: true,
220-
deprecation_message:
221-
"Template deprecated due to reasons. [Learn more](#)",
222-
},
223-
},
224-
};
225-
226-
export const Unhealthy: Story = {
227-
args: {
228-
...Running.args,
229-
workspace: {
230-
...Mocks.MockWorkspace,
231-
latest_build: { ...Mocks.MockWorkspace.latest_build, status: "running" },
232-
health: {
233-
healthy: false,
234-
failing_agents: [],
235-
},
236-
},
237-
},
238-
};
239-
240187
function makeFailedBuildLogs(): ProvisionerJobLog[] {
241188
return [
242189
{

site/src/pages/WorkspacePage/Workspace.tsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import { SidebarIconButton } from "components/FullPageLayout/Sidebar";
2020
import HubOutlined from "@mui/icons-material/HubOutlined";
2121
import { ResourcesSidebar } from "./ResourcesSidebar";
2222
import { ResourceCard } from "components/Resources/ResourceCard";
23-
import { WorkspaceNotifications } from "./WorkspaceNotifications";
2423
import { WorkspacePermissions } from "./permissions";
2524
import { useResourcesNav } from "./useResourcesNav";
2625

@@ -133,6 +132,9 @@ export const Workspace: FC<WorkspaceProps> = ({
133132
isRestarting={isRestarting}
134133
canUpdateWorkspace={permissions.updateWorkspace}
135134
isOwner={isOwner}
135+
template={template}
136+
permissions={permissions}
137+
latestVersion={latestVersion}
136138
/>
137139

138140
<div
@@ -178,16 +180,6 @@ export const Workspace: FC<WorkspaceProps> = ({
178180
<div css={styles.content}>
179181
<div css={styles.dotBackground}>
180182
<div css={{ display: "flex", flexDirection: "column", gap: 24 }}>
181-
<WorkspaceNotifications
182-
workspace={workspace}
183-
template={template}
184-
latestVersion={latestVersion}
185-
permissions={permissions}
186-
onRestartWorkspace={handleRestart}
187-
onUpdateWorkspace={handleUpdate}
188-
onActivateWorkspace={handleDormantActivate}
189-
/>
190-
191183
{workspace.latest_build.status === "deleted" && (
192184
<WorkspaceDeletedBanner
193185
handleClick={() => navigate(`/templates`)}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { FC, ReactNode } from "react";
2+
import { Pill } from "components/Pill/Pill";
3+
import {
4+
Popover,
5+
PopoverContent,
6+
PopoverTrigger,
7+
usePopover,
8+
} from "components/Popover/Popover";
9+
import { Interpolation, Theme, useTheme } from "@emotion/react";
10+
import Button, { ButtonProps } from "@mui/material/Button";
11+
import { ThemeRole } from "theme/experimental";
12+
import { AlertProps } from "components/Alert/Alert";
13+
14+
export type NotificationItem = {
15+
title: string;
16+
severity: AlertProps["severity"];
17+
detail?: ReactNode;
18+
actions?: ReactNode;
19+
};
20+
21+
type NotificationsProps = {
22+
items: NotificationItem[];
23+
severity: ThemeRole;
24+
icon: ReactNode;
25+
isDefaultOpen?: boolean;
26+
};
27+
28+
export const Notifications: FC<NotificationsProps> = ({
29+
items,
30+
severity,
31+
icon,
32+
isDefaultOpen,
33+
}) => {
34+
const theme = useTheme();
35+
36+
return (
37+
<Popover mode="hover" isDefaultOpen={isDefaultOpen}>
38+
<PopoverTrigger>
39+
<div css={styles.pillContainer}>
40+
<NotificationPill items={items} severity={severity} icon={icon} />
41+
</div>
42+
</PopoverTrigger>
43+
<PopoverContent
44+
horizontal="right"
45+
css={{
46+
"& .MuiPaper-root": {
47+
borderColor: theme.experimental.roles[severity].outline,
48+
maxWidth: 400,
49+
},
50+
}}
51+
>
52+
{items.map((n) => (
53+
<NotificationItem notification={n} key={n.title} />
54+
))}
55+
</PopoverContent>
56+
</Popover>
57+
);
58+
};
59+
60+
const NotificationPill = (props: NotificationsProps) => {
61+
const { items, severity, icon } = props;
62+
const popover = usePopover();
63+
64+
return (
65+
<Pill
66+
icon={icon}
67+
css={(theme) => ({
68+
"& svg": { color: theme.experimental.roles[severity].outline },
69+
borderColor: popover.isOpen
70+
? theme.experimental.roles[severity].outline
71+
: undefined,
72+
})}
73+
>
74+
{items.length}
75+
</Pill>
76+
);
77+
};
78+
79+
const NotificationItem: FC<{ notification: NotificationItem }> = (props) => {
80+
const { notification } = props;
81+
82+
return (
83+
<article css={styles.notificationItem}>
84+
<h4 css={{ margin: 0, fontWeight: 500 }}>{notification.title}</h4>
85+
{notification.detail && (
86+
<p css={styles.notificationDetail}>{notification.detail}</p>
87+
)}
88+
<div css={{ marginTop: 8 }}>{notification.actions}</div>
89+
</article>
90+
);
91+
};
92+
93+
export const NotificationActionButton: FC<ButtonProps> = (props) => {
94+
return (
95+
<Button
96+
variant="text"
97+
css={{
98+
textDecoration: "underline",
99+
padding: 0,
100+
height: "auto",
101+
minWidth: "auto",
102+
"&:hover": { background: "none", textDecoration: "underline" },
103+
}}
104+
{...props}
105+
/>
106+
);
107+
};
108+
109+
const styles = {
110+
// Adds some spacing from the popover content
111+
pillContainer: {
112+
padding: "8px 0",
113+
},
114+
notificationItem: (theme) => ({
115+
padding: 20,
116+
lineHeight: "1.5",
117+
borderTop: `1px solid ${theme.palette.divider}`,
118+
119+
"&:first-child": {
120+
borderTop: 0,
121+
},
122+
}),
123+
notificationDetail: (theme) => ({
124+
margin: 0,
125+
color: theme.palette.text.secondary,
126+
lineHeight: 1.6,
127+
display: "block",
128+
marginTop: 8,
129+
}),
130+
} satisfies Record<string, Interpolation<Theme>>;
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import {
2+
MockOutdatedWorkspace,
3+
MockTemplate,
4+
MockTemplateVersion,
5+
MockWorkspace,
6+
} from "testHelpers/entities";
7+
import { WorkspaceNotifications } from "./WorkspaceNotifications";
8+
import type { Meta, StoryObj } from "@storybook/react";
9+
import { withDashboardProvider } from "testHelpers/storybook";
10+
import { getWorkspaceResolveAutostartQueryKey } from "api/queries/workspaceQuota";
11+
12+
const defaultPermissions = {
13+
readWorkspace: true,
14+
updateTemplate: true,
15+
updateWorkspace: true,
16+
viewDeploymentValues: true,
17+
};
18+
19+
const meta: Meta<typeof WorkspaceNotifications> = {
20+
title: "components/WorkspaceNotifications",
21+
component: WorkspaceNotifications,
22+
args: {
23+
latestVersion: MockTemplateVersion,
24+
template: MockTemplate,
25+
workspace: MockWorkspace,
26+
permissions: defaultPermissions,
27+
},
28+
decorators: [withDashboardProvider],
29+
parameters: {
30+
queries: [
31+
{
32+
key: getWorkspaceResolveAutostartQueryKey(MockOutdatedWorkspace.id),
33+
data: {
34+
parameter_mismatch: false,
35+
},
36+
},
37+
],
38+
features: ["advanced_template_scheduling"],
39+
},
40+
};
41+
42+
export default meta;
43+
type Story = StoryObj<typeof WorkspaceNotifications>;
44+
45+
export const Outdated: Story = {
46+
args: {
47+
workspace: MockOutdatedWorkspace,
48+
defaultOpen: "info",
49+
},
50+
};
51+
52+
export const RequiresManualUpdate: Story = {
53+
args: {
54+
workspace: {
55+
...MockOutdatedWorkspace,
56+
automatic_updates: "always",
57+
autostart_schedule: "daily",
58+
},
59+
defaultOpen: "warning",
60+
},
61+
parameters: {
62+
queries: [
63+
{
64+
key: getWorkspaceResolveAutostartQueryKey(MockOutdatedWorkspace.id),
65+
data: {
66+
parameter_mismatch: true,
67+
},
68+
},
69+
],
70+
},
71+
};
72+
73+
export const Unhealthy: Story = {
74+
args: {
75+
workspace: {
76+
...MockWorkspace,
77+
health: {
78+
...MockWorkspace.health,
79+
healthy: false,
80+
},
81+
latest_build: {
82+
...MockWorkspace.latest_build,
83+
status: "running",
84+
},
85+
},
86+
defaultOpen: "warning",
87+
},
88+
};
89+
90+
export const UnhealthyWithoutUpdatePermission: Story = {
91+
args: {
92+
...Unhealthy.args,
93+
permissions: {
94+
...defaultPermissions,
95+
updateWorkspace: false,
96+
},
97+
},
98+
};
99+
100+
const DormantWorkspace = {
101+
...MockWorkspace,
102+
dormant_at: new Date("2020-01-01T00:00:00Z").toISOString(),
103+
};
104+
105+
export const Dormant: Story = {
106+
args: {
107+
defaultOpen: "warning",
108+
workspace: DormantWorkspace,
109+
},
110+
};
111+
112+
export const DormantWithDeletingDate: Story = {
113+
args: {
114+
...Dormant.args,
115+
workspace: {
116+
...DormantWorkspace,
117+
deleting_at: new Date("2020-10-01T00:00:00Z").toISOString(),
118+
},
119+
},
120+
};
121+
122+
export const PendingInQueue: Story = {
123+
args: {
124+
workspace: {
125+
...MockWorkspace,
126+
latest_build: {
127+
...MockWorkspace.latest_build,
128+
status: "pending",
129+
job: {
130+
...MockWorkspace.latest_build.job,
131+
queue_size: 10,
132+
queue_position: 3,
133+
},
134+
},
135+
},
136+
defaultOpen: "info",
137+
},
138+
};
139+
140+
export const TemplateDeprecated: Story = {
141+
args: {
142+
template: {
143+
...MockTemplate,
144+
deprecated: true,
145+
deprecation_message:
146+
"Template deprecated due to reasons. [Learn more](#)",
147+
},
148+
defaultOpen: "warning",
149+
},
150+
};

0 commit comments

Comments
 (0)