|
1 | | -import { useCallback, useEffect, useState } from "react"; |
2 | | -import YAML from "yaml"; |
| 1 | +import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query"; |
| 2 | + |
| 3 | +import type { UseMutationResult } from "@tanstack/react-query"; |
3 | 4 |
|
4 | 5 | import { usePluginContext } from "@cortexapps/plugin-core/components"; |
5 | 6 |
|
6 | | -export interface UsePluginConfigReturn { |
| 7 | +export interface UseEntityDescriptorProps { |
| 8 | + entityTag: string; |
| 9 | + mutationMethod?: "PATCH" | "POST"; |
| 10 | + onMutateSuccess?: (data: any, variables: any, context?: any) => void; |
| 11 | + onMutateError?: (error: Error, variables: any, context?: any) => void; |
| 12 | + onMutateSettled?: ( |
| 13 | + data: any, |
| 14 | + error: Error, |
| 15 | + variables: any, |
| 16 | + context?: any |
| 17 | + ) => void; |
| 18 | + onMutate?: (variables: any) => void; |
| 19 | +} |
| 20 | + |
| 21 | +export interface UseEntityDescriptorReturn { |
| 22 | + entity: any; |
7 | 23 | isLoading: boolean; |
8 | | - pluginConfig: any | null; |
9 | | - savePluginConfig: (config: any) => Promise<void>; |
10 | | - refreshPluginConfig: () => void; |
| 24 | + isFetching: boolean; |
| 25 | + error: unknown; |
| 26 | + updateEntity: UseMutationResult<any, Error, any>["mutate"]; |
| 27 | + isMutating: boolean; |
11 | 28 | } |
12 | 29 |
|
13 | | -export const usePluginConfig = (): UsePluginConfigReturn => { |
| 30 | +export const useEntityDescriptor = ({ |
| 31 | + entityTag, |
| 32 | + mutationMethod = "POST", |
| 33 | + onMutateSuccess = () => {}, |
| 34 | + onMutateError = () => {}, |
| 35 | + onMutateSettled = () => {}, |
| 36 | + onMutate = () => {}, |
| 37 | +}: UseEntityDescriptorProps): UseEntityDescriptorReturn => { |
14 | 38 | const { apiBaseUrl } = usePluginContext(); |
15 | 39 |
|
16 | | - const [refreshCounter, setRefreshCounter] = useState(0); |
17 | | - const [isLoading, setIsLoading] = useState(true); |
18 | | - const [pluginConfig, setPluginConfig] = useState<any | null>(null); |
19 | | - |
20 | | - useEffect(() => { |
21 | | - const fetchPluginConfig = async (): Promise<void> => { |
22 | | - setIsLoading(true); |
23 | | - setPluginConfig(null); |
24 | | - try { |
25 | | - const response = await fetch( |
26 | | - `${apiBaseUrl}/catalog/info-cards-plugin-config/openapi` |
27 | | - ); |
28 | | - const config = await response.json(); |
29 | | - setPluginConfig(config); |
30 | | - } catch (error) { |
31 | | - console.error(error); |
32 | | - } finally { |
33 | | - setIsLoading(false); |
34 | | - } |
35 | | - }; |
36 | | - void fetchPluginConfig(); |
37 | | - }, [apiBaseUrl, refreshCounter]); |
38 | | - |
39 | | - const savePluginConfig = useCallback( |
40 | | - async (config: any) => { |
41 | | - let existingConfig: any; |
| 40 | + const queryClient = useQueryClient(); |
42 | 41 |
|
43 | | - // Fetch existing config if it exists |
44 | | - try { |
45 | | - const r = await fetch( |
46 | | - `${apiBaseUrl}/catalog/info-cards-plugin-config/openapi` |
47 | | - ); |
48 | | - if (!r.ok) { |
49 | | - throw new Error("Failed to fetch existing config"); |
50 | | - } |
51 | | - existingConfig = await r.json(); |
52 | | - } catch (error) {} |
| 42 | + const query = useQuery({ |
| 43 | + queryKey: ["entityDescriptor", entityTag], |
| 44 | + queryFn: async () => { |
| 45 | + const response = await fetch( |
| 46 | + `${apiBaseUrl}/catalog/${entityTag}/openapi` |
| 47 | + ); |
| 48 | + return await response.json(); |
| 49 | + }, |
| 50 | + enabled: !!apiBaseUrl, |
| 51 | + retry: false, |
| 52 | + }); |
53 | 53 |
|
54 | | - // Validate the passed in config |
55 | | - if (!config.info?.["x-cortex-definition"]?.infoRows) { |
56 | | - // this should never happen since the plugin should always pass in a valid config |
57 | | - console.error("Invalid config", config); |
58 | | - throw new Error("Invalid config"); |
| 54 | + const mutation = useMutation({ |
| 55 | + mutationFn: async (data: any) => { |
| 56 | + // throw if the data is not an object or data.info is not an object |
| 57 | + if (typeof data !== "object" || typeof data.info !== "object") { |
| 58 | + throw new Error("Invalid data format"); |
59 | 59 | } |
60 | | - |
61 | | - config.info["x-cortex-tag"] = "info-cards-plugin-config"; |
62 | | - config.info.title = "Info Cards Plugin Configuration"; |
63 | | - config.openapi = "3.0.1"; |
64 | | - |
65 | | - // Preserve the existing x-cortex-type if it exists |
66 | | - config.info["x-cortex-type"] = |
67 | | - existingConfig?.info?.["x-cortex-type"] || "plugin-configuration"; |
68 | | - |
69 | | - // See if the entity type exists, if not create it |
70 | | - try { |
71 | | - const r = await fetch( |
72 | | - `${apiBaseUrl}/catalog/definitions/${ |
73 | | - config.info["x-cortex-type"] as string |
74 | | - }` |
75 | | - ); |
76 | | - if (!r.ok) { |
77 | | - throw new Error("Failed to fetch existing entity type"); |
78 | | - } |
79 | | - } catch (error) { |
80 | | - // Create the entity type |
81 | | - const entityTypeBody = { |
82 | | - iconTag: "bucket", |
83 | | - name: "Plugin Configuration", |
84 | | - schema: { properties: {}, required: [] }, |
85 | | - type: config.info["x-cortex-type"], |
86 | | - }; |
87 | | - const entityTypeResponse = await fetch( |
88 | | - `${apiBaseUrl}/catalog/definitions`, |
89 | | - { |
90 | | - method: "POST", |
91 | | - headers: { |
92 | | - "Content-Type": "application/json", |
93 | | - }, |
94 | | - body: JSON.stringify(entityTypeBody), |
95 | | - } |
96 | | - ); |
97 | | - if (!entityTypeResponse.ok) { |
98 | | - throw new Error("Failed to create entity type"); |
99 | | - } |
| 60 | + // make sure basic info is set |
| 61 | + data.openapi = "3.0.1"; |
| 62 | + // don't allow changing the tag |
| 63 | + data.info["x-cortex-tag"] = entityTag; |
| 64 | + // set a title if it's not set |
| 65 | + if (!data.info.title) { |
| 66 | + data.info.title = entityTag |
| 67 | + .replace(/-/g, " ") |
| 68 | + .replace(/\b\w/g, (l) => l.toUpperCase()); |
100 | 69 | } |
101 | | - |
102 | | - // Save the new config |
103 | | - await fetch(`${apiBaseUrl}/open-api`, { |
104 | | - method: "POST", |
| 70 | + const response = await fetch(`${apiBaseUrl}/open-api`, { |
| 71 | + method: mutationMethod, |
105 | 72 | headers: { |
106 | 73 | "Content-Type": "application/openapi;charset=utf-8", |
107 | 74 | }, |
108 | | - body: YAML.stringify(config), |
| 75 | + body: JSON.stringify(data), |
109 | 76 | }); |
110 | | - |
111 | | - setRefreshCounter((prev) => prev + 1); |
| 77 | + return await response.json(); |
112 | 78 | }, |
113 | | - [apiBaseUrl] |
114 | | - ); |
115 | | - |
116 | | - const refreshPluginConfig = useCallback(() => { |
117 | | - setRefreshCounter((prev) => prev + 1); |
118 | | - }, []); |
| 79 | + onMutate, |
| 80 | + onError: onMutateError, |
| 81 | + onSettled: onMutateSettled, |
| 82 | + onSuccess: (data, variables, context) => { |
| 83 | + void queryClient.invalidateQueries({ |
| 84 | + queryKey: ["entityDescriptor", entityTag], |
| 85 | + }); |
| 86 | + onMutateSuccess(data, variables, context); |
| 87 | + }, |
| 88 | + }); |
119 | 89 |
|
120 | 90 | return { |
121 | | - isLoading, |
122 | | - pluginConfig, |
123 | | - savePluginConfig, |
124 | | - refreshPluginConfig, |
| 91 | + entity: query.data, |
| 92 | + isLoading: query.isLoading, |
| 93 | + isFetching: query.isFetching, |
| 94 | + error: query.error, |
| 95 | + updateEntity: mutation.mutate, |
| 96 | + isMutating: mutation.isPending, |
125 | 97 | }; |
126 | 98 | }; |
0 commit comments