Skip to content

Commit bc3b8d5

Browse files
1 parent b330c08 commit bc3b8d5

File tree

10 files changed

+676
-64
lines changed

10 files changed

+676
-64
lines changed

site/src/components/Table/Table.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* @see {@link https://ui.shadcn.com/docs/components/table}
44
*/
55

6+
import { type VariantProps, cva } from "class-variance-authority";
67
import * as React from "react";
78
import { cn } from "utils/cn";
89

@@ -60,15 +61,38 @@ const TableFooter = React.forwardRef<
6061
/>
6162
));
6263

64+
const tableRowVariants = cva(
65+
[
66+
"border-0 border-b border-solid border-border transition-colors",
67+
"data-[state=selected]:bg-muted",
68+
],
69+
{
70+
variants: {
71+
hover: {
72+
false: null,
73+
true: cn([
74+
"cursor-pointer hover:outline focus:outline outline-1 -outline-offset-1 outline-border-hover",
75+
"first:rounded-t-md last:rounded-b-md",
76+
]),
77+
},
78+
},
79+
defaultVariants: {
80+
hover: false,
81+
},
82+
},
83+
);
84+
6385
export const TableRow = React.forwardRef<
6486
HTMLTableRowElement,
65-
React.HTMLAttributes<HTMLTableRowElement>
66-
>(({ className, ...props }, ref) => (
87+
React.HTMLAttributes<HTMLTableRowElement> &
88+
VariantProps<typeof tableRowVariants>
89+
>(({ className, hover, ...props }, ref) => (
6790
<tr
6891
ref={ref}
6992
className={cn(
7093
"border-0 border-b border-solid border-border transition-colors",
7194
"data-[state=selected]:bg-muted",
95+
tableRowVariants({ hover }),
7296
className,
7397
)}
7498
{...props}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { WorkspaceAppStatus } from "api/typesGenerated";
2+
import { Spinner } from "components/Spinner/Spinner";
3+
import {
4+
CircleAlertIcon,
5+
CircleCheckIcon,
6+
HourglassIcon,
7+
TriangleAlertIcon,
8+
} from "lucide-react";
9+
import type { FC } from "react";
10+
import { cn } from "utils/cn";
11+
12+
type AppStatusIconProps = {
13+
status: WorkspaceAppStatus;
14+
latest: boolean;
15+
className?: string;
16+
};
17+
18+
export const AppStatusIcon: FC<AppStatusIconProps> = ({
19+
status,
20+
latest,
21+
className: customClassName,
22+
}) => {
23+
const className = cn(["size-4 shrink-0", customClassName]);
24+
25+
switch (status.state) {
26+
case "complete":
27+
return (
28+
<CircleCheckIcon className={cn([className, "text-content-success"])} />
29+
);
30+
case "failure":
31+
return (
32+
<CircleAlertIcon className={cn([className, "text-content-warning"])} />
33+
);
34+
case "working":
35+
return latest ? (
36+
<Spinner size="sm" className="shrink-0" loading />
37+
) : (
38+
<HourglassIcon className={cn([className, "text-highlight-sky"])} />
39+
);
40+
default:
41+
return (
42+
<TriangleAlertIcon
43+
className={cn([className, "text-content-secondary"])}
44+
/>
45+
);
46+
}
47+
};

site/src/modules/tasks/tasks.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Workspace } from "api/typesGenerated";
2+
3+
export const AI_PROMPT_PARAMETER_NAME = "AI Prompt";
4+
5+
export type Task = {
6+
workspace: Workspace;
7+
prompt: string;
8+
};

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export const WorkspaceAppStatus = ({
3434
}
3535

3636
return (
37-
<div className="flex flex-col">
37+
<div className="flex flex-col text-content-secondary">
3838
<TooltipProvider>
3939
<Tooltip>
4040
<TooltipTrigger asChild>
@@ -48,7 +48,9 @@ export const WorkspaceAppStatus = ({
4848
<TooltipContent>{status.message}</TooltipContent>
4949
</Tooltip>
5050
</TooltipProvider>
51-
<span className="first-letter:uppercase block pl-6">{status.state}</span>
51+
<span className="text-xs first-letter:uppercase block pl-6">
52+
{status.state}
53+
</span>
5254
</div>
5355
);
5456
};
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { spyOn } from "@storybook/test";
3+
import {
4+
MockFailedWorkspace,
5+
MockStartingWorkspace,
6+
MockStoppedWorkspace,
7+
MockWorkspace,
8+
MockWorkspaceAgent,
9+
MockWorkspaceApp,
10+
MockWorkspaceAppStatus,
11+
MockWorkspaceResource,
12+
mockApiError,
13+
} from "testHelpers/entities";
14+
import { withProxyProvider } from "testHelpers/storybook";
15+
import TaskPage, { data } from "./TaskPage";
16+
17+
const meta: Meta<typeof TaskPage> = {
18+
title: "pages/TaskPage",
19+
component: TaskPage,
20+
parameters: {
21+
layout: "fullscreen",
22+
},
23+
};
24+
25+
export default meta;
26+
type Story = StoryObj<typeof TaskPage>;
27+
28+
export const Loading: Story = {
29+
beforeEach: () => {
30+
spyOn(data, "fetchTask").mockImplementation(
31+
() => new Promise((res) => 1000 * 60 * 60),
32+
);
33+
},
34+
};
35+
36+
export const LoadingError: Story = {
37+
beforeEach: () => {
38+
spyOn(data, "fetchTask").mockRejectedValue(
39+
mockApiError({
40+
message: "Failed to load task",
41+
detail: "You don't have permission to access this resource.",
42+
}),
43+
);
44+
},
45+
};
46+
47+
export const WaitingOnBuild: Story = {
48+
beforeEach: () => {
49+
spyOn(data, "fetchTask").mockResolvedValue({
50+
prompt: "Create competitors page",
51+
workspace: MockStartingWorkspace,
52+
});
53+
},
54+
};
55+
56+
export const WaitingOnStatus: Story = {
57+
beforeEach: () => {
58+
spyOn(data, "fetchTask").mockResolvedValue({
59+
prompt: "Create competitors page",
60+
workspace: {
61+
...MockWorkspace,
62+
latest_app_status: null,
63+
},
64+
});
65+
},
66+
};
67+
68+
export const FailedBuild: Story = {
69+
beforeEach: () => {
70+
spyOn(data, "fetchTask").mockResolvedValue({
71+
prompt: "Create competitors page",
72+
workspace: MockFailedWorkspace,
73+
});
74+
},
75+
};
76+
77+
export const TerminatedBuild: Story = {
78+
beforeEach: () => {
79+
spyOn(data, "fetchTask").mockResolvedValue({
80+
prompt: "Create competitors page",
81+
workspace: MockStoppedWorkspace,
82+
});
83+
},
84+
};
85+
86+
export const TerminatedBuildWithStatus: Story = {
87+
beforeEach: () => {
88+
spyOn(data, "fetchTask").mockResolvedValue({
89+
prompt: "Create competitors page",
90+
workspace: {
91+
...MockStoppedWorkspace,
92+
latest_app_status: MockWorkspaceAppStatus,
93+
},
94+
});
95+
},
96+
};
97+
98+
export const Active: Story = {
99+
decorators: [withProxyProvider()],
100+
beforeEach: () => {
101+
spyOn(data, "fetchTask").mockResolvedValue({
102+
prompt: "Create competitors page",
103+
workspace: {
104+
...MockWorkspace,
105+
latest_build: {
106+
...MockWorkspace.latest_build,
107+
resources: [
108+
{
109+
...MockWorkspaceResource,
110+
agents: [
111+
{
112+
...MockWorkspaceAgent,
113+
apps: [
114+
{
115+
...MockWorkspaceApp,
116+
id: "claude-code",
117+
display_name: "Claude Code",
118+
icon: "/icon/claude.svg",
119+
url: `${window.location.protocol}/iframe.html?viewMode=story&id=pages-terminal--ready&args=&globals=`,
120+
external: true,
121+
statuses: [
122+
MockWorkspaceAppStatus,
123+
{
124+
...MockWorkspaceAppStatus,
125+
id: "2",
126+
message: "Planning changes",
127+
state: "working",
128+
},
129+
],
130+
},
131+
{
132+
...MockWorkspaceApp,
133+
id: "vscode",
134+
display_name: "VSCode",
135+
icon: "/icon/code.svg",
136+
},
137+
],
138+
},
139+
],
140+
},
141+
],
142+
},
143+
latest_app_status: {
144+
...MockWorkspaceAppStatus,
145+
app_id: "claude-code",
146+
},
147+
},
148+
});
149+
},
150+
};

0 commit comments

Comments
 (0)