Skip to content
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

fix: show banner in archived workspace #165

Merged
merged 4 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion src/components/icons/FlipBackward.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const SvgFlipBackward = (props: SVGProps<SVGSVGElement>) => (
{...props}
>
<path
stroke="#2E323A"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
d="M1 5h13.5a4.5 4.5 0 1 1 0 9H10M1 5l4-4M1 5l4 4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ vi.mock("@monaco-editor/react", () => {
});

const renderComponent = () =>
render(<SystemPromptEditor workspaceName="foo" />);
render(<SystemPromptEditor isArchived={false} workspaceName="foo" />);

test("can update system prompt", async () => {
server.use(
http.get("*/api/v1/workspaces/:name/system-prompt", () => {
return HttpResponse.json({ prompt: "initial prompt from server" });
}),
})
);

const { getByRole } = renderComponent();
Expand All @@ -48,7 +48,7 @@ test("can update system prompt", async () => {
server.use(
http.get("*/api/v1/workspaces/:name/system-prompt", () => {
return HttpResponse.json({ prompt: "new prompt from test" });
}),
})
);

await waitFor(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,11 @@ function usePromptValue({
export function SystemPromptEditor({
className,
workspaceName,
isArchived,
}: {
className?: string;
workspaceName: string;
isArchived: boolean | undefined;
}) {
const context = useContext(DarkModeContext);
const theme: Theme = inferDarkMode(context);
Expand Down Expand Up @@ -194,21 +196,22 @@ export function SystemPromptEditor({
<Editor
options={{
minimap: { enabled: false },
readOnly: isArchived,
}}
value={value}
onChange={(v) => setValue(v ?? "")}
height="20rem"
defaultLanguage="Markdown"
theme={theme}
className="bg-base"
className={twMerge("bg-base", isArchived ? "opacity-25" : "")}
/>
)}
</div>
</CardBody>
<CardFooter className="justify-end gap-2">
<Button
isPending={isMutationPending}
isDisabled={Boolean(isGetPromptPending ?? saved)}
isDisabled={Boolean(isArchived ?? isGetPromptPending ?? saved)}
onPress={() => handleSubmit(value)}
>
{saved ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const mockToast = vi.fn();
vi.mock("react-router-dom", async () => {
const original =
await vi.importActual<typeof import("react-router-dom")>(
"react-router-dom",
"react-router-dom"
);
return {
...original,
Expand All @@ -19,7 +19,7 @@ vi.mock("react-router-dom", async () => {
vi.mock("@stacklok/ui-kit", async () => {
const original =
await vi.importActual<typeof import("@stacklok/ui-kit")>(
"@stacklok/ui-kit",
"@stacklok/ui-kit"
);
return {
...original,
Expand All @@ -28,7 +28,7 @@ vi.mock("@stacklok/ui-kit", async () => {
});

test("archive workspace", async () => {
render(<ArchiveWorkspace workspaceName="foo" />);
render(<ArchiveWorkspace isArchived={false} workspaceName="foo" />);

await userEvent.click(screen.getByRole("button", { name: /archive/i }));
await waitFor(() => expect(mockNavigate).toBeCalled());
Expand Down
18 changes: 7 additions & 11 deletions src/features/workspace/components/archive-workspace.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Card, CardBody, Button, Text } from "@stacklok/ui-kit";
import { twMerge } from "tailwind-merge";
import { useArchiveWorkspace } from "../../workspace-system-prompt/hooks/use-archive-workspace";
import { useRestoreWorkspaceButton } from "../hooks/use-restore-workspace-button";
import { useArchiveWorkspaceButton } from "../hooks/use-archive-workspace-button";

export function ArchiveWorkspace({
className,
workspaceName,
isArchived,
}: {
workspaceName: string;
className?: string;
isArchived: boolean | undefined;
}) {
const { mutate, isPending } = useArchiveWorkspace();
const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName });
const archiveButtonProps = useArchiveWorkspaceButton({ workspaceName });

return (
<Card className={twMerge(className, "shrink-0")}>
Expand All @@ -22,15 +26,7 @@ export function ArchiveWorkspace({
</Text>
</div>

<Button
isDestructive
isPending={isPending}
onPress={() => {
mutate({ path: { workspace_name: workspaceName } });
}}
>
Archive
</Button>
<Button {...(isArchived ? restoreButtonProps : archiveButtonProps)} />
</CardBody>
</Card>
);
Expand Down
9 changes: 8 additions & 1 deletion src/features/workspace/components/workspace-name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import { FormEvent, useState } from "react";
export function WorkspaceName({
className,
workspaceName,
isArchived,
}: {
className?: string;
workspaceName: string;
isArchived: boolean | undefined;
}) {
const [name, setName] = useState(workspaceName);
const { mutate, isPending, error } = useCreateWorkspace();
Expand All @@ -38,6 +40,7 @@ export function WorkspaceName({
name="Workspace name"
validationBehavior="aria"
isRequired
isDisabled={isArchived}
onChange={setName}
>
<Label>Workspace name</Label>
Expand All @@ -46,7 +49,11 @@ export function WorkspaceName({
</TextField>
</CardBody>
<CardFooter className="justify-end gap-2">
<Button isDisabled={name === ""} isPending={isPending} type="submit">
<Button
isDisabled={isArchived || name === ""}
isPending={isPending}
type="submit"
>
Save
</Button>
</CardFooter>
Expand Down
19 changes: 19 additions & 0 deletions src/features/workspace/hooks/use-archive-workspace-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Button } from "@stacklok/ui-kit";
import { ComponentProps } from "react";
import { useArchiveWorkspace } from "@/features/workspace-system-prompt/hooks/use-archive-workspace";

export function useArchiveWorkspaceButton({
workspaceName,
}: {
workspaceName: string;
}): ComponentProps<typeof Button> {
const { mutate, isPending } = useArchiveWorkspace();

return {
isPending,
isDisabled: isPending,
onPress: () => mutate({ path: { workspace_name: workspaceName } }),
isDestructive: true,
children: "Archive",
};
}
12 changes: 9 additions & 3 deletions src/features/workspace/hooks/use-archived-workspaces.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { useQuery } from "@tanstack/react-query";
import { v1ListArchivedWorkspacesOptions } from "@/api/generated/@tanstack/react-query.gen";
import { V1ListArchivedWorkspacesResponse } from "@/api/generated";

export const useArchivedWorkspaces = () => {
export function useArchivedWorkspaces<T = V1ListArchivedWorkspacesResponse>({
select,
}: {
select?: (data: V1ListArchivedWorkspacesResponse) => T;
} = {}) {
return useQuery({
...v1ListArchivedWorkspacesOptions(),
refetchInterval: 5_000,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 why was this changed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not manually, might have been a formatter/linter

refetchInterval: 5000,
refetchIntervalInBackground: true,
refetchOnMount: true,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
retry: false,
select,
});
};
}
12 changes: 9 additions & 3 deletions src/features/workspace/hooks/use-list-workspaces.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { useQuery } from "@tanstack/react-query";
import { v1ListWorkspacesOptions } from "@/api/generated/@tanstack/react-query.gen";
import { V1ListWorkspacesResponse } from "@/api/generated";

export const useListWorkspaces = () => {
export function useListWorkspaces<T = V1ListWorkspacesResponse>({
select,
}: {
select?: (data: V1ListWorkspacesResponse) => T;
} = {}) {
return useQuery({
...v1ListWorkspacesOptions(),
refetchInterval: 5_000,
refetchInterval: 5000,
refetchIntervalInBackground: true,
refetchOnMount: true,
refetchOnReconnect: true,
refetchOnWindowFocus: true,
retry: false,
select,
});
};
}
18 changes: 18 additions & 0 deletions src/features/workspace/hooks/use-restore-workspace-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Button } from "@stacklok/ui-kit";
import { ComponentProps } from "react";
import { useRestoreWorkspace } from "./use-restore-workspace";

export function useRestoreWorkspaceButton({
workspaceName,
}: {
workspaceName: string;
}): ComponentProps<typeof Button> {
const { mutate, isPending } = useRestoreWorkspace();

return {
isPending,
isDisabled: isPending,
onPress: () => mutate({ path: { workspace_name: workspaceName } }),
children: "Restore",
};
}
42 changes: 38 additions & 4 deletions src/routes/route-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,37 @@ import { ArchiveWorkspace } from "@/features/workspace/components/archive-worksp
import { SystemPromptEditor } from "@/features/workspace-system-prompt/components/system-prompt-editor";
import { WorkspaceHeading } from "@/features/workspace/components/workspace-heading";
import { WorkspaceName } from "@/features/workspace/components/workspace-name";
import { Breadcrumb, Breadcrumbs } from "@stacklok/ui-kit";
import { Alert, Breadcrumb, Breadcrumbs } from "@stacklok/ui-kit";
import { useParams } from "react-router-dom";
import { useArchivedWorkspaces } from "@/features/workspace/hooks/use-archived-workspaces";
import { useRestoreWorkspaceButton } from "@/features/workspace/hooks/use-restore-workspace-button";

function WorkspaceArchivedBanner({ name }: { name: string }) {
const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName: name });

return (
<Alert
variant="warning"
title="This workspace has been archived"
className="mb-8 animate-in fade-in zoom-in-95"
actionButtonProps={restoreButtonProps}
>
You can still view this workspace's configuration. To begin using it
again, you must restore it.
</Alert>
);
}

export function RouteWorkspace() {
const { name } = useParams();

if (!name) throw Error("Workspace name is required");

const { data: isArchived } = useArchivedWorkspaces<boolean>({
select: (data) =>
data?.workspaces.find((w) => w.name === name) !== undefined,
});

return (
<>
<Breadcrumbs>
Expand All @@ -20,9 +43,20 @@ export function RouteWorkspace() {
</Breadcrumbs>

<WorkspaceHeading title="Workspace settings" />
<WorkspaceName className="mb-4" workspaceName={name} />
<SystemPromptEditor workspaceName={name} className="mb-4" />
<ArchiveWorkspace workspaceName={name} />

{isArchived ? <WorkspaceArchivedBanner name={name} /> : null}

<WorkspaceName
isArchived={isArchived}
className="mb-4"
workspaceName={name}
/>
<SystemPromptEditor
isArchived={isArchived}
workspaceName={name}
className="mb-4"
/>
<ArchiveWorkspace isArchived={isArchived} workspaceName={name} />
</>
);
}
8 changes: 3 additions & 5 deletions src/routes/route-workspaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Settings, SquarePlus } from "lucide-react";
import { useArchivedWorkspaces } from "@/features/workspace/hooks/use-archived-workspaces";
import { Workspace } from "@/api/generated";
import SvgFlipBackward from "@/components/icons/FlipBackward";
import { useRestoreWorkspace } from "@/features/workspace/hooks/use-restore-workspace";
import { useRestoreWorkspaceButton } from "@/features/workspace/hooks/use-restore-workspace-button";

function CellName({
name,
Expand Down Expand Up @@ -48,17 +48,15 @@ function CellConfiguration({
name: string;
isArchived?: boolean;
}) {
const { mutate, isPending } = useRestoreWorkspace();
const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName: name });

if (isArchived) {
return (
<Cell>
<Button
variant="tertiary"
isPending={isPending}
isDisabled={isPending}
className="flex w-full gap-2 items-center"
onPress={() => mutate({ path: { workspace_name: name } })}
{...restoreButtonProps}
>
<SvgFlipBackward /> Restore Configuration
</Button>
Expand Down
Loading