Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

refactor: improve app status and statuses#18121

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
BrunoQuaresma merged 1 commit intomainfrombq/improvements-resource-stats
May 30, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
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
8 changes: 4 additions & 4 deletionssite/src/modules/apps/AppStatusIcon.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,22 +25,22 @@ export const AppStatusIcon: FC<AppStatusIconProps> = ({
switch (status.state) {
case "complete":
return (
<CircleCheckIcon className={cn([className,"text-content-success"])} />
<CircleCheckIcon className={cn(["text-content-success", className])} />
);
case "failure":
return (
<CircleAlertIcon className={cn([className,"text-content-warning"])} />
<CircleAlertIcon className={cn(["text-content-warning", className])} />
);
case "working":
return latest ? (
<Spinner size="sm" className="shrink-0" loading />
) : (
<HourglassIcon className={cn([className,"text-highlight-sky"])} />
<HourglassIcon className={cn(["text-highlight-sky", className])} />
);
default:
return (
<TriangleAlertIcon
className={cn([className,"text-content-secondary"])}
className={cn(["text-content-secondary", className])}
/>
);
}
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -48,3 +48,10 @@ export const LongMessage: Story = {
},
},
};

export const Disabled: Story = {
args: {
status: MockWorkspaceAppStatus,
disabled: true,
},
};
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
import type {
WorkspaceAppStatus as APIWorkspaceAppStatus,
WorkspaceAppStatusState,
} from "api/typesGenerated";
import { Spinner } from "components/Spinner/Spinner";
import type { WorkspaceAppStatus as APIWorkspaceAppStatus } from "api/typesGenerated";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import {CircleAlertIcon, CircleCheckIcon} from "lucide-react";
importtype { ReactNode } from "react";
import {AppStatusIcon} from "modules/apps/AppStatusIcon";
import{ cn } from "utils/cn";

const iconByState: Record<WorkspaceAppStatusState, ReactNode> = {
complete: (
<CircleCheckIcon className="size-4 shrink-0 text-content-success" />
),
failure: <CircleAlertIcon className="size-4 shrink-0 text-content-warning" />,
working: <Spinner size="sm" className="shrink-0" loading />,
type WorkspaceAppStatusProps = {
status: APIWorkspaceAppStatus | null;
disabled?: boolean;
};

export const WorkspaceAppStatus = ({
status,
}: {
status: APIWorkspaceAppStatus | null;
}) => {
disabled,
}: WorkspaceAppStatusProps) => {
if (!status) {
return (
<span className="text-content-disabled text-sm">
Expand All@@ -39,7 +31,13 @@ export const WorkspaceAppStatus = ({
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center gap-2">
{iconByState[status.state]}
<AppStatusIcon
status={status}
latest
className={cn({
"text-content-disabled": disabled,
})}
/>
<span className="whitespace-nowrap max-w-72 overflow-hidden text-ellipsis text-sm text-content-primary font-medium">
{status.message}
</span>
Expand Down
212 changes: 62 additions & 150 deletionssite/src/pages/WorkspacePage/AppStatuses.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
import type { Meta, StoryObj } from "@storybook/react";
import type { WorkspaceAppStatus } from "api/typesGenerated";
import {
MockWorkspace,
MockWorkspaceAgent,
MockWorkspaceApp,
MockWorkspaceAppStatus,
MockWorkspaceAppStatuses,
createTimestamp,
} from "testHelpers/entities";
import { withProxyProvider } from "testHelpers/storybook";
import { AppStatuses } from "./AppStatuses";
Expand All@@ -13,6 +16,8 @@ const meta: Meta<typeof AppStatuses> = {
component: AppStatuses,
args: {
referenceDate: new Date("2024-03-26T15:15:00Z"),
agent: mockAgent(MockWorkspaceAppStatuses),
workspace: MockWorkspace,
},
decorators: [withProxyProvider()],
};
Expand All@@ -21,163 +26,70 @@ export default meta;

type Story = StoryObj<typeof AppStatuses>;

export const Default: Story = {
export const Default: Story = {};

// Add a story with a "Working" status as the latest
export const WorkingState: Story = {
args: {
workspace: MockWorkspace,
agent: {
...MockWorkspaceAgent,
apps: [
{
...MockWorkspaceApp,
statuses: [
{
// This is the latest status chronologically (15:04:38)
...MockWorkspaceAppStatus,
id: "status-7",
icon: "/emojis/1f4dd.png", // 📝
message: "Creating PR with gh CLI",
created_at: createTimestamp(4, 38), // 15:04:38
uri: "https://github.com/coder/coder/pull/5678",
state: "complete" as const,
},
{
// (15:03:56)
...MockWorkspaceAppStatus,
id: "status-6",
icon: "/emojis/1f680.png", // 🚀
message: "Pushing branch to remote",
created_at: createTimestamp(3, 56), // 15:03:56
uri: "",
state: "complete" as const,
},
{
// (15:02:29)
...MockWorkspaceAppStatus,
id: "status-5",
icon: "/emojis/1f527.png", // 🔧
message: "Configuring git identity",
created_at: createTimestamp(2, 29), // 15:02:29
uri: "",
state: "complete" as const,
},
{
// (15:02:04)
...MockWorkspaceAppStatus,
id: "status-4",
icon: "/emojis/1f4be.png", // 💾
message: "Committing changes",
created_at: createTimestamp(2, 4), // 15:02:04
uri: "",
state: "complete" as const,
},
{
// (15:01:44)
...MockWorkspaceAppStatus,
id: "status-3",
icon: "/emojis/2795.png", // +
message: "Adding files to staging",
created_at: createTimestamp(1, 44), // 15:01:44
uri: "",
state: "complete" as const,
},
{
// (15:01:32)
...MockWorkspaceAppStatus,
id: "status-2",
icon: "/emojis/1f33f.png", // 🌿
message: "Creating a new branch for PR",
created_at: createTimestamp(1, 32), // 15:01:32
uri: "",
state: "complete" as const,
},
{
// (15:01:00) - Oldest
...MockWorkspaceAppStatus,
id: "status-1",
icon: "/emojis/1f680.png", // 🚀
message: "Starting to create a PR",
created_at: createTimestamp(1, 0), // 15:01:00
uri: "",
state: "complete" as const,
},
].sort(
(a, b) =>
new Date(b.created_at).getTime() -
new Date(a.created_at).getTime(),
), // Ensure sorted correctly for component input if needed
},
],
},
agent: mockAgent([
{
// This is now the latest (15:05:15) and is "working"
...MockWorkspaceAppStatus,
id: "status-8",
icon: "", // Let the component handle the spinner icon
message: "Processing final checks...",
created_at: createTimestamp(5, 15), // 15:05:15 (after referenceDate)
uri: "",
state: "working" as const,
},
...MockWorkspaceAppStatuses,
]),
},
};

// Pass the reference date to the component for Storybook rendering
export const LongStatusText: Story = {
args: {
agent: mockAgent([
{
// This is now the latest (15:05:15) and is "working"
...MockWorkspaceAppStatus,
id: "status-8",
icon: "", // Let the component handle the spinner icon
message:
"Processing final checks with a very long message that exceeds the usual length to test how the component handles overflow and truncation in the UI. This should be long enough to ensure it wraps correctly and doesn't break the layout.",
created_at: createTimestamp(5, 15), // 15:05:15 (after referenceDate)
uri: "",
state: "complete" as const,
},
...MockWorkspaceAppStatuses,
]),
},
};

// Add a story with a "Working" status as the latest
export const WorkingState: Story = {
export const SingleStatus: Story = {
args: {
workspace: MockWorkspace,
agent: {
...MockWorkspaceAgent,
apps: [
{
...MockWorkspaceApp,
statuses: [
{
// This is now the latest (15:05:15) and is "working"
...MockWorkspaceAppStatus,
id: "status-8",
icon: "", // Let the component handle the spinner icon
message: "Processing final checks...",
created_at: createTimestamp(5, 15), // 15:05:15 (after referenceDate)
uri: "",
state: "working" as const,
},
{
// Previous latest (15:04:38)
...MockWorkspaceAppStatus,
id: "status-7",
icon: "/emojis/1f4dd.png", // 📝
message: "Creating PR with gh CLI",
created_at: createTimestamp(4, 38), // 15:04:38
uri: "https://github.com/coder/coder/pull/5678",
state: "complete" as const,
},
{
// (15:03:56)
...MockWorkspaceAppStatus,
id: "status-6",
icon: "/emojis/1f680.png", // 🚀
message: "Pushing branch to remote",
created_at: createTimestamp(3, 56), // 15:03:56
uri: "",
state: "complete" as const,
},
// ... include other older statuses if desired ...
{
// (15:01:00) - Oldest
...MockWorkspaceAppStatus,
id: "status-1",
icon: "/emojis/1f680.png", // 🚀
message: "Starting to create a PR",
created_at: createTimestamp(1, 0), // 15:01:00
uri: "",
state: "complete" as const,
},
].sort(
(a, b) =>
new Date(b.created_at).getTime() -
new Date(a.created_at).getTime(),
),
},
],
},
agent: mockAgent([
{
...MockWorkspaceAppStatus,
id: "status-1",
icon: "",
message: "Initial setup complete.",
created_at: createTimestamp(5, 10), // 15:05:10 (after referenceDate)
uri: "",
state: "complete" as const,
},
]),
},
};

function createTimestamp(minuteOffset: number, secondOffset: number) {
const baseDate = new Date("2024-03-26T15:00:00Z");
baseDate.setMinutes(baseDate.getMinutes() + minuteOffset);
baseDate.setSeconds(baseDate.getSeconds() + secondOffset);
return baseDate.toISOString();
function mockAgent(statuses: WorkspaceAppStatus[]) {
return {
...MockWorkspaceAgent,
apps: [
{
...MockWorkspaceApp,
statuses,
},
],
};
}
13 changes: 8 additions & 5 deletionssite/src/pages/WorkspacePage/AppStatuses.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -103,15 +103,17 @@ export const AppStatuses: FC<AppStatusesProps> = ({
<div className="flex flex-col border border-solid border-border rounded-lg">
<div
className={`
flex items-center justify-between px-4 py-3
flex items-center justify-between px-4 py-3 gap-6
border-0 [&:not(:last-child)]:border-b border-solid border-border
`}
>
<div className="flex flex-col">
<span className="text-sm font-medium text-content-primary flex items-center gap-2">
<div className="flex flex-col overflow-hidden">
<div className="text-sm font-medium text-content-primary flex items-center gap-2">
<AppStatusIcon status={latestStatus} latest />
{latestStatus.message}
</span>
<span className="block flex-1 whitespace-nowrap overflow-hidden text-ellipsis">
{latestStatus.message}
</span>
</div>
<span className="text-xs text-content-secondary first-letter:uppercase block pl-[26px]">
{timeFrom(new Date(latestStatus.created_at), comparisonDate)}
</span>
Expand DownExpand Up@@ -154,6 +156,7 @@ export const AppStatuses: FC<AppStatusesProps> = ({
<Tooltip>
<TooltipTrigger asChild>
<Button
disabled={otherStatuses.length === 0}
size="icon"
variant="subtle"
onClick={() => {
Expand Down
5 changes: 4 additions & 1 deletionsite/src/pages/WorkspacesPage/WorkspacesTable.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -297,7 +297,10 @@ export const WorkspacesTable: FC<WorkspacesTableProps> = ({

{hasActivity && (
<TableCell>
<WorkspaceAppStatus status={workspace.latest_app_status} />
<WorkspaceAppStatus
status={workspace.latest_app_status}
disabled={workspace.latest_build.status !== "running"}
/>
</TableCell>
)}

Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp