Skip to content

Commit 6acd46d

Browse files
authored
feat: make the custom mcp-form group-aware (#884)
* feat: make the custom mcp-form group-aware * fix test errors * simplify mocks * cleanpu
1 parent ed1ff05 commit 6acd46d

File tree

13 files changed

+196
-39
lines changed

13 files changed

+196
-39
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2+
import React from 'react'
3+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
4+
import { render, screen } from '@testing-library/react'
5+
import userEvent from '@testing-library/user-event'
6+
import { useCheckServerStatus } from '../use-check-server-status'
7+
import * as polling from '../../lib/polling'
8+
import { toast } from 'sonner'
9+
10+
describe('useCheckServerStatus', () => {
11+
const success = vi.mocked(toast.success)
12+
const loading = vi.mocked(toast.loading)
13+
14+
beforeEach(() => {
15+
// Make polling immediately resolve to success
16+
vi.spyOn(polling, 'pollServerStatus').mockResolvedValue(true)
17+
})
18+
19+
afterEach(() => {
20+
vi.restoreAllMocks()
21+
})
22+
23+
function TestHarness({
24+
serverName,
25+
groupName,
26+
}: {
27+
serverName: string
28+
groupName: string
29+
}) {
30+
const { checkServerStatus } = useCheckServerStatus()
31+
return (
32+
<button
33+
onClick={() =>
34+
checkServerStatus({ serverName, groupName, isEditing: false })
35+
}
36+
>
37+
Trigger
38+
</button>
39+
)
40+
}
41+
42+
function renderWithClient(ui: React.ReactElement) {
43+
const client = new QueryClient({
44+
defaultOptions: { queries: { gcTime: 0, staleTime: 0 } },
45+
})
46+
return render(
47+
<QueryClientProvider client={client}>{ui}</QueryClientProvider>
48+
)
49+
}
50+
51+
it('renders View link to the correct group after successful start', async () => {
52+
renderWithClient(
53+
<TestHarness serverName="my-server" groupName="research" />
54+
)
55+
56+
await userEvent.click(screen.getByRole('button', { name: 'Trigger' }))
57+
58+
// Loading toast shown first
59+
expect(loading).toHaveBeenCalled()
60+
61+
// Success toast should contain an action with a Link to the correct group
62+
expect(success).toHaveBeenCalled()
63+
const [, opts] = success.mock.calls[0]!
64+
// Extract Link element from action (Button asChild > Link)
65+
const actionNode = (opts as { action?: unknown })?.action as
66+
| { props?: Record<string, unknown> }
67+
| undefined
68+
const linkEl =
69+
((actionNode?.props?.children as { props?: Record<string, unknown> }) ??
70+
actionNode) ||
71+
undefined
72+
73+
const to = (linkEl?.props as { to?: unknown })?.to
74+
expect(to).toBe('/group/$groupName')
75+
76+
const params = (linkEl?.props as { params?: { groupName?: string } })
77+
?.params
78+
expect(params?.groupName).toBe('research')
79+
80+
const search = (
81+
linkEl?.props as {
82+
search?: { newServerName?: string }
83+
}
84+
)?.search
85+
expect(search?.newServerName).toBe('my-server')
86+
})
87+
})

renderer/src/common/hooks/use-check-server-status.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ export function useCheckServerStatus() {
2121
const checkServerStatus = useCallback(
2222
async ({
2323
serverName,
24+
groupName,
2425
isEditing = false,
2526
}: {
2627
serverName: string
28+
groupName: string
2729
isEditing?: boolean
2830
}): Promise<boolean> => {
2931
toast.loading(
@@ -58,7 +60,7 @@ export function useCheckServerStatus() {
5860
<Button asChild>
5961
<Link
6062
to="/group/$groupName"
61-
params={{ groupName: 'default' }}
63+
params={{ groupName }}
6264
search={{ newServerName: serverName }}
6365
onClick={() => toast.dismiss(toastIdRef.current)}
6466
viewTransition={{ types: ['slide-left'] }}

renderer/src/features/mcp-servers/components/card-mcp-server/server-actions/items/edit-configuration-menu-item.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,14 @@ export function EditConfigurationMenuItem({
4242
</a>
4343
</DropdownMenuItem>
4444

45-
{!isLoadingServer && (
45+
{!isLoadingServer && serverData?.group && (
4646
<WrapperDialogFormMcp
4747
serverType={serverDialogOpen}
4848
closeDialog={() =>
4949
setServerDialogOpen({ local: false, remote: false })
5050
}
5151
serverToEdit={serverName}
52+
groupName={serverData.group}
5253
/>
5354
)}
5455
</>

renderer/src/features/mcp-servers/components/local-mcp/__tests__/dialog-form-local-mcp.test.tsx

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('DialogFormLocalMcp', () => {
8181
it('renders form fields correctly for docker image', async () => {
8282
renderWithProviders(
8383
<Wrapper>
84-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
84+
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} groupName="default" />
8585
</Wrapper>
8686
)
8787

@@ -122,7 +122,7 @@ describe('DialogFormLocalMcp', () => {
122122

123123
renderWithProviders(
124124
<Wrapper>
125-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
125+
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} groupName="default" />
126126
</Wrapper>
127127
)
128128

@@ -178,7 +178,7 @@ describe('DialogFormLocalMcp', () => {
178178

179179
renderWithProviders(
180180
<Wrapper>
181-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
181+
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} groupName="default" />
182182
</Wrapper>
183183
)
184184

@@ -251,7 +251,7 @@ describe('DialogFormLocalMcp', () => {
251251

252252
renderWithProviders(
253253
<Wrapper>
254-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
254+
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} groupName="default" />
255255
</Wrapper>
256256
)
257257

@@ -337,7 +337,7 @@ describe('DialogFormLocalMcp', () => {
337337

338338
renderWithProviders(
339339
<Wrapper>
340-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
340+
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} groupName="default" />
341341
</Wrapper>
342342
)
343343

@@ -376,7 +376,7 @@ describe('DialogFormLocalMcp', () => {
376376

377377
renderWithProviders(
378378
<Wrapper>
379-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
379+
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} groupName="default" />
380380
</Wrapper>
381381
)
382382

@@ -411,7 +411,7 @@ describe('DialogFormLocalMcp', () => {
411411
it('renders aria-hidden column labels for storage volumes', async () => {
412412
renderWithProviders(
413413
<Wrapper>
414-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
414+
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} groupName="default" />
415415
</Wrapper>
416416
)
417417

@@ -447,7 +447,11 @@ describe('DialogFormLocalMcp', () => {
447447

448448
renderWithProviders(
449449
<Wrapper>
450-
<DialogFormLocalMcp isOpen closeDialog={mockOnOpenChange} />
450+
<DialogFormLocalMcp
451+
isOpen
452+
closeDialog={mockOnOpenChange}
453+
groupName="default"
454+
/>
451455
</Wrapper>
452456
)
453457

@@ -494,7 +498,11 @@ describe('DialogFormLocalMcp', () => {
494498

495499
renderWithProviders(
496500
<Wrapper>
497-
<DialogFormLocalMcp isOpen closeDialog={mockOnOpenChange} />
501+
<DialogFormLocalMcp
502+
isOpen
503+
closeDialog={mockOnOpenChange}
504+
groupName="default"
505+
/>
498506
</Wrapper>
499507
)
500508

@@ -518,7 +526,11 @@ describe('DialogFormLocalMcp', () => {
518526

519527
renderWithProviders(
520528
<Wrapper>
521-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
529+
<DialogFormLocalMcp
530+
isOpen
531+
closeDialog={vi.fn()}
532+
groupName="default"
533+
/>
522534
</Wrapper>
523535
)
524536

@@ -585,7 +597,11 @@ describe('DialogFormLocalMcp', () => {
585597

586598
renderWithProviders(
587599
<Wrapper>
588-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
600+
<DialogFormLocalMcp
601+
isOpen
602+
closeDialog={vi.fn()}
603+
groupName="default"
604+
/>
589605
</Wrapper>
590606
)
591607

@@ -627,7 +643,11 @@ describe('DialogFormLocalMcp', () => {
627643

628644
renderWithProviders(
629645
<Wrapper>
630-
<DialogFormLocalMcp isOpen closeDialog={mockOnOpenChange} />
646+
<DialogFormLocalMcp
647+
isOpen
648+
closeDialog={mockOnOpenChange}
649+
groupName="default"
650+
/>
631651
</Wrapper>
632652
)
633653

@@ -645,7 +665,11 @@ describe('DialogFormLocalMcp', () => {
645665

646666
renderWithProviders(
647667
<Wrapper>
648-
<DialogFormLocalMcp isOpen closeDialog={mockOnOpenChange} />
668+
<DialogFormLocalMcp
669+
isOpen
670+
closeDialog={mockOnOpenChange}
671+
groupName="default"
672+
/>
649673
</Wrapper>
650674
)
651675

@@ -660,7 +684,11 @@ describe('DialogFormLocalMcp', () => {
660684
it('activates the network isolation tab if a validation error occurs there while on the configuration tab', async () => {
661685
renderWithProviders(
662686
<Wrapper>
663-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
687+
<DialogFormLocalMcp
688+
isOpen
689+
closeDialog={vi.fn()}
690+
groupName="default"
691+
/>
664692
</Wrapper>
665693
)
666694

@@ -710,7 +738,11 @@ describe('DialogFormLocalMcp', () => {
710738
it('activates the configuration tab if a validation error occurs there while on the network isolation tab', async () => {
711739
renderWithProviders(
712740
<Wrapper>
713-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
741+
<DialogFormLocalMcp
742+
isOpen
743+
closeDialog={vi.fn()}
744+
groupName="default"
745+
/>
714746
</Wrapper>
715747
)
716748

@@ -733,7 +765,11 @@ describe('DialogFormLocalMcp', () => {
733765
it('shows alert when network isolation is enabled but no hosts or ports are configured', async () => {
734766
renderWithProviders(
735767
<Wrapper>
736-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
768+
<DialogFormLocalMcp
769+
isOpen
770+
closeDialog={vi.fn()}
771+
groupName="default"
772+
/>
737773
</Wrapper>
738774
)
739775

@@ -781,7 +817,11 @@ describe('DialogFormLocalMcp', () => {
781817

782818
renderWithProviders(
783819
<Wrapper>
784-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
820+
<DialogFormLocalMcp
821+
isOpen
822+
closeDialog={vi.fn()}
823+
groupName="default"
824+
/>
785825
</Wrapper>
786826
)
787827

@@ -874,7 +914,7 @@ describe('DialogFormLocalMcp', () => {
874914

875915
renderWithProviders(
876916
<Wrapper>
877-
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} />
917+
<DialogFormLocalMcp isOpen closeDialog={vi.fn()} groupName="default" />
878918
</Wrapper>
879919
)
880920

renderer/src/features/mcp-servers/components/local-mcp/dialog-form-local-mcp.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,12 @@ export function DialogFormLocalMcp({
7373
isOpen,
7474
closeDialog,
7575
serverToEdit,
76+
groupName,
7677
}: {
7778
isOpen: boolean
7879
closeDialog: () => void
7980
serverToEdit?: string | null
81+
groupName: string
8082
}) {
8183
const [error, setError] = useState<string | null>(null)
8284
const [isSubmitting, setIsSubmitting] = useState(false)
@@ -107,6 +109,7 @@ export function DialogFormLocalMcp({
107109
onSecretError: (error, variables) => {
108110
log.error('onSecretError', error, variables)
109111
},
112+
groupName,
110113
})
111114

112115
const { updateServerMutation } = useUpdateServer(serverToEdit || '', {
@@ -163,7 +166,7 @@ export function DialogFormLocalMcp({
163166
{ data },
164167
{
165168
onSuccess: () => {
166-
checkServerStatus({ serverName: data.name, isEditing })
169+
checkServerStatus({ serverName: data.name, groupName, isEditing })
167170
closeDialog()
168171
},
169172
onSettled: (_, error) => {
@@ -183,7 +186,7 @@ export function DialogFormLocalMcp({
183186
{ data },
184187
{
185188
onSuccess: () => {
186-
checkServerStatus({ serverName: data.name })
189+
checkServerStatus({ serverName: data.name, groupName })
187190
closeDialog()
188191
},
189192
onSettled: (_, error) => {

0 commit comments

Comments
 (0)