Skip to content

refactor(site): refactor workspace notifications #11520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0b28628
Refactor outdated warning
BrunoQuaresma Jan 9, 2024
ab96aab
Move unhealthy warning
BrunoQuaresma Jan 9, 2024
38f213e
Move dormant notification
BrunoQuaresma Jan 9, 2024
9ec5952
Remove mutation errors from alerts
BrunoQuaresma Jan 9, 2024
17ecf0e
Move pending in queue notification
BrunoQuaresma Jan 9, 2024
c969df5
Move deprecated notification
BrunoQuaresma Jan 9, 2024
8f6040d
Move outdated to notifications array
BrunoQuaresma Jan 9, 2024
691c028
Move unhealthy to notifications array
BrunoQuaresma Jan 9, 2024
706bbd5
Fix permissions for restart workspace
BrunoQuaresma Jan 9, 2024
d3e68fb
Move dormant notifications to notifications array
BrunoQuaresma Jan 9, 2024
2f1ac83
Structure notification pills
BrunoQuaresma Jan 9, 2024
c334031
Minor style adjustments
BrunoQuaresma Jan 9, 2024
a78d4cf
Remove bottom bar from workspace page
BrunoQuaresma Jan 9, 2024
24c7552
Display notifications under the pills
BrunoQuaresma Jan 9, 2024
b711018
Improve canAutoStart check
BrunoQuaresma Jan 11, 2024
121005c
Move css into a style
BrunoQuaresma Jan 11, 2024
3234d49
Move css into a style
BrunoQuaresma Jan 11, 2024
b30dc44
Remove unecessary boolean
BrunoQuaresma Jan 11, 2024
04994a4
Refactor props
BrunoQuaresma Jan 11, 2024
5ba0765
Improve semantics
BrunoQuaresma Jan 11, 2024
3d0d70b
Merge branch 'bq/move-alert' of https://github.com/coder/coder into b…
BrunoQuaresma Jan 11, 2024
9299c68
Merge branch 'main' of https://github.com/coder/coder into bq/move-alert
BrunoQuaresma Jan 11, 2024
6d3148b
Merge branch 'main' of https://github.com/coder/coder into bq/move-alert
BrunoQuaresma Jan 12, 2024
45be26c
Add tests
BrunoQuaresma Jan 12, 2024
d21be38
Merge branch 'main' of https://github.com/coder/coder into bq/move-alert
BrunoQuaresma Jan 12, 2024
478a333
Fix storybook
BrunoQuaresma Jan 12, 2024
d62833c
Merge branch 'main' of https://github.com/coder/coder into bq/move-alert
BrunoQuaresma Jan 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add tests
  • Loading branch information
BrunoQuaresma committed Jan 12, 2024
commit 45be26cdc9fbfbe52e0f9fe456f7076dc6a6bbde
1 change: 1 addition & 0 deletions site/.storybook/preview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ function withQuery(Story, { parameters }) {
defaultOptions: {
queries: {
staleTime: Infinity,
retry: false,
},
},
});
Expand Down
53 changes: 0 additions & 53 deletions site/src/pages/WorkspacePage/Workspace.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import EventSource from "eventsourcemock";
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
import { DashboardProviderContext } from "components/Dashboard/DashboardProvider";
import { WorkspaceBuildLogsSection } from "pages/WorkspacePage/WorkspaceBuildLogsSection";
import { getWorkspaceResolveAutostartQueryKey } from "api/queries/workspaceQuota";
import { WorkspacePermissions } from "./permissions";

const MockedAppearance = {
Expand Down Expand Up @@ -185,58 +184,6 @@ export const Canceled: Story = {
},
};

export const Outdated: Story = {
args: {
...Running.args,
workspace: Mocks.MockOutdatedWorkspace,
},
};

export const CantAutostart: Story = {
args: {
...Running.args,
workspace: Mocks.MockOutdatedRunningWorkspaceRequireActiveVersion,
},
parameters: {
queries: [
{
key: getWorkspaceResolveAutostartQueryKey(
Mocks.MockOutdatedRunningWorkspaceRequireActiveVersion.id,
),
data: {
parameter_mismatch: false,
},
},
],
},
};

export const Deprecated: Story = {
args: {
...Running.args,
template: {
...Mocks.MockTemplate,
deprecated: true,
deprecation_message:
"Template deprecated due to reasons. [Learn more](#)",
},
},
};

export const Unhealthy: Story = {
args: {
...Running.args,
workspace: {
...Mocks.MockWorkspace,
latest_build: { ...Mocks.MockWorkspace.latest_build, status: "running" },
health: {
healthy: false,
failing_agents: [],
},
},
},
};

function makeFailedBuildLogs(): ProvisionerJobLog[] {
return [
{
Expand Down
14 changes: 3 additions & 11 deletions site/src/pages/WorkspacePage/Workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { SidebarIconButton } from "components/FullPageLayout/Sidebar";
import HubOutlined from "@mui/icons-material/HubOutlined";
import { ResourcesSidebar } from "./ResourcesSidebar";
import { ResourceCard } from "components/Resources/ResourceCard";
import { WorkspaceNotifications } from "./WorkspaceNotifications";
import { WorkspacePermissions } from "./permissions";
import { useResourcesNav } from "./useResourcesNav";

Expand Down Expand Up @@ -133,6 +132,9 @@ export const Workspace: FC<WorkspaceProps> = ({
isRestarting={isRestarting}
canUpdateWorkspace={permissions.updateWorkspace}
isOwner={isOwner}
template={template}
permissions={permissions}
latestVersion={latestVersion}
/>

<div
Expand Down Expand Up @@ -178,16 +180,6 @@ export const Workspace: FC<WorkspaceProps> = ({
<div css={styles.content}>
<div css={styles.dotBackground}>
<div css={{ display: "flex", flexDirection: "column", gap: 24 }}>
<WorkspaceNotifications
workspace={workspace}
template={template}
latestVersion={latestVersion}
permissions={permissions}
onRestartWorkspace={handleRestart}
onUpdateWorkspace={handleUpdate}
onActivateWorkspace={handleDormantActivate}
/>

{workspace.latest_build.status === "deleted" && (
<WorkspaceDeletedBanner
handleClick={() => navigate(`/templates`)}
Expand Down
130 changes: 130 additions & 0 deletions site/src/pages/WorkspacePage/WorkspaceNotifications/Notifications.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { FC, ReactNode } from "react";
import { Pill } from "components/Pill/Pill";
import {
Popover,
PopoverContent,
PopoverTrigger,
usePopover,
} from "components/Popover/Popover";
import { Interpolation, Theme, useTheme } from "@emotion/react";
import Button, { ButtonProps } from "@mui/material/Button";
import { ThemeRole } from "theme/experimental";
import { AlertProps } from "components/Alert/Alert";

export type NotificationItem = {
title: string;
severity: AlertProps["severity"];
detail?: ReactNode;
actions?: ReactNode;
};

type NotificationsProps = {
items: NotificationItem[];
severity: ThemeRole;
icon: ReactNode;
isDefaultOpen?: boolean;
};

export const Notifications: FC<NotificationsProps> = ({
items,
severity,
icon,
isDefaultOpen,
}) => {
const theme = useTheme();

return (
<Popover mode="hover" isDefaultOpen={isDefaultOpen}>
<PopoverTrigger>
<div css={styles.pillContainer}>
<NotificationPill items={items} severity={severity} icon={icon} />
</div>
</PopoverTrigger>
<PopoverContent
horizontal="right"
css={{
"& .MuiPaper-root": {
borderColor: theme.experimental.roles[severity].outline,
maxWidth: 400,
},
}}
>
{items.map((n) => (
<NotificationItem notification={n} key={n.title} />
))}
</PopoverContent>
</Popover>
);
};

const NotificationPill = (props: NotificationsProps) => {
const { items, severity, icon } = props;
const popover = usePopover();

return (
<Pill
icon={icon}
css={(theme) => ({
"& svg": { color: theme.experimental.roles[severity].outline },
borderColor: popover.isOpen
? theme.experimental.roles[severity].outline
: undefined,
})}
>
{items.length}
</Pill>
);
};

const NotificationItem: FC<{ notification: NotificationItem }> = (props) => {
const { notification } = props;

return (
<article css={styles.notificationItem}>
<h4 css={{ margin: 0, fontWeight: 500 }}>{notification.title}</h4>
{notification.detail && (
<p css={styles.notificationDetail}>{notification.detail}</p>
)}
<div css={{ marginTop: 8 }}>{notification.actions}</div>
</article>
);
};

export const NotificationActionButton: FC<ButtonProps> = (props) => {
return (
<Button
variant="text"
css={{
textDecoration: "underline",
padding: 0,
height: "auto",
minWidth: "auto",
"&:hover": { background: "none", textDecoration: "underline" },
}}
{...props}
/>
);
};

const styles = {
// Adds some spacing from the popover content
pillContainer: {
padding: "8px 0",
},
notificationItem: (theme) => ({
padding: 20,
lineHeight: "1.5",
borderTop: `1px solid ${theme.palette.divider}`,

"&:first-child": {
borderTop: 0,
},
}),
notificationDetail: (theme) => ({
margin: 0,
color: theme.palette.text.secondary,
lineHeight: 1.6,
display: "block",
marginTop: 8,
}),
} satisfies Record<string, Interpolation<Theme>>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import {
MockOutdatedWorkspace,
MockTemplate,
MockTemplateVersion,
MockWorkspace,
} from "testHelpers/entities";
import { WorkspaceNotifications } from "./WorkspaceNotifications";
import type { Meta, StoryObj } from "@storybook/react";
import { withDashboardProvider } from "testHelpers/storybook";
import { getWorkspaceResolveAutostartQueryKey } from "api/queries/workspaceQuota";

const defaultPermissions = {
readWorkspace: true,
updateTemplate: true,
updateWorkspace: true,
viewDeploymentValues: true,
};

const meta: Meta<typeof WorkspaceNotifications> = {
title: "components/WorkspaceNotifications",
component: WorkspaceNotifications,
args: {
latestVersion: MockTemplateVersion,
template: MockTemplate,
workspace: MockWorkspace,
permissions: defaultPermissions,
},
decorators: [withDashboardProvider],
parameters: {
queries: [
{
key: getWorkspaceResolveAutostartQueryKey(MockOutdatedWorkspace.id),
data: {
parameter_mismatch: false,
},
},
],
features: ["advanced_template_scheduling"],
},
};

export default meta;
type Story = StoryObj<typeof WorkspaceNotifications>;

export const Outdated: Story = {
args: {
workspace: MockOutdatedWorkspace,
defaultOpen: "info",
},
};

export const RequiresManualUpdate: Story = {
args: {
workspace: {
...MockOutdatedWorkspace,
automatic_updates: "always",
autostart_schedule: "daily",
},
defaultOpen: "warning",
},
parameters: {
queries: [
{
key: getWorkspaceResolveAutostartQueryKey(MockOutdatedWorkspace.id),
data: {
parameter_mismatch: true,
},
},
],
},
};

export const Unhealthy: Story = {
args: {
workspace: {
...MockWorkspace,
health: {
...MockWorkspace.health,
healthy: false,
},
latest_build: {
...MockWorkspace.latest_build,
status: "running",
},
},
defaultOpen: "warning",
},
};

export const UnhealthyWithoutUpdatePermission: Story = {
args: {
...Unhealthy.args,
permissions: {
...defaultPermissions,
updateWorkspace: false,
},
},
};

const DormantWorkspace = {
...MockWorkspace,
dormant_at: new Date("2020-01-01T00:00:00Z").toISOString(),
};

export const Dormant: Story = {
args: {
defaultOpen: "warning",
workspace: DormantWorkspace,
},
};

export const DormantWithDeletingDate: Story = {
args: {
...Dormant.args,
workspace: {
...DormantWorkspace,
deleting_at: new Date("2020-10-01T00:00:00Z").toISOString(),
},
},
};

export const PendingInQueue: Story = {
args: {
workspace: {
...MockWorkspace,
latest_build: {
...MockWorkspace.latest_build,
status: "pending",
job: {
...MockWorkspace.latest_build.job,
queue_size: 10,
queue_position: 3,
},
},
},
defaultOpen: "info",
},
};

export const TemplateDeprecated: Story = {
args: {
template: {
...MockTemplate,
deprecated: true,
deprecation_message:
"Template deprecated due to reasons. [Learn more](#)",
},
defaultOpen: "warning",
},
};
Loading