Skip to content

Commit 228b457

Browse files
committed
feat: Document Store
1 parent ae704bd commit 228b457

File tree

7 files changed

+802
-175
lines changed

7 files changed

+802
-175
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @file index.ts
3+
* @author ChRIS UI Team
4+
* @description Exports all custom hooks for the Store component
5+
*/
6+
7+
export { useStoreAuthentication } from "./useStoreAuthentication";
8+
export { usePluginInstallation } from "./usePluginInstallation";
9+
export { usePluginSelection } from "./usePluginSelection";
10+
11+
// Also export types from the hooks for convenience
12+
export type { PendingOperation } from "./useStoreAuthentication";
13+
export type { RefreshMap } from "./usePluginInstallation";
14+
export type { PluginResourceSelections } from "./usePluginSelection";
Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,57 @@
1-
import type { ComputeResource, Plugin } from "@fnndsc/chrisapi";
1+
/**
2+
* @file updateComputeResource.ts
3+
* @author ChRIS UI Team
4+
* @description Utility function for updating plugin compute resources
5+
*/
6+
27
import axios from "axios";
38

4-
interface ModifyResourceArgs {
5-
adminCred: string;
6-
plugin: Plugin["data"];
7-
newComputeResource: ComputeResource[];
8-
}
9-
10-
async function postModifyComputeResource({
11-
adminCred,
12-
plugin,
13-
newComputeResource,
14-
}: ModifyResourceArgs) {
9+
/**
10+
* Updates compute resources for a plugin in the ChRIS store
11+
*
12+
* @param {string} pluginName - Plugin name
13+
* @param {string} version - Plugin version
14+
* @param {string[]} resourceNames - Array of compute resource names
15+
* @param {string} authHeader - Authentication header
16+
* @param {string} storeUrl - URL of the ChRIS store
17+
* @returns {Promise<any>} API response
18+
*/
19+
const postModifyComputeResource = async (
20+
pluginName: string,
21+
version: string,
22+
resourceNames: string[],
23+
authHeader: string,
24+
storeUrl: string,
25+
): Promise<any> => {
1526
const adminURL = import.meta.env.VITE_CHRIS_UI_URL.replace(
1627
"/api/v1/",
1728
"/chris-admin/api/v1/",
1829
);
30+
1931
if (!adminURL) {
20-
throw new Error("Please provide a valid chris-admin URL.");
32+
throw new Error("Please provide a link to your chris-admin URL.");
2133
}
22-
const computeResourceList = newComputeResource
23-
.map((r) => r.data.name)
24-
.join(",");
34+
35+
const computeResourceList = resourceNames.join(",");
36+
37+
// Extract the base URL from the store URL to build plugin store URL
38+
const storeBaseUrl = storeUrl.substring(0, storeUrl.indexOf("/plugins/"));
39+
2540
const pluginData = {
2641
compute_names: computeResourceList,
27-
name: plugin.name,
28-
version: plugin.version,
29-
plugin_store_url: plugin.url,
42+
name: pluginName,
43+
version: version,
44+
plugin_store_url: `${storeBaseUrl}/`,
3045
};
46+
3147
const response = await axios.post(adminURL, pluginData, {
3248
headers: {
33-
Authorization: adminCred,
49+
Authorization: authHeader,
3450
"Content-Type": "application/json",
3551
},
3652
});
53+
3754
return response.data;
38-
}
55+
};
3956

4057
export default postModifyComputeResource;
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/**
2+
* @file usePluginInstallation.ts
3+
* @author ChRIS UI Team
4+
* @description Custom hook for managing plugin installation operations
5+
*/
6+
7+
import { useState, useCallback } from "react";
8+
import { notification } from "antd";
9+
import type { Plugin } from "@fnndsc/chrisapi";
10+
import type { ComputeResource } from "@fnndsc/chrisapi";
11+
import type { StorePlugin } from "./useFetchPlugins";
12+
import { handleInstallPlugin } from "../../PipelinesCopy/utils";
13+
import postModifyComputeResource from "./updateComputeResource";
14+
15+
/**
16+
* @interface RefreshMap
17+
* @description Maps plugin IDs to refresh counters for triggering UI updates
18+
*/
19+
export interface RefreshMap {
20+
[pluginId: string]: number;
21+
}
22+
23+
/**
24+
* @interface PluginInstallationState
25+
* @description State and functions returned by the usePluginInstallation hook
26+
*/
27+
interface PluginInstallationState {
28+
/** Maps plugin IDs to refresh counters */
29+
refreshMap: RefreshMap;
30+
/** Whether a bulk installation is in progress */
31+
isBulkInstalling: boolean;
32+
/** Handle installation of a single plugin */
33+
handleInstall: (
34+
plugin: StorePlugin,
35+
resources: ComputeResource[],
36+
authHeader: string,
37+
) => Promise<void>;
38+
/** Handle modification of compute resources for an existing plugin */
39+
handleModify: (
40+
plugin: Plugin,
41+
resources: ComputeResource[],
42+
authHeader: string,
43+
) => Promise<void>;
44+
/** Install all plugins in the current view */
45+
installAllPlugins: (
46+
plugins: StorePlugin[],
47+
getResourcesForPlugin: (id: string) => ComputeResource[],
48+
getAuthHeader: (
49+
plugin: StorePlugin,
50+
resources: ComputeResource[],
51+
) => string | null,
52+
) => Promise<void>;
53+
/** Update the refresh counter for a plugin */
54+
updatePluginRefreshCounter: (pluginId: string) => void;
55+
}
56+
57+
/**
58+
* Custom hook for managing plugin installation operations
59+
*
60+
* @returns {PluginInstallationState} Plugin installation state and functions
61+
*/
62+
export const usePluginInstallation = (): PluginInstallationState => {
63+
// Track refresh state for each plugin
64+
const [refreshMap, setRefreshMap] = useState<RefreshMap>({});
65+
66+
// Track bulk installation state
67+
const [isBulkInstalling, setBulkInstalling] = useState(false);
68+
69+
/**
70+
* Update the refresh counter for a plugin
71+
* Used to trigger UI updates for a specific plugin
72+
*
73+
* @param {string} pluginId - ID of the plugin to refresh
74+
*/
75+
const updatePluginRefreshCounter = useCallback((pluginId: string) => {
76+
setRefreshMap((prev) => ({
77+
...prev,
78+
[pluginId]: (prev[pluginId] || 0) + 1,
79+
}));
80+
}, []);
81+
82+
/**
83+
* Handle installation of a single plugin
84+
*
85+
* This function:
86+
* 1. Calls the plugin installation utility
87+
* 2. Shows success/error notifications
88+
* 3. Updates the refresh counter for the plugin
89+
*
90+
* @param {StorePlugin} plugin - Plugin to install
91+
* @param {ComputeResource[]} resources - Compute resources for the plugin
92+
* @param {string} authHeader - Authentication header
93+
* @returns {Promise<void>}
94+
*/
95+
const handleInstall = useCallback(
96+
async (
97+
plugin: StorePlugin,
98+
resources: ComputeResource[],
99+
authHeader: string,
100+
) => {
101+
try {
102+
// Important: Include plugin_store_url directly as the API requires it
103+
const pluginAdapter = {
104+
name: plugin.name,
105+
version: plugin.version,
106+
// Provide plugin_store_url directly instead of relying on url mapping
107+
url: plugin.url,
108+
};
109+
110+
// Fixed parameter order to match handleInstallPlugin function definition
111+
await handleInstallPlugin(authHeader, pluginAdapter, resources);
112+
113+
notification.success({
114+
message: "Success",
115+
description: `Installed plugin ${plugin.title} to ${plugin.url || ""}`,
116+
placement: "bottomRight",
117+
});
118+
updatePluginRefreshCounter(plugin.id);
119+
} catch (err: any) {
120+
console.error(err);
121+
notification.error({
122+
message: "Installation failed",
123+
description: err?.message || "Installation failed",
124+
placement: "bottomRight",
125+
});
126+
}
127+
},
128+
[updatePluginRefreshCounter],
129+
);
130+
131+
/**
132+
* Handle modification of compute resources for an existing plugin
133+
*
134+
* This function:
135+
* 1. Makes API call to update compute resources
136+
* 2. Shows success/error notifications
137+
* 3. Updates the refresh counter for the plugin
138+
*
139+
* @param {Plugin} plugin - Plugin to modify
140+
* @param {ComputeResource[]} resources - New compute resources
141+
* @param {string} authHeader - Authentication header
142+
* @returns {Promise<void>}
143+
*/
144+
const handleModify = useCallback(
145+
async (
146+
plugin: Plugin,
147+
resources: ComputeResource[],
148+
authHeader: string,
149+
) => {
150+
try {
151+
await postModifyComputeResource(
152+
plugin.data.name,
153+
plugin.data.version,
154+
resources.map((r) => r.data.name),
155+
authHeader,
156+
plugin.collection.items[0].href,
157+
);
158+
notification.success({
159+
message: "Success",
160+
description: `Updated compute resources for plugin ${plugin.data.name}.`,
161+
placement: "bottomRight",
162+
});
163+
updatePluginRefreshCounter(plugin.data.id);
164+
} catch (err) {
165+
console.error(err);
166+
notification.error({
167+
message: "Error",
168+
description: `Failed to update compute resources for ${plugin.data.name}.`,
169+
placement: "bottomRight",
170+
});
171+
}
172+
},
173+
[updatePluginRefreshCounter],
174+
);
175+
176+
/**
177+
* Install all plugins in the current view
178+
*
179+
* This function:
180+
* 1. Sets bulk installation state to true
181+
* 2. Maps through all plugins and installs each one
182+
* 3. Uses selected compute resources if available
183+
* 4. Sets bulk installation state back to false when complete
184+
*
185+
* @param {StorePlugin[]} plugins - Plugins to install
186+
* @param {Function} getResourcesForPlugin - Function to get compute resources for a plugin
187+
* @param {Function} getAuthHeader - Function to get auth header for a plugin
188+
* @returns {Promise<void>}
189+
*/
190+
const installAllPlugins = useCallback(
191+
async (
192+
plugins: StorePlugin[],
193+
getResourcesForPlugin: (id: string) => ComputeResource[],
194+
getAuthHeader: (
195+
plugin: StorePlugin,
196+
resources: ComputeResource[],
197+
) => string | null,
198+
) => {
199+
setBulkInstalling(true);
200+
201+
const installs = plugins.map(async (plugin) => {
202+
try {
203+
// Get compute resources for this plugin
204+
const resources = getResourcesForPlugin(plugin.id);
205+
206+
// Get auth header for this plugin
207+
const hdr = getAuthHeader(plugin, resources);
208+
if (!hdr) return;
209+
210+
// Important: Include plugin_store_url directly as the API requires it
211+
const pluginAdapter = {
212+
name: plugin.name,
213+
version: plugin.version,
214+
// Provide plugin_store_url directly instead of relying on url mapping
215+
plugin_store_url: plugin.url,
216+
};
217+
218+
// Install the plugin with fixed parameter order
219+
await handleInstallPlugin(hdr, pluginAdapter, resources);
220+
updatePluginRefreshCounter(plugin.id);
221+
222+
// Show success notification
223+
notification.success({
224+
message: "Success",
225+
description: `Installed plugin ${plugin.title}.`,
226+
placement: "bottomRight",
227+
});
228+
} catch (err: any) {
229+
console.error(err);
230+
notification.error({
231+
message: "Installation failed",
232+
description: `Failed to install ${plugin.title}: ${err?.message || ""}`,
233+
placement: "bottomRight",
234+
});
235+
}
236+
});
237+
238+
await Promise.all(installs);
239+
setBulkInstalling(false);
240+
},
241+
[updatePluginRefreshCounter],
242+
);
243+
244+
return {
245+
refreshMap,
246+
isBulkInstalling,
247+
handleInstall,
248+
handleModify,
249+
installAllPlugins,
250+
updatePluginRefreshCounter,
251+
};
252+
};

0 commit comments

Comments
 (0)