Skip to content

Commit c6e4355

Browse files
committed
refactor: activity column in workspaces table
1 parent e1934fe commit c6e4355

File tree

3 files changed

+107
-341
lines changed

3 files changed

+107
-341
lines changed

site/src/modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus.stories.tsx

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import type { Meta, StoryObj } from "@storybook/react";
22
import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext";
33
import {
44
MockProxyLatencies,
5-
MockWorkspace,
6-
MockWorkspaceAgent,
7-
MockWorkspaceApp,
85
MockWorkspaceAppStatus,
96
} from "testHelpers/entities";
107
import { WorkspaceAppStatus } from "./WorkspaceAppStatus";
@@ -68,24 +65,6 @@ export const Working: Story = {
6865
},
6966
};
7067

71-
export const LongURI: Story = {
72-
args: {
73-
status: {
74-
...MockWorkspaceAppStatus,
75-
uri: "https://www.google.com/search?q=hello+world+plus+a+lot+of+other+words",
76-
},
77-
},
78-
};
79-
80-
export const FileURI: Story = {
81-
args: {
82-
status: {
83-
...MockWorkspaceAppStatus,
84-
uri: "file:///Users/jason/Desktop/test.txt",
85-
},
86-
},
87-
};
88-
8968
export const LongMessage: Story = {
9069
args: {
9170
status: {
@@ -95,14 +74,3 @@ export const LongMessage: Story = {
9574
},
9675
},
9776
};
98-
99-
export const WithApp: Story = {
100-
args: {
101-
status: MockWorkspaceAppStatus,
102-
app: {
103-
...MockWorkspaceApp,
104-
},
105-
agent: MockWorkspaceAgent,
106-
workspace: MockWorkspace,
107-
},
108-
};
Lines changed: 34 additions & 285 deletions
Original file line numberDiff line numberDiff line change
@@ -1,305 +1,54 @@
1-
import type { Theme } from "@emotion/react";
2-
import { useTheme } from "@emotion/react";
3-
import CircularProgress from "@mui/material/CircularProgress";
41
import type {
52
WorkspaceAppStatus as APIWorkspaceAppStatus,
6-
Workspace,
7-
WorkspaceAgent,
8-
WorkspaceApp,
3+
WorkspaceAppStatusState,
94
} from "api/typesGenerated";
5+
import { Spinner } from "components/Spinner/Spinner";
106
import {
11-
CircleAlertIcon,
12-
CircleCheckIcon,
13-
ExternalLinkIcon,
14-
FileIcon,
15-
LayoutGridIcon,
16-
TriangleAlertIcon,
17-
} from "lucide-react";
18-
import { useAppLink } from "modules/apps/useAppLink";
19-
import type { FC } from "react";
20-
21-
const formatURI = (uri: string) => {
22-
try {
23-
const url = new URL(uri);
24-
return url.hostname + url.pathname;
25-
} catch {
26-
return uri;
27-
}
28-
};
29-
30-
const getStatusColor = (
31-
theme: Theme,
32-
state: APIWorkspaceAppStatus["state"],
33-
) => {
34-
switch (state) {
35-
case "complete":
36-
return theme.palette.success.main;
37-
case "failure":
38-
return theme.palette.error.main;
39-
case "working":
40-
return theme.palette.primary.main;
41-
default:
42-
// Assuming unknown state maps to warning/secondary visually
43-
return theme.palette.text.secondary;
44-
}
45-
};
46-
47-
const getStatusIcon = (theme: Theme, state: APIWorkspaceAppStatus["state"]) => {
48-
const color = getStatusColor(theme, state);
49-
switch (state) {
50-
case "complete":
51-
return <CircleCheckIcon className="size-icon-xs" style={{ color }} />;
52-
case "failure":
53-
return <CircleAlertIcon className="size-icon-xs" style={{ color }} />;
54-
case "working":
55-
return <CircularProgress size={16} sx={{ color }} />;
56-
default:
57-
return <TriangleAlertIcon className="size-icon-xs" style={{ color }} />;
58-
}
7+
Tooltip,
8+
TooltipContent,
9+
TooltipProvider,
10+
TooltipTrigger,
11+
} from "components/Tooltip/Tooltip";
12+
import { CircleAlertIcon, CircleCheckIcon } from "lucide-react";
13+
import type { ReactNode } from "react";
14+
15+
const iconByState: Record<WorkspaceAppStatusState, ReactNode> = {
16+
complete: (
17+
<CircleCheckIcon className="size-4 shrink-0 text-content-success" />
18+
),
19+
failure: <CircleAlertIcon className="size-4 shrink-0 text-content-warning" />,
20+
working: <Spinner size="sm" className="shrink-0" loading />,
5921
};
6022

6123
export const WorkspaceAppStatus = ({
62-
workspace,
6324
status,
64-
agent,
65-
app,
6625
}: {
67-
workspace: Workspace;
68-
status?: APIWorkspaceAppStatus | null;
69-
app?: WorkspaceApp;
70-
agent?: WorkspaceAgent;
26+
status: APIWorkspaceAppStatus | null;
7127
}) => {
72-
const theme = useTheme();
73-
const commonStyles = useCommonStyles();
74-
7528
if (!status) {
7629
return (
77-
<div
78-
css={{
79-
display: "flex",
80-
alignItems: "center",
81-
gap: 12,
82-
minWidth: 0,
83-
paddingRight: 16,
84-
}}
85-
>
86-
<div
87-
css={{
88-
fontSize: "14px",
89-
color: theme.palette.text.disabled,
90-
flexShrink: 1,
91-
minWidth: 0,
92-
}}
93-
>
94-
95-
</div>
96-
</div>
30+
<span className="text-content-disabled text-sm">
31+
-<span className="sr-only">No activity</span>
32+
</span>
9733
);
9834
}
99-
const isFileURI = status.uri?.startsWith("file://");
10035

10136
return (
102-
<div
103-
css={{
104-
display: "flex",
105-
alignItems: "flex-start",
106-
gap: 8,
107-
minWidth: 0,
108-
paddingRight: 16,
109-
}}
110-
>
111-
<div
112-
css={{
113-
display: "flex",
114-
alignItems: "center",
115-
flexShrink: 0,
116-
marginTop: 2,
117-
}}
118-
>
119-
{getStatusIcon(theme, status.state)}
120-
</div>
121-
<div
122-
css={{
123-
display: "flex",
124-
flexDirection: "column",
125-
gap: 6,
126-
minWidth: 0,
127-
flex: 1,
128-
}}
129-
>
130-
<div
131-
css={{
132-
fontSize: "14px",
133-
lineHeight: "20px",
134-
color: "text.primary",
135-
margin: 0,
136-
display: "-webkit-box",
137-
WebkitLineClamp: 2,
138-
WebkitBoxOrient: "vertical",
139-
overflow: "hidden",
140-
textOverflow: "ellipsis",
141-
maxWidth: "100%",
142-
}}
143-
>
144-
{status.message}
145-
</div>
146-
<div
147-
css={{
148-
display: "flex",
149-
alignItems: "center",
150-
}}
151-
>
152-
{app && agent && (
153-
<AppLink app={app} workspace={workspace} agent={agent} />
154-
)}
155-
{status.uri && (
156-
<div
157-
css={{
158-
display: "flex",
159-
minWidth: 0,
160-
}}
161-
>
162-
{isFileURI ? (
163-
<div
164-
css={{
165-
...commonStyles,
166-
}}
167-
>
168-
<FileIcon
169-
className="size-icon-xs"
170-
css={{
171-
opacity: 0.5,
172-
marginRight: "0.25rem",
173-
}}
174-
/>
175-
<span>{formatURI(status.uri)}</span>
176-
</div>
177-
) : (
178-
<a
179-
href={status.uri}
180-
target="_blank"
181-
rel="noopener noreferrer"
182-
css={{
183-
...commonStyles,
184-
color: theme.palette.text.secondary,
185-
"&:hover": {
186-
...commonStyles["&:hover"],
187-
color: theme.palette.text.primary,
188-
},
189-
}}
190-
>
191-
<ExternalLinkIcon
192-
className="size-icon-xs"
193-
css={{
194-
opacity: 0.7,
195-
flexShrink: 0,
196-
marginRight: 2,
197-
}}
198-
/>
199-
<span
200-
css={{
201-
backgroundColor: "transparent",
202-
padding: 0,
203-
color: "inherit",
204-
fontSize: "inherit",
205-
lineHeight: "inherit",
206-
overflow: "hidden",
207-
textOverflow: "ellipsis",
208-
whiteSpace: "nowrap",
209-
}}
210-
>
211-
{formatURI(status.uri)}
212-
</span>
213-
</a>
214-
)}
37+
<div className="flex flex-col">
38+
<TooltipProvider>
39+
<Tooltip>
40+
<TooltipTrigger asChild>
41+
<div className="flex items-center gap-2">
42+
{iconByState[status.state]}
43+
<span className="whitespace-nowrap max-w-72 overflow-hidden text-ellipsis text-sm text-content-primary font-medium">
44+
{status.message}
45+
</span>
21546
</div>
216-
)}
217-
</div>
218-
</div>
47+
</TooltipTrigger>
48+
<TooltipContent>{status.message}</TooltipContent>
49+
</Tooltip>
50+
</TooltipProvider>
51+
<span className="first-letter:uppercase block pl-6">{status.state}</span>
21952
</div>
22053
);
22154
};
222-
223-
type AppLinkProps = {
224-
app: WorkspaceApp;
225-
workspace: Workspace;
226-
agent: WorkspaceAgent;
227-
};
228-
229-
const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
230-
const theme = useTheme();
231-
const commonStyles = useCommonStyles();
232-
const link = useAppLink(app, { agent, workspace });
233-
234-
return (
235-
<a
236-
href={link.href}
237-
onClick={link.onClick}
238-
target="_blank"
239-
rel="noopener noreferrer"
240-
css={{
241-
...commonStyles,
242-
marginRight: 8,
243-
position: "relative",
244-
color: theme.palette.text.secondary,
245-
"&:hover": {
246-
...commonStyles["&:hover"],
247-
color: theme.palette.text.primary,
248-
"& img": {
249-
opacity: 1,
250-
},
251-
},
252-
}}
253-
>
254-
{app.icon ? (
255-
<img
256-
src={app.icon}
257-
alt={`${app.display_name} icon`}
258-
width={14}
259-
height={14}
260-
css={{
261-
borderRadius: "3px",
262-
opacity: 0.8,
263-
marginRight: 4,
264-
}}
265-
/>
266-
) : (
267-
<LayoutGridIcon
268-
className="size-icon-xs"
269-
css={{
270-
opacity: 0.7,
271-
}}
272-
/>
273-
)}
274-
<span>{app.display_name}</span>
275-
</a>
276-
);
277-
};
278-
279-
const useCommonStyles = () => {
280-
const theme = useTheme();
281-
282-
return {
283-
fontSize: "12px",
284-
lineHeight: "15px",
285-
color: theme.palette.text.disabled,
286-
display: "inline-flex",
287-
alignItems: "center",
288-
gap: 4,
289-
padding: "2px 6px",
290-
borderRadius: "6px",
291-
bgcolor: "transparent",
292-
minWidth: 0,
293-
maxWidth: "fit-content",
294-
overflow: "hidden",
295-
textOverflow: "ellipsis",
296-
whiteSpace: "nowrap",
297-
textDecoration: "none",
298-
transition: "all 0.15s ease-in-out",
299-
"&:hover": {
300-
textDecoration: "none",
301-
backgroundColor: theme.palette.action.hover,
302-
color: theme.palette.text.secondary,
303-
},
304-
};
305-
};

0 commit comments

Comments
 (0)