Skip to content

Commit c85e1aa

Browse files
feat: add activate workspace button into detail page (#328)
Co-authored-by: Alex McGovern <[email protected]>
1 parent 35aea9c commit c85e1aa

File tree

3 files changed

+151
-10
lines changed

3 files changed

+151
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useMutationActivateWorkspace } from '@/hooks/use-mutation-activate-workspace'
2+
import { useQueryActiveWorkspaceName } from '@/hooks/use-query-active-workspace-name'
3+
import {
4+
Button,
5+
Tooltip,
6+
TooltipInfoButton,
7+
TooltipTrigger,
8+
} from '@stacklok/ui-kit'
9+
import { Check } from '@untitled-ui/icons-react'
10+
11+
function getTooltipText({
12+
isActivated,
13+
isArchived,
14+
}: {
15+
isArchived: boolean
16+
isActivated: boolean
17+
}) {
18+
if (isArchived) {
19+
return 'Cannot activate an archived workspace'
20+
}
21+
22+
if (isActivated) {
23+
return 'Workspace already active'
24+
}
25+
26+
return null
27+
}
28+
29+
function TooltipActivateBtn({
30+
isActivated,
31+
isArchived,
32+
}: {
33+
isActivated: boolean
34+
isArchived: boolean
35+
}) {
36+
const text = getTooltipText({ isActivated, isArchived })
37+
38+
if (!text) return null
39+
return (
40+
<TooltipTrigger delay={0}>
41+
<TooltipInfoButton aria-label="Context active button" />
42+
<Tooltip>{text}</Tooltip>
43+
</TooltipTrigger>
44+
)
45+
}
46+
47+
export function WorkspaceActivateButton({
48+
workspaceName,
49+
isArchived,
50+
}: {
51+
workspaceName: string
52+
isArchived: boolean | undefined
53+
}) {
54+
const { data: activeWorkspaceName, isPending: isPendingWsName } =
55+
useQueryActiveWorkspaceName()
56+
const { mutateAsync: activateWorkspace, isPending: isPendingMutation } =
57+
useMutationActivateWorkspace()
58+
const isActivated = activeWorkspaceName === workspaceName
59+
const isPending = isPendingWsName || isPendingMutation
60+
61+
return (
62+
<div
63+
className="flex items-center justify-end gap-2"
64+
data-testid="workspace-activate"
65+
>
66+
<Button
67+
isDisabled={isActivated || isArchived}
68+
isPending={isPending}
69+
type="submit"
70+
onPress={() => activateWorkspace({ body: { name: workspaceName } })}
71+
>
72+
<Check /> Activate
73+
</Button>
74+
<TooltipActivateBtn isActivated={isActivated} isArchived={!!isArchived} />
75+
</div>
76+
)
77+
}

src/routes/__tests__/route-workspace.test.tsx

+61
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { test, expect, vi } from 'vitest'
33
import userEvent from '@testing-library/user-event'
44
import { RouteWorkspace } from '../route-workspace'
55
import { useParams } from 'react-router-dom'
6+
import { server } from '@/mocks/msw/node'
7+
import { http, HttpResponse } from 'msw'
8+
import { mswEndpoint } from '@/test/msw-endpoint'
69

710
const mockNavigate = vi.fn()
811

@@ -148,3 +151,61 @@ test('revert changes button', async () => {
148151
})
149152
).toHaveValue('foo')
150153
})
154+
155+
test('disable activate workspace button', async () => {
156+
server.use(
157+
http.get(mswEndpoint('/api/v1/workspaces/active'), async () => {
158+
return HttpResponse.json({
159+
workspaces: [
160+
{
161+
name: 'foo',
162+
is_active: true,
163+
last_updated: new Date(Date.now()).toISOString(),
164+
},
165+
],
166+
})
167+
})
168+
)
169+
170+
const { getByTestId } = renderComponent()
171+
const activateSection = getByTestId(/workspace-activate/i)
172+
await waitFor(() => {
173+
expect(
174+
within(activateSection).getByRole('button', { name: /activate/i })
175+
).toBeDisabled()
176+
})
177+
178+
expect(
179+
within(activateSection).getByRole('button', {
180+
name: /context active button/i,
181+
})
182+
).toBeVisible()
183+
})
184+
185+
test('activate workspace', async () => {
186+
server.use(
187+
http.get(mswEndpoint('/api/v1/workspaces/active'), async () => {
188+
return HttpResponse.json({
189+
workspaces: [
190+
{
191+
name: 'bar',
192+
is_active: true,
193+
last_updated: new Date(Date.now()).toISOString(),
194+
},
195+
],
196+
})
197+
})
198+
)
199+
const { getByTestId, getByText } = renderComponent()
200+
const activateSection = getByTestId(/workspace-activate/i)
201+
const activateButton = await within(activateSection).findByRole('button', {
202+
name: /activate/i,
203+
})
204+
expect(activateButton).not.toBeDisabled()
205+
206+
await userEvent.click(activateButton)
207+
208+
await waitFor(() => {
209+
expect(getByText(/Activated "foo" workspace/i)).toBeVisible()
210+
})
211+
})

src/routes/route-workspace.tsx

+13-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { BreadcrumbHome } from '@/components/BreadcrumbHome'
22
import { ArchiveWorkspace } from '@/features/workspace/components/archive-workspace'
3-
import { PageHeading } from "@/components/heading";
4-
import { WorkspaceName } from "@/features/workspace/components/workspace-name";
5-
import { Alert, Breadcrumb, Breadcrumbs } from "@stacklok/ui-kit";
6-
import { useParams } from "react-router-dom";
7-
import { useArchivedWorkspaces } from "@/features/workspace/hooks/use-archived-workspaces";
8-
import { useRestoreWorkspaceButton } from "@/features/workspace/hooks/use-restore-workspace-button";
9-
import { WorkspaceCustomInstructions } from "@/features/workspace/components/workspace-custom-instructions";
10-
import { WorkspaceMuxingModel } from "@/features/workspace/components/workspace-muxing-model";
11-
import { PageContainer } from "@/components/page-container";
3+
import { PageHeading } from '@/components/heading'
4+
import { WorkspaceName } from '@/features/workspace/components/workspace-name'
5+
import { Alert, Breadcrumb, Breadcrumbs } from '@stacklok/ui-kit'
6+
import { useParams } from 'react-router-dom'
7+
import { useArchivedWorkspaces } from '@/features/workspace/hooks/use-archived-workspaces'
8+
import { useRestoreWorkspaceButton } from '@/features/workspace/hooks/use-restore-workspace-button'
9+
import { WorkspaceCustomInstructions } from '@/features/workspace/components/workspace-custom-instructions'
10+
import { WorkspaceMuxingModel } from '@/features/workspace/components/workspace-muxing-model'
11+
import { PageContainer } from '@/components/page-container'
12+
import { WorkspaceActivateButton } from '@/features/workspace/components/workspace-activate-button'
1213

1314
function WorkspaceArchivedBanner({ name }: { name: string }) {
1415
const restoreButtonProps = useRestoreWorkspaceButton({ workspaceName: name })
@@ -44,7 +45,9 @@ export function RouteWorkspace() {
4445
<Breadcrumb>Workspace Settings</Breadcrumb>
4546
</Breadcrumbs>
4647

47-
<PageHeading level={1} title={`Workspace settings for ${name}`} />
48+
<PageHeading level={1} title={`Workspace settings for ${name}`}>
49+
<WorkspaceActivateButton isArchived={isArchived} workspaceName={name} />
50+
</PageHeading>
4851

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

0 commit comments

Comments
 (0)