Skip to content

Commit 4ecd688

Browse files
authored
Merge pull request #145 from docker/cm/0.2.43
Optimize queries and mutations
2 parents b505316 + 95c71fb commit 4ecd688

File tree

11 files changed

+56
-73
lines changed

11 files changed

+56
-73
lines changed

src/extension/ui/src/App.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import { CatalogGrid } from './components/CatalogGrid';
77
import { POLL_INTERVAL } from './Constants';
88
import ConfigurationModal from './components/tile/Modal';
99
import LoadingState from './components/LoadingState';
10-
import { useCatalogAll } from './hooks/useCatalog';
11-
import { useRequiredImages } from './hooks/useRequiredImages';
12-
import { useMCPClient } from './hooks/useMCPClient';
13-
import { useConfig } from './hooks/useConfig';
14-
import { useSecrets } from './hooks/useSecrets';
10+
import { useCatalogAll } from './queries/useCatalog';
11+
import { useRequiredImages } from './queries/useRequiredImages';
12+
import { useMCPClient } from './queries/useMCPClient';
13+
import { useConfig } from './queries/useConfig';
14+
import { useSecrets } from './queries/useSecrets';
1515

1616
export const client = createDockerDesktopClient();
1717

src/extension/ui/src/components/tabs/ToolCatalog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Grid2 } from '@mui/material';
33
import Tile from '../tile/Index';
44
import { v1 } from "@docker/extension-api-client-types";
55
import { CATALOG_LAYOUT_SX } from '../../Constants';
6-
import { useCatalog } from '../../hooks/useCatalog';
6+
import { useCatalogAll } from '../../queries/useCatalog';
77

88
interface ToolCatalogProps {
99
search: string;
@@ -12,7 +12,7 @@ interface ToolCatalogProps {
1212
}
1313

1414
const ToolCatalog: React.FC<ToolCatalogProps> = ({ search, client, showMine }) => {
15-
const { catalogItems } = useCatalog(client)
15+
const { catalogItems } = useCatalogAll(client)
1616
const filteredCatalogItems = catalogItems.filter(item => {
1717
const matchesSearch = item.name.toLowerCase().includes(search.toLowerCase());
1818
const hideBecauseItsNotMine = showMine && !item.registered;

src/extension/ui/src/components/tabs/YourTools.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { v1 } from '@docker/extension-api-client-types';
77
import { createDockerDesktopClient } from '@docker/extension-api-client';
88
import { CatalogItemRichened } from '../../types/catalog';
99
import { Secret } from '../../types/secrets';
10-
import { useCatalog } from '../../hooks/useCatalog';
10+
import { useCatalogAll } from '../../queries/useCatalog';
1111
// Initialize the Docker Desktop client
1212
const client = createDockerDesktopClient();
1313

@@ -18,7 +18,7 @@ interface YourToolsProps {
1818
const YourTools: React.FC<YourToolsProps> = ({
1919
search,
2020
}) => {
21-
const { catalogItems } = useCatalog(client)
21+
const { catalogItems } = useCatalogAll(client)
2222
return (
2323
<Grid2 container spacing={1} sx={CATALOG_LAYOUT_SX}>
2424
{catalogItems.map((catalogItem) => {

src/extension/ui/src/components/tile/ConfigEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Alert, Stack } from "@mui/material";
33
import { CatalogItemRichened } from "../../types/catalog";
44
import { useEffect, useState, useCallback, useMemo } from "react";
55
import * as JsonSchema from "json-schema-library";
6-
import { getTemplateForItem, useConfig } from "../../hooks/useConfig";
6+
import { getTemplateForItem, useConfig } from "../../queries/useConfig";
77
import { buildObjectFromFlattenedObject, deepFlattenObject, deepSet } from "../../MergeDeep";
88
import { CheckOutlined, CloseOutlined } from "@mui/icons-material";
99
import { v1 } from "@docker/extension-api-client-types";

src/extension/ui/src/components/tile/Index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import Top from "./Top";
88
import Center from "./Center";
99
import Bottom from "./Bottom";
1010
import { v1 } from "@docker/extension-api-client-types";
11-
import { useSecrets } from "../../hooks/useSecrets";
12-
import { useCatalogOperations, useRegistry } from "../../hooks/useCatalog";
11+
import { useSecrets } from "../../queries/useSecrets";
12+
import { useCatalogAll, useCatalogOperations } from "../../queries/useCatalog";
1313
import { MCP_POLICY_NAME } from "../../Constants";
1414

1515
type TileProps = {
@@ -25,8 +25,8 @@ const Tile = ({ item, client }: TileProps) => {
2525
const [secretLoading, setSecretLoading] = useState(false)
2626
const [showConfigModal, setShowConfigModal] = useState(false)
2727
const { isLoading: secretsLoading, mutate: mutateSecret } = useSecrets(client)
28-
const { registryLoading } = useRegistry(client)
2928
const { registerCatalogItem, unregisterCatalogItem } = useCatalogOperations(client)
29+
const { registryLoading } = useCatalogAll(client)
3030

3131
if (registryLoading || secretsLoading) {
3232
return <>

src/extension/ui/src/components/tile/Modal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { CatalogItemRichened } from "../../types/catalog";
55
import { v1 } from "@docker/extension-api-client-types";
66
import { ASSIGNED_SECRET_PLACEHOLDER, CATALOG_LAYOUT_SX, MCP_POLICY_NAME, UNASSIGNED_SECRET_PLACEHOLDER } from "../../Constants";
77
import ConfigEditor from "./ConfigEditor";
8-
import { useSecrets } from "../../hooks/useSecrets";
9-
import { useCatalogOperations, useRegistry } from "../../hooks/useCatalog";
10-
import { useConfig } from "../../hooks/useConfig";
8+
import { useSecrets } from "../../queries/useSecrets";
9+
import { useCatalogAll, useCatalogOperations } from "../../queries/useCatalog";
10+
import { useConfig } from "../../queries/useConfig";
1111

1212
interface TabPanelProps {
1313
children?: React.ReactNode;
@@ -54,7 +54,7 @@ const ConfigurationModal = ({
5454
const theme = useTheme();
5555

5656
const { isLoading: secretsLoading, mutate: mutateSecret } = useSecrets(client)
57-
const { registryLoading } = useRegistry(client)
57+
const { registryLoading } = useCatalogAll(client)
5858
const { registerCatalogItem, unregisterCatalogItem } = useCatalogOperations(client)
5959
const { configLoading } = useConfig(client)
6060

src/extension/ui/src/hooks/useCatalog.ts renamed to src/extension/ui/src/queries/useCatalog.ts

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { getTemplateForItem } from './useConfig';
99
import { useState, useEffect } from 'react';
1010
import { escapeJSONForPlatformShell, tryRunImageSync } from '../FileUtils';
1111
import { useConfig } from './useConfig';
12-
import { useRequiredImages } from './useRequiredImages';
1312
import { useSecrets } from "./useSecrets";
1413

1514
// Storage keys for each query type
@@ -25,7 +24,7 @@ interface QueryContextWithMeta {
2524
};
2625
}
2726

28-
export function useCatalog(client: v1.DockerDesktopClient) {
27+
function useCatalog(client: v1.DockerDesktopClient) {
2928
const queryClient = useQueryClient();
3029
const { data: secrets, isLoading: secretsLoading } = useSecrets(client);
3130
const { registryItems, registryLoading } = useRegistry(client);
@@ -38,9 +37,7 @@ export function useCatalog(client: v1.DockerDesktopClient) {
3837
const configTemplate = getTemplateForItem(item, itemConfigValue);
3938
const baseConfigTemplate = getTemplateForItem(item, {});
4039
const unConfigured = Boolean(item.config) && (neverOnceConfigured || JSON.stringify(itemConfigValue) === JSON.stringify(baseConfigTemplate));
41-
if (item.name === 'elevenlabs') {
42-
console.log('elevenlabs', itemConfigValue, configTemplate, unConfigured)
43-
}
40+
4441
const missingASecret = secretsWithAssignment.some((secret) => !secret.assigned);
4542
const enrichedItem: CatalogItemRichened = {
4643
...item,
@@ -80,10 +77,7 @@ export function useCatalog(client: v1.DockerDesktopClient) {
8077
// without causing a full catalog reload
8178
useEffect(() => {
8279
if (catalogItems.length > 0 && !secretsLoading && !configLoading && !registryLoading) {
83-
const enrichedItems = catalogItems.map(item => ({
84-
...item,
85-
...enrichCatalogItem(item)
86-
}));
80+
const enrichedItems = catalogItems.map(enrichCatalogItem);
8781

8882
// Use the same reference if nothing changed to prevent unnecessary re-renders
8983
const hasChanges = JSON.stringify(enrichedItems) !== JSON.stringify(catalogItems);
@@ -123,7 +117,7 @@ export function useCatalog(client: v1.DockerDesktopClient) {
123117
};
124118
}
125119

126-
export function useRegistry(client: v1.DockerDesktopClient) {
120+
function useRegistry(client: v1.DockerDesktopClient) {
127121
const queryClient = useQueryClient();
128122
const [canRegister, setCanRegister] = useState<boolean>(false);
129123

@@ -210,9 +204,8 @@ export function useRegistry(client: v1.DockerDesktopClient) {
210204

211205
export function useCatalogOperations(client: v1.DockerDesktopClient) {
212206
const queryClient = useQueryClient();
213-
const { registryItems, canRegister } = useRegistry(client);
214-
const { config, syncConfigWithRegistry } = useConfig(client);
215-
const { loadAllImages } = useRequiredImages(client);
207+
const { registryItems } = useRegistry(client);
208+
const { config } = useConfig(client);
216209

217210
// Register catalog item mutation
218211
const registerItemMutation = useMutation({
@@ -231,32 +224,6 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
231224

232225
// If there's a JSON schema configuration, validate and generate default values
233226
if (Array.isArray(item.config) && item.config.length > 0) {
234-
const configSchema = item.config[0];
235-
236-
// Check if we have required fields from anyOf conditions
237-
if (configSchema.anyOf) {
238-
configSchema.anyOf.forEach((condition: any) => {
239-
if (condition.required) {
240-
condition.required.forEach((field: string) => {
241-
if (!(field in itemConfig)) {
242-
// Generate a default value if possible
243-
itemConfig[field] = "";
244-
}
245-
});
246-
}
247-
});
248-
}
249-
250-
// Handle normal required fields
251-
if (configSchema.required) {
252-
configSchema.required.forEach((field: string) => {
253-
if (!(field in itemConfig)) {
254-
// Generate a default value if possible
255-
itemConfig[field] = "";
256-
}
257-
});
258-
}
259-
260227
// Use JSON schema template for any remaining defaults
261228
const template = getTemplateForItem(item, itemConfig);
262229
itemConfig = { ...template, ...itemConfig };
@@ -273,18 +240,34 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
273240

274241
await tryRunImageSync(client, ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', 'registry.yaml', payload]);
275242

276-
await syncConfigWithRegistry(newRegistry);
277-
await loadAllImages();
278-
279243
if (showNotification) {
280244
client.desktopUI.toast.success(`${item.name} registered successfully.`);
281245
}
282246
return { success: true, newRegistry };
283247
} catch (error) {
284248
client.desktopUI.toast.error('Failed to register catalog item: ' + error);
249+
// Treat YAML file write failures as fatal, no rollback
285250
throw error;
286251
}
287252
},
253+
onMutate: async ({ item }) => {
254+
// Optimistically update the registry data
255+
const currentRegistry = queryClient.getQueryData(['registry']) as { [key: string]: { ref: string; config?: any } } || {};
256+
const newRegistry: { [key: string]: { ref: string; config?: any } } = {
257+
...currentRegistry,
258+
[item.name]: { ref: item.ref }
259+
};
260+
261+
// If there's config, add it
262+
if (item.config && config && config[item.name]) {
263+
newRegistry[item.name] = {
264+
...newRegistry[item.name],
265+
config: config[item.name]
266+
};
267+
}
268+
269+
queryClient.setQueryData(['registry'], newRegistry);
270+
},
288271
onSuccess: async (data) => {
289272
// Update the registry data after successful registration
290273
queryClient.setQueryData(['registry'], data.newRegistry);
@@ -310,15 +293,25 @@ export function useCatalogOperations(client: v1.DockerDesktopClient) {
310293

311294
await tryRunImageSync(client, ['--rm', '-v', 'docker-prompts:/docker-prompts', '--workdir', '/docker-prompts', 'vonwig/function_write_files:latest', 'registry.yaml', payload]);
312295

313-
// Explicitly sync the registry with config
314-
await syncConfigWithRegistry(currentRegistry);
315296
client.desktopUI.toast.success(`${item.name} unregistered successfully.`);
316297
return { success: true, newRegistry: currentRegistry };
317298
} catch (error) {
318299
client.desktopUI.toast.error('Failed to unregister catalog item: ' + error);
300+
// Treat YAML file write failures as fatal, no rollback
319301
throw error;
320302
}
321303
},
304+
onMutate: async (item) => {
305+
// Optimistically update the registry data
306+
const currentRegistry = { ...(queryClient.getQueryData(['registry']) as { [key: string]: { ref: string; config?: any } } || {}) };
307+
308+
// Remove the item
309+
if (currentRegistry[item.name]) {
310+
delete currentRegistry[item.name];
311+
}
312+
313+
queryClient.setQueryData(['registry'], currentRegistry);
314+
},
322315
onSuccess: async (data) => {
323316
// Update the registry data after successful unregistration
324317
queryClient.setQueryData(['registry'], data.newRegistry);

src/extension/ui/src/hooks/useConfig.ts renamed to src/extension/ui/src/queries/useConfig.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,31 +67,21 @@ export function useConfig(client: v1.DockerDesktopClient) {
6767
return { itemName, updatedConfig: updatedConfigRef };
6868
} catch (error) {
6969
client.desktopUI.toast.error('Failed to update config: ' + error);
70+
// Treat YAML file write failures as fatal, no rollback
7071
throw error;
7172
}
7273
},
7374
onMutate: async ({ itemName, newConfig }) => {
7475
// Cancel any outgoing refetches
7576
await queryClient.cancelQueries({ queryKey: ['config'] });
7677

77-
// Snapshot the previous value
78-
const previousConfig = queryClient.getQueryData(['config']);
79-
8078
// Optimistically update to the new value
8179
const updatedConfig = {
82-
...(previousConfig as Record<string, any> || {}),
80+
...(queryClient.getQueryData(['config']) as Record<string, any> || {}),
8381
[itemName]: newConfig
8482
};
8583

8684
queryClient.setQueryData(['config'], updatedConfig);
87-
88-
return { previousConfig };
89-
},
90-
onError: (err, variables, context) => {
91-
// If the mutation fails, use the context to roll back
92-
if (context?.previousConfig) {
93-
queryClient.setQueryData(['config'], context.previousConfig);
94-
}
9585
},
9686
onSuccess: (data) => {
9787
client.desktopUI.toast.success('Config saved successfully.');
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)