diff --git a/src/features/workspace/components/__tests__/archive-workspace.test.tsx b/src/features/workspace/components/__tests__/archive-workspace.test.tsx
index a66c1417..f27d6183 100644
--- a/src/features/workspace/components/__tests__/archive-workspace.test.tsx
+++ b/src/features/workspace/components/__tests__/archive-workspace.test.tsx
@@ -2,13 +2,16 @@ import { render } from "@/lib/test-utils";
import { ArchiveWorkspace } from "../archive-workspace";
import userEvent from "@testing-library/user-event";
import { waitFor } from "@testing-library/react";
+import { server } from "@/mocks/msw/node";
+import { http, HttpResponse } from "msw";
test("has correct buttons when not archived", async () => {
- const { getByRole } = render(
+ const { getByRole, queryByRole } = render(
,
);
expect(getByRole("button", { name: /archive/i })).toBeVisible();
+ expect(queryByRole("button", { name: /contextual help/i })).toBe(null);
});
test("has correct buttons when archived", async () => {
@@ -60,3 +63,38 @@ test("can permanently delete archived workspace", async () => {
expect(getByText(/permanently deleted "foo-bar" workspace/i)).toBeVisible();
});
});
+
+test("can't archive active workspace", async () => {
+ server.use(
+ http.get("*/api/v1/workspaces/active", () =>
+ HttpResponse.json({
+ workspaces: [
+ {
+ name: "foo",
+ is_active: true,
+ last_updated: new Date(Date.now()).toISOString(),
+ },
+ ],
+ }),
+ ),
+ );
+ const { getByRole } = render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(getByRole("button", { name: /archive/i })).toBeDisabled();
+ expect(getByRole("button", { name: /contextual help/i })).toBeVisible();
+ });
+});
+
+test("can't archive default workspace", async () => {
+ const { getByRole } = render(
+ ,
+ );
+
+ await waitFor(() => {
+ expect(getByRole("button", { name: /archive/i })).toBeDisabled();
+ expect(getByRole("button", { name: /contextual help/i })).toBeVisible();
+ });
+});
diff --git a/src/features/workspace/components/__tests__/workspace-name.test.tsx b/src/features/workspace/components/__tests__/workspace-name.test.tsx
index 2d1ba447..d92b25b4 100644
--- a/src/features/workspace/components/__tests__/workspace-name.test.tsx
+++ b/src/features/workspace/components/__tests__/workspace-name.test.tsx
@@ -2,6 +2,8 @@ import { test, expect } from "vitest";
import { WorkspaceName } from "../workspace-name";
import { render, waitFor } from "@/lib/test-utils";
import userEvent from "@testing-library/user-event";
+import { server } from "@/mocks/msw/node";
+import { http, HttpResponse } from "msw";
test("can rename workspace", async () => {
const { getByRole, getByText } = render(
@@ -29,3 +31,34 @@ test("can't rename archived workspace", async () => {
expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
expect(getByRole("button", { name: /save/i })).toBeDisabled();
});
+
+test("can't rename active workspace", async () => {
+ server.use(
+ http.get("*/api/v1/workspaces/active", () =>
+ HttpResponse.json({
+ workspaces: [
+ {
+ name: "foo",
+ is_active: true,
+ last_updated: new Date(Date.now()).toISOString(),
+ },
+ ],
+ }),
+ ),
+ );
+ const { getByRole } = render(
+ ,
+ );
+
+ expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
+ expect(getByRole("button", { name: /save/i })).toBeDisabled();
+});
+
+test("can't rename default workspace", async () => {
+ const { getByRole } = render(
+ ,
+ );
+
+ expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled();
+ expect(getByRole("button", { name: /save/i })).toBeDisabled();
+});
diff --git a/src/features/workspace/components/archive-workspace.tsx b/src/features/workspace/components/archive-workspace.tsx
index a599f847..aedb2fbf 100644
--- a/src/features/workspace/components/archive-workspace.tsx
+++ b/src/features/workspace/components/archive-workspace.tsx
@@ -1,15 +1,62 @@
-import { Card, CardBody, Button, Text } from "@stacklok/ui-kit";
+import {
+ Card,
+ CardBody,
+ Button,
+ Text,
+ TooltipTrigger,
+ Tooltip,
+ TooltipInfoButton,
+} from "@stacklok/ui-kit";
import { twMerge } from "tailwind-merge";
import { useRestoreWorkspaceButton } from "../hooks/use-restore-workspace-button";
import { useArchiveWorkspaceButton } from "../hooks/use-archive-workspace-button";
import { useConfirmHardDeleteWorkspace } from "../hooks/use-confirm-hard-delete-workspace";
import { useNavigate } from "react-router-dom";
import { hrefs } from "@/lib/hrefs";
+import { useActiveWorkspaceName } from "../hooks/use-active-workspace-name";
+
+function getContextualText({
+ activeWorkspaceName,
+ workspaceName,
+}: {
+ workspaceName: string;
+ activeWorkspaceName: string;
+}) {
+ if (workspaceName === activeWorkspaceName) {
+ return "Cannot archive the active workspace";
+ }
+ if (workspaceName === "default") {
+ return "Cannot archive the default workspace";
+ }
+ return null;
+}
+
+// NOTE: You can't show a tooltip on a disabled button
+// React Aria's recommended approach is https://spectrum.adobe.com/page/contextual-help/
+function ContextualHelp({ workspaceName }: { workspaceName: string }) {
+ const { data: activeWorkspaceName } = useActiveWorkspaceName();
+ if (!activeWorkspaceName) return null;
+
+ const text = getContextualText({ activeWorkspaceName, workspaceName });
+ if (!text) return null;
+
+ return (
+
+
+ {text}
+
+ );
+}
const ButtonsUnarchived = ({ workspaceName }: { workspaceName: string }) => {
const archiveButtonProps = useArchiveWorkspaceButton({ workspaceName });
- return ;
+ return (
+
+
+
+
+ );
};
const ButtonsArchived = ({ workspaceName }: { workspaceName: string }) => {
@@ -51,7 +98,7 @@ export function ArchiveWorkspace({
Archive Workspace
-
+
Archiving this workspace removes it from the main workspaces list,
though it can be restored if needed.
diff --git a/src/features/workspace/components/workspace-custom-instructions.tsx b/src/features/workspace/components/workspace-custom-instructions.tsx
index 2d878842..d2387a8b 100644
--- a/src/features/workspace/components/workspace-custom-instructions.tsx
+++ b/src/features/workspace/components/workspace-custom-instructions.tsx
@@ -186,8 +186,8 @@ export function WorkspaceCustomInstructions({
Custom instructions
- Pass custom instructions to your LLM to augment it's behavior, and
- save time & tokens.
+ Pass custom instructions to your LLM to augment its behavior, and save
+ time & tokens.
{isCustomInstructionsPending ? (
diff --git a/src/features/workspace/hooks/use-archive-workspace-button.tsx b/src/features/workspace/hooks/use-archive-workspace-button.tsx
index cc8e0ebe..e6782ed4 100644
--- a/src/features/workspace/hooks/use-archive-workspace-button.tsx
+++ b/src/features/workspace/hooks/use-archive-workspace-button.tsx
@@ -1,17 +1,22 @@
import { Button } from "@stacklok/ui-kit";
import { ComponentProps } from "react";
import { useMutationArchiveWorkspace } from "@/features/workspace/hooks/use-mutation-archive-workspace";
+import { useActiveWorkspaceName } from "./use-active-workspace-name";
export function useArchiveWorkspaceButton({
workspaceName,
}: {
workspaceName: string;
}): ComponentProps
{
+ const { data: activeWorkspaceName } = useActiveWorkspaceName();
const { mutateAsync, isPending } = useMutationArchiveWorkspace();
return {
isPending,
- isDisabled: isPending,
+ isDisabled:
+ isPending ||
+ workspaceName === activeWorkspaceName ||
+ workspaceName === "default",
onPress: () => mutateAsync({ path: { workspace_name: workspaceName } }),
isDestructive: true,
children: "Archive",
diff --git a/src/features/workspace/hooks/use-confirm-hard-delete-workspace.tsx b/src/features/workspace/hooks/use-confirm-hard-delete-workspace.tsx
index 120eec16..74e6350a 100644
--- a/src/features/workspace/hooks/use-confirm-hard-delete-workspace.tsx
+++ b/src/features/workspace/hooks/use-confirm-hard-delete-workspace.tsx
@@ -11,10 +11,12 @@ export function useConfirmHardDeleteWorkspace() {
async (...params: Parameters) => {
const answer = await confirm(
<>
- Are you sure you want to delete this workspace?
+
+ Are you sure you want to permanently delete this workspace?
+
- You will lose any custom instructions, or other configuration.{" "}
- This action cannot be undone.
+ You will lose all configuration and data associated with this
+ workspace, like prompt history or alerts.
>,
{