diff --git a/frontend/src/shell/WorkspaceManager.test.tsx b/frontend/src/shell/WorkspaceManager.test.tsx index c318f4e3..5accabb8 100644 --- a/frontend/src/shell/WorkspaceManager.test.tsx +++ b/frontend/src/shell/WorkspaceManager.test.tsx @@ -169,7 +169,7 @@ test("offers a button above the workspace list that returns to Level 1", async ( expect(screen.queryByRole("heading", { name: "Workspace-specific actions" })).not.toBeInTheDocument(); }); -test("level one explains the read-only Git sequence and the repository update button", async () => { +test("level one explains workspace files, shared repositories, and the read-only Git flow", async () => { const user = userEvent.setup(); server.use(http.post("/api/workspace-registry/pull", () => HttpResponse.json({ branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false, @@ -178,30 +178,56 @@ test("level one explains the read-only Git sequence and the repository update bu expect(await screen.findByRole("heading", { name: "Workspace management" })).toBeVisible(); const overview = screen.getByTestId("workspace-overview"); - expect(within(overview).getByText(/Git server such as GitHub, GitLab, or Gitea/i)).toBeVisible(); - expect(within(overview).getByText(/configured during ThothII installation/i)).toBeVisible(); - const repositoryStep = within(overview).getAllByRole("listitem")[0]; - expect(repositoryStep).toHaveTextContent(/create a workspace repository/i); - expect(repositoryStep).toHaveTextContent(/one directory for each workspace/i); - expect(repositoryStep).toHaveTextContent(/thoth-workspaces\.yaml/i); - expect(repositoryStep).toHaveTextContent(/database connection/i); - expect(repositoryStep).toHaveTextContent(/Evidence sources/i); - expect(repositoryStep).toHaveTextContent(/vector-database collection/i); + expect(within(overview).getByRole("heading", { name: "What workspace files tell ThothII" })).toBeVisible(); + expect(within(overview).getByText(/A workspace file describes one data environment/i)).toBeVisible(); + expect(within(overview).getByText(/one Git repository can contain many workspaces/i)).toBeVisible(); + expect(within(overview).getByText(/each workspace has its own directory/i)).toHaveTextContent(/database target/i); + expect(within(overview).getByText(/each workspace has its own directory/i)).toHaveTextContent(/Evidence/i); + const repositorySteps = within(overview).getByRole("list"); + expect(repositorySteps).toHaveClass("gap-0"); + expect(within(repositorySteps).getAllByRole("listitem")).toHaveLength(3); expect(within(overview).getByRole("link", { name: /workspace authoring instructions on GitHub/i })).toHaveAttribute( "href", "https://github.com/mptyl/ThothII/blob/main/docs/install/local-workspace-registry.md#prepare-and-publish-a-workspace-source", ); + const repositoryLink = await within(overview).findByRole("link", { + name: "git.example.test/analytics/thoth-workspaces", + }); + expect(repositoryLink).toHaveAttribute("href", "https://git.example.test/analytics/thoth-workspaces"); + expect(repositoryLink).toHaveAttribute("target", "_blank"); + expect(repositoryLink).toHaveAttribute("rel", "noreferrer"); + expect(within(overview).getByText(/configured during ThothII installation/i)).toBeVisible(); expect(within(overview).getAllByText(/managed read-only checkout/i)).toHaveLength(2); expect(within(overview).getByText(/current active revision remains unchanged/i)).toBeVisible(); expect(within(overview).getByText(/No workspace selection is required/i)).toBeVisible(); - expect(await within(overview).findByText("git.example.test/analytics/thoth-workspaces")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Update workspace repository" })); expect(await screen.findByText("Workspace repository updated and validated.")).toBeVisible(); - expect(screen.getByText(/create a workspace repository/i)).toBeInTheDocument(); + expect(screen.getByText(/A workspace file describes one data environment/i)).toBeInTheDocument(); + expect(screen.queryByText(/create a workspace repository/i)).not.toBeInTheDocument(); expect(screen.queryByText(/import|export|bundle/i)).not.toBeInTheDocument(); }); +test("shows a local workspace repository as text instead of an invalid web link", async () => { + server.use(http.get("/api/workspace-registry/status", () => HttpResponse.json({ + branch: "main", + head: "a".repeat(40), + ahead: 0, + behind: 0, + degraded: false, + repository: { + host: "local", + repository: "configured-repository", + transport: "local", + }, + }))); + renderManager(); + + const overview = await screen.findByTestId("workspace-overview"); + expect(await within(overview).findByText("local/configured-repository")).toBeVisible(); + expect(within(overview).queryByRole("link", { name: "local/configured-repository" })).not.toBeInTheDocument(); +}); + test("workspace-specific commands remain isolated until a workspace is selected", async () => { const user = userEvent.setup(); renderManager(); @@ -209,10 +235,15 @@ test("workspace-specific commands remain isolated until a workspace is selected" expect(screen.queryByRole("heading", { name: "Workspace-specific actions" })).not.toBeInTheDocument(); await user.click(await screen.findByRole("button", { name: "PSD Clinical" })); + expect(await screen.findByRole("heading", { name: "About this workspace" })).toBeVisible(); expect(await screen.findByRole("heading", { name: "Workspace-specific actions" })).toBeVisible(); - expect(screen.getByText(/reads this revision without modifying or publishing it/i)).toBeVisible(); - expect(screen.getByText(/checks workspace.yaml and the required workspace directories/i)).toBeVisible(); - expect(screen.getByText(/temporary decrypted credentials/i)).toBeVisible(); + expect(screen.getByText(/one workspace in the shared repository/i)).toHaveTextContent(/workspace.yaml/i); + expect(screen.getByText(/one workspace in the shared repository/i)).toHaveTextContent(/Evidence/i); + expect(screen.getByText(/exact version currently activated by ThothII/i)).toBeVisible(); + expect(screen.getByText(/does not modify the repository/i)).toBeVisible(); + expect(screen.getByText(/confirms that workspace.yaml and the directories/i)).toBeVisible(); + expect(screen.getByText(/reach the data warehouse and Evidence sources/i)).toBeVisible(); + expect(screen.getByText(/credentials only for the duration of the test/i)).toBeVisible(); const databaseField = screen.getByText("Database").parentElement; expect(databaseField).not.toBeNull(); expect(databaseField).toHaveTextContent("engine: postgres"); diff --git a/frontend/src/shell/WorkspaceManager.tsx b/frontend/src/shell/WorkspaceManager.tsx index c0678f42..61aa266a 100644 --- a/frontend/src/shell/WorkspaceManager.tsx +++ b/frontend/src/shell/WorkspaceManager.tsx @@ -386,6 +386,9 @@ export function WorkspaceManager({ const repositoryLabel = repository ? `${repository.host}/${repository.repository}` : "the repository configured for this ThothII installation"; + const repositoryWebUrl = repository && repository.transport !== "local" + ? `https://${repository.host}/${repository.repository}` + : undefined; const runtime = runtimeQuery.data; const hasEnteredSecrets = Object.values(secretValues).some((value) => value.length > 0); @@ -459,16 +462,34 @@ export function WorkspaceManager({

Level 1 ยท Repository

-

How workspaces reach ThothII

+

What workspace files tell ThothII

+
+
+

+ A workspace file describes one data environment. Its workspace.yaml gives ThothII the workspace identity and language, the database target and schema it may query, the Evidence sources that support analysis, and the processing settings to apply. Runtime credentials are stored separately from Git. +

+

+ One Git repository can contain many workspaces. At its root, thoth-workspaces.yaml lists them, and each workspace has its own directory, workspace.yaml, database target and access method, and Evidence sources. This keeps separate data environments and supporting material distinct while versioning them together. +

+
+
+

How this installation uses the repository

+
    +
  1. + The repository address, branch, and read-only Git credentials are configured during ThothII installation. This installation reads{" "} + {repositoryWebUrl ? ( + {repositoryLabel} + ) : ( + {repositoryLabel} + )}{" "} + on branch {statusQuery.data?.branch ?? "main"}. +
  2. +
  3. ThothII fetches the configured branch into its managed read-only checkout, reads the workspaces listed in the repository, and validates the complete candidate revision before activating it.
  4. +
  5. The active revision stays read-only. ThothII never edits, commits, pushes, or publishes workspace files; if validation fails, the previous valid revision stays active.
  6. +
-
    -
  1. Create a workspace repository in any local directory you choose. Add one directory for each workspace you want ThothII to manage. At the repository root, thoth-workspaces.yaml lists those workspaces; each workspace directory contains its own workspace.yaml, which declares the database connection, the Evidence sources, and the ThothII vector-database collection used during the process.
  2. -
  3. Publish that source by committing and pushing it to a repository hosted by a Git server such as GitHub, GitLab, or Gitea.
  4. -
  5. The repository address, branch, and read-only Git credentials are configured during ThothII installation. This installation reads {repositoryLabel} on branch {statusQuery.data?.branch ?? "main"}.
  6. -
  7. ThothII fetches the configured branch into its managed read-only checkout, validates the complete candidate revision, and activates it only when validation succeeds. It never edits, commits, pushes, or publishes workspace source.
  8. -

- Follow the workspace authoring instructions on GitHub for the required layout and validation rules. + The workspace authoring instructions on GitHub explain the required layout and validation rules for people who maintain the repository.

{canManageWorkspace &&
@@ -486,7 +507,7 @@ export function WorkspaceManager({ You can inspect workspaces. Workspace updates, validation, and connection tests require workspace management permission.

)} -

Select a workspace from the left only for workspace-specific validation, runtime credentials, and connection tests.

+

Selecting a workspace from the left opens its own revision, database and Evidence configuration, runtime requirements, validation, and connection checks.

) : (
@@ -503,9 +524,14 @@ export function WorkspaceManager({ {detailQuery.data && runtime && ( <> -
-

Workspace-specific actions

-

The actions below apply only to {selectedSummary.displayName}. ThothII reads this revision without modifying or publishing it.

+
+

About this workspace

+

+ {selectedSummary.displayName} is one workspace in the shared repository. Its workspace.yaml defines the database target, processing settings, and Evidence sources for this data environment. Other workspaces in the same repository can use different databases and keep their Evidence separate. +

+

+ The active revision shown below is the exact version currently activated by ThothII. Reading or checking it does not modify the repository. Source changes are made in Git and become available after a new revision is fetched, validated, and activated. +

@@ -522,10 +548,15 @@ export function WorkspaceManager({
Runtime status
{stateLabel(runtime.configurationState)}
+
+

Workspace-specific actions

+

The validation and connection checks below apply only to {selectedSummary.displayName} and read its active revision without changing or publishing it.

+
+

Validate workspace source

-

Checks workspace.yaml and the required workspace directories against the supported workspace schema. No source file is changed.

+

Confirms that workspace.yaml and the directories belonging to this workspace follow the supported format. This identifies source problems before activation, without changing repository files.

{validationNotice && (

{validationNotice} @@ -546,7 +577,7 @@ export function WorkspaceManager({

Test workspace connections

-

Uses temporary decrypted credentials to verify the configured data warehouse and Evidence source. Temporary files are deleted after the test.

+

Verifies that ThothII can reach the data warehouse and Evidence sources configured for this workspace. It decrypts credentials only for the duration of the test and deletes temporary files when the check finishes.

{connectionNotice && (

{connectionNotice} @@ -589,7 +620,7 @@ export function WorkspaceManager({

Runtime secrets

-

Enter only new or replacement values. Stored values are never displayed. Saving replaces the selected secret and clears the form field.

+

Some connectors need credentials at runtime. ThothII stores these values separately from Git and never displays stored values. Enter a value only when adding or replacing a credential for this workspace.

{runtime.requirements.length === 0 ? (