feat(#116): update command hooks for synchronous group response
Add CommandGroupResponse and ConfigUpdateResponse types. Switch useSendGroupCommand and useSendRouteCommand from openapi-fetch to authFetch returning CommandGroupResponse. Update useUpdateApplicationConfig to return ConfigUpdateResponse and fix all consumer onSuccess callbacks to access saved.config.version instead of saved.version. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { api } from '../client'
|
|
||||||
import { useAuthStore } from '../../auth/auth-store'
|
import { useAuthStore } from '../../auth/auth-store'
|
||||||
|
|
||||||
// ── Application Config ────────────────────────────────────────────────────
|
// ── Application Config ────────────────────────────────────────────────────
|
||||||
@@ -65,6 +64,11 @@ export function useApplicationConfig(application: string | undefined) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ConfigUpdateResponse {
|
||||||
|
config: ApplicationConfig
|
||||||
|
pushResult: CommandGroupResponse
|
||||||
|
}
|
||||||
|
|
||||||
export function useUpdateApplicationConfig() {
|
export function useUpdateApplicationConfig() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@@ -75,10 +79,10 @@ export function useUpdateApplicationConfig() {
|
|||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
})
|
})
|
||||||
if (!res.ok) throw new Error('Failed to update config')
|
if (!res.ok) throw new Error('Failed to update config')
|
||||||
return res.json() as Promise<ApplicationConfig>
|
return res.json() as Promise<ConfigUpdateResponse>
|
||||||
},
|
},
|
||||||
onSuccess: (saved) => {
|
onSuccess: (result) => {
|
||||||
queryClient.setQueryData(['applicationConfig', saved.application], saved)
|
queryClient.setQueryData(['applicationConfig', result.config.application], result.config)
|
||||||
queryClient.invalidateQueries({ queryKey: ['applicationConfig', 'all'] })
|
queryClient.invalidateQueries({ queryKey: ['applicationConfig', 'all'] })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -98,6 +102,16 @@ export function useProcessorRouteMapping(application?: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Group Command Response ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface CommandGroupResponse {
|
||||||
|
success: boolean
|
||||||
|
total: number
|
||||||
|
responded: number
|
||||||
|
responses: { agentId: string; status: string; message: string }[]
|
||||||
|
timedOut: string[]
|
||||||
|
}
|
||||||
|
|
||||||
// ── Generic Group Command (kept for non-config commands) ──────────────────
|
// ── Generic Group Command (kept for non-config commands) ──────────────────
|
||||||
|
|
||||||
interface SendGroupCommandParams {
|
interface SendGroupCommandParams {
|
||||||
@@ -109,12 +123,13 @@ interface SendGroupCommandParams {
|
|||||||
export function useSendGroupCommand() {
|
export function useSendGroupCommand() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ group, type, payload }: SendGroupCommandParams) => {
|
mutationFn: async ({ group, type, payload }: SendGroupCommandParams) => {
|
||||||
const { data, error } = await api.POST('/agents/groups/{group}/commands', {
|
const res = await authFetch(`/api/v1/agents/groups/${encodeURIComponent(group)}/commands`, {
|
||||||
params: { path: { group } },
|
method: 'POST',
|
||||||
body: { type, payload } as any,
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ type, payload }),
|
||||||
})
|
})
|
||||||
if (error) throw new Error('Failed to send command')
|
if (!res.ok) throw new Error('Failed to send command')
|
||||||
return data!
|
return res.json() as Promise<CommandGroupResponse>
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -163,12 +178,13 @@ export function useSendRouteCommand() {
|
|||||||
action: 'start' | 'stop' | 'suspend' | 'resume'
|
action: 'start' | 'stop' | 'suspend' | 'resume'
|
||||||
routeId: string
|
routeId: string
|
||||||
}) => {
|
}) => {
|
||||||
const { data, error } = await api.POST('/agents/groups/{group}/commands', {
|
const res = await authFetch(`/api/v1/agents/groups/${encodeURIComponent(application)}/commands`, {
|
||||||
params: { path: { group: application } },
|
method: 'POST',
|
||||||
body: { type: 'route-control', payload: { routeId, action, nonce: crypto.randomUUID() } } as any,
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ type: 'route-control', payload: { routeId, action, nonce: crypto.randomUUID() } }),
|
||||||
})
|
})
|
||||||
if (error) throw new Error('Failed to send route command')
|
if (!res.ok) throw new Error('Failed to send route command')
|
||||||
return data!
|
return res.json() as Promise<CommandGroupResponse>
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export default function AppConfigDetailPage() {
|
|||||||
updateConfig.mutate(updated, {
|
updateConfig.mutate(updated, {
|
||||||
onSuccess: (saved) => {
|
onSuccess: (saved) => {
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
toast({ title: 'Config saved', description: `${appId} updated to v${saved.version}`, variant: 'success' });
|
toast({ title: 'Config saved', description: `${appId} updated to v${saved.config.version}`, variant: 'success' });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast({ title: 'Save failed', description: 'Could not update configuration', variant: 'error' });
|
toast({ title: 'Save failed', description: 'Could not update configuration', variant: 'error' });
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ function AppConfigDetail({ appId, onClose }: { appId: string; onClose: () => voi
|
|||||||
if (!config || !form) return;
|
if (!config || !form) return;
|
||||||
const updated = { ...config, ...form, tracedProcessors: tracedDraft, routeRecording: routeRecordingDraft } as ApplicationConfig;
|
const updated = { ...config, ...form, tracedProcessors: tracedDraft, routeRecording: routeRecordingDraft } as ApplicationConfig;
|
||||||
updateConfig.mutate(updated, {
|
updateConfig.mutate(updated, {
|
||||||
onSuccess: (saved) => { setEditing(false); toast({ title: 'Config saved', description: `${appId} updated to v${saved.version}`, variant: 'success' }); },
|
onSuccess: (saved) => { setEditing(false); toast({ title: 'Config saved', description: `${appId} updated to v${saved.config.version}`, variant: 'success' }); },
|
||||||
onError: () => { toast({ title: 'Save failed', description: 'Could not update configuration', variant: 'error' }); },
|
onError: () => { toast({ title: 'Save failed', description: 'Could not update configuration', variant: 'error' }); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ export default function AgentHealth() {
|
|||||||
onSuccess: (saved) => {
|
onSuccess: (saved) => {
|
||||||
setConfigEditing(false);
|
setConfigEditing(false);
|
||||||
setConfigDraft({});
|
setConfigDraft({});
|
||||||
toast({ title: 'Config updated', description: `${appId} (v${saved.version})`, variant: 'success' });
|
toast({ title: 'Config updated', description: `${appId} (v${saved.config.version})`, variant: 'success' });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast({ title: 'Config update failed', variant: 'error' });
|
toast({ title: 'Config update failed', variant: 'error' });
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearS
|
|||||||
if (!updatedConfig) return;
|
if (!updatedConfig) return;
|
||||||
updateConfig.mutate(updatedConfig, {
|
updateConfig.mutate(updatedConfig, {
|
||||||
onSuccess: (saved) => {
|
onSuccess: (saved) => {
|
||||||
toast({ title: 'Tap configuration saved', description: `Pushed to agents (v${saved.version})`, variant: 'success' });
|
toast({ title: 'Tap configuration saved', description: `Pushed to agents (v${saved.config.version})`, variant: 'success' });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast({ title: 'Tap update failed', description: 'Could not save configuration', variant: 'error' });
|
toast({ title: 'Tap update failed', description: 'Could not save configuration', variant: 'error' });
|
||||||
@@ -229,7 +229,7 @@ function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearS
|
|||||||
const taps = appConfig.taps.filter(t => t.tapId !== tap.tapId);
|
const taps = appConfig.taps.filter(t => t.tapId !== tap.tapId);
|
||||||
updateConfig.mutate({ ...appConfig, taps }, {
|
updateConfig.mutate({ ...appConfig, taps }, {
|
||||||
onSuccess: (saved) => {
|
onSuccess: (saved) => {
|
||||||
toast({ title: 'Tap deleted', description: `${tap.attributeName} removed (v${saved.version})`, variant: 'success' });
|
toast({ title: 'Tap deleted', description: `${tap.attributeName} removed (v${saved.config.version})`, variant: 'success' });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast({ title: 'Tap delete failed', description: 'Could not save configuration', variant: 'error' });
|
toast({ title: 'Tap delete failed', description: 'Could not save configuration', variant: 'error' });
|
||||||
@@ -256,7 +256,7 @@ function DiagramPanel({ appId, routeId, exchangeId, onCorrelatedSelect, onClearS
|
|||||||
tracedProcessors,
|
tracedProcessors,
|
||||||
}, {
|
}, {
|
||||||
onSuccess: (saved) => {
|
onSuccess: (saved) => {
|
||||||
toast({ title: `Tracing ${enabled ? 'enabled' : 'disabled'}`, description: `${nodeId} — pushed to agents (v${saved.version})`, variant: 'success' });
|
toast({ title: `Tracing ${enabled ? 'enabled' : 'disabled'}`, description: `${nodeId} — pushed to agents (v${saved.config.version})`, variant: 'success' });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
useTracingStore.getState().toggleProcessor(appId, nodeId);
|
useTracingStore.getState().toggleProcessor(appId, nodeId);
|
||||||
|
|||||||
Reference in New Issue
Block a user