From 43e4a11d5c8e2c9aee450627e9617cd5e1317de1 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 20 Jan 2025 16:36:03 +0100 Subject: [PATCH 1/8] feat: add create workspace --- src/App.tsx | 2 + .../workspace/components/workspace-create.tsx | 52 +++++++++++++++++++ .../workspace/hooks/use-create-workspace.ts | 14 +++++ 3 files changed, 68 insertions(+) create mode 100644 src/features/workspace/components/workspace-create.tsx create mode 100644 src/features/workspace/hooks/use-create-workspace.ts diff --git a/src/App.tsx b/src/App.tsx index 21153c5f..113ad39c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,7 @@ import { RouteHelp } from "./routes/route-help"; import { RouteChat } from "./routes/route-chat"; import { RouteDashboard } from "./routes/route-dashboard"; import { RouteCertificateSecurity } from "./routes/route-certificate-security"; +import { WorkspaceCreation } from "./features/workspace/components/workspace-create"; function App() { const { data: prompts, isLoading } = usePromptsData(); @@ -32,6 +33,7 @@ function App() { } /> } /> } /> + } /> } diff --git a/src/features/workspace/components/workspace-create.tsx b/src/features/workspace/components/workspace-create.tsx new file mode 100644 index 00000000..d9c73fd6 --- /dev/null +++ b/src/features/workspace/components/workspace-create.tsx @@ -0,0 +1,52 @@ +import { useCreateWorkspace } from "@/features/workspace/hooks/use-create-workspace"; +import { + Button, + Card, + CardBody, + CardFooter, + Heading, + Input, + Label, + LinkButton, + TextField, +} from "@stacklok/ui-kit"; +import { useState } from "react"; + +export function WorkspaceCreation() { + const [workspaceName, setWorkspaceName] = useState(""); + const { mutate, isPending, error } = useCreateWorkspace(); + const errorMsg = error?.detail ? `${error?.detail}` : ""; + + const handleCreateWorkspace = () => { + mutate({ body: { name: workspaceName } }); + }; + + return ( + <> + Create Workspace + + + + + + {errorMsg &&
{errorMsg}
} +
+
+ + Cancel + + +
+ + ); +} diff --git a/src/features/workspace/hooks/use-create-workspace.ts b/src/features/workspace/hooks/use-create-workspace.ts new file mode 100644 index 00000000..25d032f7 --- /dev/null +++ b/src/features/workspace/hooks/use-create-workspace.ts @@ -0,0 +1,14 @@ +import { useMutation } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; +import { v1CreateWorkspaceMutation } from "@/api/generated/@tanstack/react-query.gen"; + +export function useCreateWorkspace() { + const navigate = useNavigate(); + return useMutation({ + ...v1CreateWorkspaceMutation(), + onError(error) { + return error?.detail; + }, + onSuccess: () => navigate("/workspaces"), + }); +} From a577915d8b8255a238165eb2c43a7a38ebb17f94 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 20 Jan 2025 16:50:09 +0100 Subject: [PATCH 2/8] test: add workspace selection --- src/App.test.tsx | 42 +++++++++++++++---- .../components/workspaces-selection.tsx | 2 +- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index 83f745c4..41abf8f9 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -3,6 +3,7 @@ import { screen, waitFor } from "@testing-library/react"; import { describe, expect, it, vi } from "vitest"; import App from "./App"; import React from "react"; +import userEvent from "@testing-library/user-event"; vi.mock("recharts", async (importOriginal) => { const originalModule = (await importOriginal()) as Record; @@ -22,38 +23,63 @@ describe("App", () => { expect(screen.getByText("Setup")).toBeVisible(); expect(screen.getByRole("banner", { name: "App header" })).toBeVisible(); expect( - screen.getByRole("heading", { name: /codeGate dashboard/i }), + screen.getByRole("heading", { name: /codeGate dashboard/i }) ).toBeVisible(); expect( screen.getByRole("link", { name: /certificate security/i, - }), + }) ).toBeVisible(); expect( screen.getByRole("link", { name: /set up in continue/i, - }), + }) ).toBeVisible(); expect( screen.getByRole("link", { name: /set up in copilot/i, - }), + }) ).toBeVisible(); expect( screen.getByRole("link", { name: /download/i, - }), + }) ).toBeVisible(); expect( screen.getByRole("link", { name: /documentation/i, - }), + }) ).toBeVisible(); await waitFor(() => expect( - screen.getByRole("link", { name: /codeGate dashboard/i }), - ).toBeVisible(), + screen.getByRole("link", { name: /codeGate dashboard/i }) + ).toBeVisible() + ); + }); + + it("should render workspaces dropdown", async () => { + render(); + + await waitFor(() => + expect( + screen.getByRole("link", { name: "CodeGate Dashboard" }) + ).toBeVisible() + ); + + const workspaceSelectionButton = screen.getByRole("button", { + name: "Workspace default", + }); + await waitFor(() => expect(workspaceSelectionButton).toBeVisible()); + + await userEvent.click(workspaceSelectionButton); + + await waitFor(() => + expect( + screen.getByRole("option", { + name: /anotherworkspae/i, + }) + ).toBeVisible() ); }); }); diff --git a/src/features/workspace/components/workspaces-selection.tsx b/src/features/workspace/components/workspaces-selection.tsx index aab1971e..58a50269 100644 --- a/src/features/workspace/components/workspaces-selection.tsx +++ b/src/features/workspace/components/workspaces-selection.tsx @@ -25,7 +25,7 @@ export function WorkspacesSelection() { const { mutateAsync: activateWorkspace } = useActivateWorkspace(); const activeWorkspaceName: string | null = - activeWorkspacesResponse?.workspaces[0]?.name ?? null; + activeWorkspacesResponse?.workspaces?.[0]?.name ?? null; const [isOpen, setIsOpen] = useState(false); const [searchWorkspace, setSearchWorkspace] = useState(""); From 94c090ec0f8a3fe9cf50db8eb41be82ace57bff6 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 20 Jan 2025 16:51:45 +0100 Subject: [PATCH 3/8] fix: rename files --- src/App.tsx | 2 +- .../components/{workspace-create.tsx => workspace-creation.tsx} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/features/workspace/components/{workspace-create.tsx => workspace-creation.tsx} (100%) diff --git a/src/App.tsx b/src/App.tsx index 113ad39c..66e56194 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,7 @@ import { RouteHelp } from "./routes/route-help"; import { RouteChat } from "./routes/route-chat"; import { RouteDashboard } from "./routes/route-dashboard"; import { RouteCertificateSecurity } from "./routes/route-certificate-security"; -import { WorkspaceCreation } from "./features/workspace/components/workspace-create"; +import { WorkspaceCreation } from "./features/workspace/components/workspace-creation"; function App() { const { data: prompts, isLoading } = usePromptsData(); diff --git a/src/features/workspace/components/workspace-create.tsx b/src/features/workspace/components/workspace-creation.tsx similarity index 100% rename from src/features/workspace/components/workspace-create.tsx rename to src/features/workspace/components/workspace-creation.tsx From 1619ceef3ed89a4f1db7242799dcf9ffc179ee02 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 20 Jan 2025 17:13:37 +0100 Subject: [PATCH 4/8] refactor: create workflow heading component --- src/App.tsx | 7 ++- .../components/workspace-creation.tsx | 50 +++++++++---------- .../components/workspace-heading.tsx | 9 ++++ src/routes/route-workspace-creation.tsx | 11 ++++ src/routes/route-workspace.tsx | 5 +- src/routes/route-workspaces.tsx | 7 +-- 6 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 src/features/workspace/components/workspace-heading.tsx create mode 100644 src/routes/route-workspace-creation.tsx diff --git a/src/App.tsx b/src/App.tsx index 66e56194..ef8d5cb4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,7 @@ import { RouteHelp } from "./routes/route-help"; import { RouteChat } from "./routes/route-chat"; import { RouteDashboard } from "./routes/route-dashboard"; import { RouteCertificateSecurity } from "./routes/route-certificate-security"; -import { WorkspaceCreation } from "./features/workspace/components/workspace-creation"; +import { RouteWorkspaceCreation } from "./routes/route-workspace-creation"; function App() { const { data: prompts, isLoading } = usePromptsData(); @@ -33,7 +33,10 @@ function App() { } /> } /> } /> - } /> + } + /> } diff --git a/src/features/workspace/components/workspace-creation.tsx b/src/features/workspace/components/workspace-creation.tsx index d9c73fd6..150353af 100644 --- a/src/features/workspace/components/workspace-creation.tsx +++ b/src/features/workspace/components/workspace-creation.tsx @@ -4,7 +4,6 @@ import { Card, CardBody, CardFooter, - Heading, Input, Label, LinkButton, @@ -22,31 +21,28 @@ export function WorkspaceCreation() { }; return ( - <> - Create Workspace - - - - - - {errorMsg &&
{errorMsg}
} -
-
- - Cancel - - -
- + + + + + + {errorMsg &&
{errorMsg}
} +
+
+ + Cancel + + +
); } diff --git a/src/features/workspace/components/workspace-heading.tsx b/src/features/workspace/components/workspace-heading.tsx new file mode 100644 index 00000000..07c2f6e2 --- /dev/null +++ b/src/features/workspace/components/workspace-heading.tsx @@ -0,0 +1,9 @@ +import { Heading } from "@stacklok/ui-kit"; + +export function WorkspaceHeading({ title }: { title: string }) { + return ( + + {title} + + ); +} diff --git a/src/routes/route-workspace-creation.tsx b/src/routes/route-workspace-creation.tsx new file mode 100644 index 00000000..fc187fef --- /dev/null +++ b/src/routes/route-workspace-creation.tsx @@ -0,0 +1,11 @@ +import { WorkspaceCreation } from "@/features/workspace/components/workspace-creation"; +import { WorkspaceHeading } from "@/features/workspace/components/workspace-heading"; + +export function RouteWorkspaceCreation() { + return ( + <> + + + + ); +} diff --git a/src/routes/route-workspace.tsx b/src/routes/route-workspace.tsx index db68a247..c9f0c4a2 100644 --- a/src/routes/route-workspace.tsx +++ b/src/routes/route-workspace.tsx @@ -1,7 +1,8 @@ import { BreadcrumbHome } from "@/components/BreadcrumbHome"; 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, Heading } from "@stacklok/ui-kit"; +import { Breadcrumb, Breadcrumbs } from "@stacklok/ui-kit"; export function RouteWorkspace() { return ( @@ -12,7 +13,7 @@ export function RouteWorkspace() { Workspace Settings - Workspace settings + diff --git a/src/routes/route-workspaces.tsx b/src/routes/route-workspaces.tsx index 05612a73..eb2aa47b 100644 --- a/src/routes/route-workspaces.tsx +++ b/src/routes/route-workspaces.tsx @@ -1,3 +1,4 @@ +import { WorkspaceHeading } from "@/features/workspace/components/workspace-heading"; import { useListWorkspaces } from "@/features/workspace/hooks/use-list-workspaces"; import { BreadcrumbHome } from "@/components/BreadcrumbHome"; import { @@ -5,7 +6,6 @@ import { Breadcrumbs, Cell, Column, - Heading, LinkButton, Row, Table, @@ -25,10 +25,7 @@ export function RouteWorkspaces() { Manage Workspaces - - Manage Workspaces - - + From 39f31466f965f3482034830456f2231706b82c1a Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 20 Jan 2025 17:14:16 +0100 Subject: [PATCH 5/8] test: add post workflow msw handler --- src/mocks/msw/handlers.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mocks/msw/handlers.ts b/src/mocks/msw/handlers.ts index dbefe629..49ff76f7 100644 --- a/src/mocks/msw/handlers.ts +++ b/src/mocks/msw/handlers.ts @@ -33,4 +33,7 @@ export const handlers = [ http.get("*/api/v1/workspaces", () => { return HttpResponse.json(mockedWorkspaces); }), + http.post("*/api/v1/workspace", () => { + return HttpResponse.json(mockedWorkspaces); + }), ]; From 42ead6aa2c0803c92e9e2bc1d7addfc84d6660eb Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 20 Jan 2025 18:04:40 +0100 Subject: [PATCH 6/8] test: add workspace creation --- .../__tests__/workspace-creation.test.tsx | 27 +++++++++++++++++++ .../workspace/hooks/use-create-workspace.ts | 3 --- src/mocks/msw/handlers.ts | 2 +- src/routes/route-workspace-creation.tsx | 2 +- 4 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 src/features/workspace/components/__tests__/workspace-creation.test.tsx diff --git a/src/features/workspace/components/__tests__/workspace-creation.test.tsx b/src/features/workspace/components/__tests__/workspace-creation.test.tsx new file mode 100644 index 00000000..58d061e5 --- /dev/null +++ b/src/features/workspace/components/__tests__/workspace-creation.test.tsx @@ -0,0 +1,27 @@ +import { WorkspaceCreation } from "../workspace-creation"; +import { render } from "@/lib/test-utils"; +import userEvent from "@testing-library/user-event"; +import { screen, waitFor } from "@testing-library/react"; + +const mockNavigate = vi.fn(); +vi.mock("react-router-dom", async () => { + const original = + await vi.importActual( + "react-router-dom", + ); + return { + ...original, + useNavigate: () => mockNavigate, + }; +}); + +test("create workspace", async () => { + render(); + + expect(screen.getByText(/name/i)).toBeVisible(); + + screen.logTestingPlaygroundURL(); + await userEvent.type(screen.getByRole("textbox"), "workspaceA"); + await userEvent.click(screen.getByRole("button", { name: /create/i })); + await waitFor(() => expect(mockNavigate).toBeCalled()); +}); diff --git a/src/features/workspace/hooks/use-create-workspace.ts b/src/features/workspace/hooks/use-create-workspace.ts index 25d032f7..e5eacfa3 100644 --- a/src/features/workspace/hooks/use-create-workspace.ts +++ b/src/features/workspace/hooks/use-create-workspace.ts @@ -6,9 +6,6 @@ export function useCreateWorkspace() { const navigate = useNavigate(); return useMutation({ ...v1CreateWorkspaceMutation(), - onError(error) { - return error?.detail; - }, onSuccess: () => navigate("/workspaces"), }); } diff --git a/src/mocks/msw/handlers.ts b/src/mocks/msw/handlers.ts index 49ff76f7..a0b5db3b 100644 --- a/src/mocks/msw/handlers.ts +++ b/src/mocks/msw/handlers.ts @@ -33,7 +33,7 @@ export const handlers = [ http.get("*/api/v1/workspaces", () => { return HttpResponse.json(mockedWorkspaces); }), - http.post("*/api/v1/workspace", () => { + http.post("*/api/v1/workspaces", () => { return HttpResponse.json(mockedWorkspaces); }), ]; diff --git a/src/routes/route-workspace-creation.tsx b/src/routes/route-workspace-creation.tsx index fc187fef..3140a666 100644 --- a/src/routes/route-workspace-creation.tsx +++ b/src/routes/route-workspace-creation.tsx @@ -4,7 +4,7 @@ import { WorkspaceHeading } from "@/features/workspace/components/workspace-head export function RouteWorkspaceCreation() { return ( <> - + ); From e3ebaab2037edd99ea04cff52d74e5b21ca4c3f0 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 20 Jan 2025 18:15:49 +0100 Subject: [PATCH 7/8] feat: add link button to workspace creation --- .../workspace/components/workspace-heading.tsx | 12 ++++++++++-- src/routes/route-workspace-creation.tsx | 7 +++++++ src/routes/route-workspaces.tsx | 9 +++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/features/workspace/components/workspace-heading.tsx b/src/features/workspace/components/workspace-heading.tsx index 07c2f6e2..880f9959 100644 --- a/src/features/workspace/components/workspace-heading.tsx +++ b/src/features/workspace/components/workspace-heading.tsx @@ -1,9 +1,17 @@ import { Heading } from "@stacklok/ui-kit"; +import React from "react"; -export function WorkspaceHeading({ title }: { title: string }) { +export function WorkspaceHeading({ + title, + children, +}: { + title: string; + children?: React.ReactNode; +}) { return ( - + {title} + {children} ); } diff --git a/src/routes/route-workspace-creation.tsx b/src/routes/route-workspace-creation.tsx index 3140a666..31ae5ef6 100644 --- a/src/routes/route-workspace-creation.tsx +++ b/src/routes/route-workspace-creation.tsx @@ -1,9 +1,16 @@ +import { BreadcrumbHome } from "@/components/BreadcrumbHome"; import { WorkspaceCreation } from "@/features/workspace/components/workspace-creation"; import { WorkspaceHeading } from "@/features/workspace/components/workspace-heading"; +import { Breadcrumbs, Breadcrumb } from "@stacklok/ui-kit"; export function RouteWorkspaceCreation() { return ( <> + + + Create Workspace + + diff --git a/src/routes/route-workspaces.tsx b/src/routes/route-workspaces.tsx index eb2aa47b..7fddcc37 100644 --- a/src/routes/route-workspaces.tsx +++ b/src/routes/route-workspaces.tsx @@ -12,7 +12,7 @@ import { TableBody, TableHeader, } from "@stacklok/ui-kit"; -import { Settings } from "lucide-react"; +import { Settings, SquarePlus } from "lucide-react"; export function RouteWorkspaces() { const result = useListWorkspaces(); @@ -25,7 +25,12 @@ export function RouteWorkspaces() { Manage Workspaces - + + + Create Workspace + + +
From 37e10f3b43caed4fc7ef7e7e2c7f2e133eff0c8c Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Mon, 20 Jan 2025 18:16:37 +0100 Subject: [PATCH 8/8] test: update heading level --- src/routes/__tests__/route-workspace.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/__tests__/route-workspace.test.tsx b/src/routes/__tests__/route-workspace.test.tsx index 5d7a301a..30235726 100644 --- a/src/routes/__tests__/route-workspace.test.tsx +++ b/src/routes/__tests__/route-workspace.test.tsx @@ -22,7 +22,7 @@ test("renders title", () => { const { getByRole } = renderComponent(); expect( - getByRole("heading", { name: "Workspace settings", level: 1 }), + getByRole("heading", { name: "Workspace settings", level: 4 }), ).toBeVisible(); });