From 56b67f155c8ff7d099c153523651435ede460832 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Sun, 10 Nov 2024 11:46:05 +0200 Subject: [PATCH 1/4] feat: added useSourceCRUD hook --- .../components/overview/add-entity/index.tsx | 3 +- .../choose-source-modal/index.tsx | 33 ++---- .../sources/source-drawer-container/index.tsx | 38 ++----- frontend/webapp/graphql/mutations/source.ts | 15 +-- .../compute-platform/useComputePlatform.ts | 27 ++++- .../hooks/compute-platform/useNamespace.ts | 47 ++++---- frontend/webapp/hooks/setup/useConnectEnv.ts | 38 ++----- frontend/webapp/hooks/sources/index.ts | 2 +- .../webapp/hooks/sources/useActualSources.ts | 67 +----------- .../webapp/hooks/sources/usePersistSource.ts | 46 -------- .../webapp/hooks/sources/useSourceCRUD.ts | 100 ++++++++++++++++++ .../webapp/hooks/sources/useUpdateSource.ts | 35 ------ frontend/webapp/store/index.ts | 1 + 13 files changed, 167 insertions(+), 285 deletions(-) delete mode 100644 frontend/webapp/hooks/sources/usePersistSource.ts create mode 100644 frontend/webapp/hooks/sources/useSourceCRUD.ts delete mode 100644 frontend/webapp/hooks/sources/useUpdateSource.ts diff --git a/frontend/webapp/components/overview/add-entity/index.tsx b/frontend/webapp/components/overview/add-entity/index.tsx index 7f85ff260..0a0f71f2e 100644 --- a/frontend/webapp/components/overview/add-entity/index.tsx +++ b/frontend/webapp/components/overview/add-entity/index.tsx @@ -1,10 +1,9 @@ import Image from 'next/image'; import theme from '@/styles/theme'; -import { useModalStore } from '@/store'; import { useOnClickOutside } from '@/hooks'; import React, { useState, useRef } from 'react'; import styled, { css } from 'styled-components'; -import { useBooleanStore } from '@/store/useBooleanStore'; +import { useBooleanStore, useModalStore } from '@/store'; import { DropdownOption, OVERVIEW_ENTITY_TYPES } from '@/types'; import { Button, FadeLoader, Text } from '@/reuseable-components'; diff --git a/frontend/webapp/containers/main/sources/choose-sources/choose-source-modal/index.tsx b/frontend/webapp/containers/main/sources/choose-sources/choose-source-modal/index.tsx index 4e96a1097..ffd2e8cc9 100644 --- a/frontend/webapp/containers/main/sources/choose-sources/choose-source-modal/index.tsx +++ b/frontend/webapp/containers/main/sources/choose-sources/choose-source-modal/index.tsx @@ -1,8 +1,8 @@ import React, { useState, useCallback } from 'react'; +import type { K8sActualSource } from '@/types'; import { ChooseSourcesBody } from '../choose-sources-body'; import { Modal, NavigationButtons } from '@/reuseable-components'; -import { K8sActualSource, PersistNamespaceItemInput } from '@/types'; -import { useActualSources, useConnectSourcesMenuState } from '@/hooks'; +import { useConnectSourcesMenuState, useSourceCRUD } from '@/hooks'; interface AddSourceModalProps { isOpen: boolean; @@ -12,33 +12,12 @@ interface AddSourceModalProps { export const AddSourceModal: React.FC = ({ isOpen, onClose }) => { const [sourcesList, setSourcesList] = useState([]); const { stateMenu, stateHandlers } = useConnectSourcesMenuState({ sourcesList }); - const { createSourcesForNamespace, persistNamespaceItems } = useActualSources(); + const { createSources } = useSourceCRUD(); const handleNextClick = useCallback(async () => { - try { - const namespaceItems: PersistNamespaceItemInput[] = Object.entries(stateMenu.futureAppsCheckbox).map(([namespaceName, futureSelected]) => ({ - name: namespaceName, - futureSelected, - })); - - await persistNamespaceItems(namespaceItems); - - await Promise.all( - Object.entries(stateMenu.selectedItems).map(async ([namespaceName, sources]) => { - const formattedSources = sources.map((source) => ({ - kind: source.kind, - name: source.name, - selected: true, - })); - await createSourcesForNamespace(namespaceName, formattedSources); - }) - ); - - onClose(); - } catch (error) { - console.error('Error during handleNextClick:', error); - } - }, [stateMenu, onClose, createSourcesForNamespace, persistNamespaceItems]); + await createSources(stateMenu.selectedItems, stateMenu.futureAppsCheckbox); + onClose(); + }, [stateMenu, createSources, onClose]); return ( { - const { selectedItem, setSelectedItem } = useDrawerStore((store) => store); + const selectedItem = useDrawerStore(({ selectedItem }) => selectedItem); const [isEditing, setIsEditing] = useState(false); const [isFormDirty, setIsFormDirty] = useState(false); - const { updateActualSource, deleteSourcesForNamespace } = useActualSources(); + const { deleteSources, updateSource } = useSourceCRUD(); const cardData = useMemo(() => { if (!selectedItem) return []; @@ -47,41 +47,15 @@ const SourceDrawer: React.FC = () => { }; const handleDelete = async () => { - const { namespace, name, kind } = item as K8sActualSource; + const { namespace } = item as K8sActualSource; - try { - await deleteSourcesForNamespace(namespace, [ - { - kind, - name, - selected: false, - }, - ]); - setSelectedItem(null); - } catch (error) { - console.error('Error deleting source:', error); - } + await deleteSources({ [namespace]: [item as K8sActualSource] }); }; const handleSave = async (newTitle: string) => { const { namespace, name, kind } = item as K8sActualSource; - const sourceId: WorkloadId = { - namespace, - kind, - name, - }; - - const patchRequest: PatchSourceRequestInput = { - reportedName: newTitle, - }; - - try { - await updateActualSource(sourceId, patchRequest); - setSelectedItem(null); - } catch (error) { - console.error('Error updating source:', error); - } + await updateSource({ namespace, kind, name }, { reportedName: newTitle }); }; return ( diff --git a/frontend/webapp/graphql/mutations/source.ts b/frontend/webapp/graphql/mutations/source.ts index 647258dd6..b2e199517 100644 --- a/frontend/webapp/graphql/mutations/source.ts +++ b/frontend/webapp/graphql/mutations/source.ts @@ -1,22 +1,13 @@ import { gql } from '@apollo/client'; export const PERSIST_SOURCE = gql` - mutation PersistSources( - $namespace: String! - $sources: [PersistNamespaceSourceInput!]! - ) { + mutation PersistSources($namespace: String!, $sources: [PersistNamespaceSourceInput!]!) { persistK8sSources(namespace: $namespace, sources: $sources) } `; export const UPDATE_K8S_ACTUAL_SOURCE = gql` - mutation UpdateK8sActualSource( - $sourceId: K8sSourceId! - $patchSourceRequest: PatchSourceRequestInput! - ) { - updateK8sActualSource( - sourceId: $sourceId - patchSourceRequest: $patchSourceRequest - ) + mutation UpdateK8sActualSource($sourceId: K8sSourceId!, $patchSourceRequest: PatchSourceRequestInput!) { + updateK8sActualSource(sourceId: $sourceId, patchSourceRequest: $patchSourceRequest) } `; diff --git a/frontend/webapp/hooks/compute-platform/useComputePlatform.ts b/frontend/webapp/hooks/compute-platform/useComputePlatform.ts index 1ebcc42b0..7d2e2a26a 100644 --- a/frontend/webapp/hooks/compute-platform/useComputePlatform.ts +++ b/frontend/webapp/hooks/compute-platform/useComputePlatform.ts @@ -1,5 +1,7 @@ -import { ComputePlatform } from '@/types'; +import { useCallback } from 'react'; import { useQuery } from '@apollo/client'; +import { useBooleanStore } from '@/store'; +import type { ComputePlatform } from '@/types'; import { GET_COMPUTE_PLATFORM } from '@/graphql'; type UseComputePlatformHook = { @@ -7,11 +9,28 @@ type UseComputePlatformHook = { loading: boolean; error?: Error; refetch: () => void; + startPolling: () => Promise; }; export const useComputePlatform = (): UseComputePlatformHook => { - const { data, loading, error, refetch } = - useQuery(GET_COMPUTE_PLATFORM); + const { data, loading, error, refetch } = useQuery(GET_COMPUTE_PLATFORM); + const { togglePolling } = useBooleanStore(); - return { data, loading, error, refetch }; + const startPolling = useCallback(async () => { + togglePolling(true); + + const maxRetries = 5; + const retryInterval = 1000; // Poll every second + let retries = 0; + + while (retries < maxRetries) { + await new Promise((resolve) => setTimeout(resolve, retryInterval)); + refetch(); + retries++; + } + + togglePolling(false); + }, [refetch, togglePolling]); + + return { data, loading, error, refetch, startPolling }; }; diff --git a/frontend/webapp/hooks/compute-platform/useNamespace.ts b/frontend/webapp/hooks/compute-platform/useNamespace.ts index ef83e1b97..784af8c5d 100644 --- a/frontend/webapp/hooks/compute-platform/useNamespace.ts +++ b/frontend/webapp/hooks/compute-platform/useNamespace.ts @@ -1,42 +1,33 @@ import { useMutation, useQuery } from '@apollo/client'; import { GET_NAMESPACES, PERSIST_NAMESPACE } from '@/graphql'; -import { - ComputePlatform, - K8sActualNamespace, - PersistNamespaceItemInput, -} from '@/types'; +import { ComputePlatform, PersistNamespaceItemInput } from '@/types'; +import { useNotify } from '../useNotify'; +import { NOTIFICATION } from '@/utils'; -type UseNamespaceHook = { - data?: K8sActualNamespace; - loading: boolean; - error?: Error; - persistNamespace: (namespace: PersistNamespaceItemInput) => Promise; -}; +export const useNamespace = (namespaceName?: string, instrumentationLabeled = null as boolean | null) => { + const notify = useNotify(); + + const handleError = (title: string, message: string) => { + notify({ type: NOTIFICATION.ERROR, title, message }); + }; + + const handleComplete = (title: string, message: string) => { + notify({ type: NOTIFICATION.SUCCESS, title, message }); + }; -export const useNamespace = ( - namespaceName: string | undefined, - instrumentationLabeled = null as boolean | null -): UseNamespaceHook => { const { data, loading, error } = useQuery(GET_NAMESPACES, { skip: !namespaceName, - variables: { namespaceName, instrumentationLabeled }, fetchPolicy: 'cache-first', + variables: { namespaceName, instrumentationLabeled }, }); - const [persistNamespaceMutation] = useMutation(PERSIST_NAMESPACE); - - const persistNamespace = async (namespace: PersistNamespaceItemInput) => { - try { - await persistNamespaceMutation({ - variables: { namespace }, - }); - } catch (e) { - console.error('Error persisting namespace:', e); - } - }; + const [persistNamespaceMutation] = useMutation(PERSIST_NAMESPACE, { + onError: (error) => handleError('', error.message), + onCompleted: (res, req) => {}, + }); return { - persistNamespace, + persistNamespace: async (namespace: PersistNamespaceItemInput) => await persistNamespaceMutation({ variables: { namespace } }), data: data?.computePlatform.k8sActualNamespace, loading, error, diff --git a/frontend/webapp/hooks/setup/useConnectEnv.ts b/frontend/webapp/hooks/setup/useConnectEnv.ts index 807895eb1..c8024c238 100644 --- a/frontend/webapp/hooks/setup/useConnectEnv.ts +++ b/frontend/webapp/hooks/setup/useConnectEnv.ts @@ -1,6 +1,6 @@ import { useAppStore } from '@/store'; import { DestinationInput } from '@/types'; -import { useActualSources } from '../sources'; +import { useSourceCRUD } from '../sources'; import { useState, useCallback } from 'react'; import { useDestinationCRUD } from '../destinations'; @@ -10,8 +10,8 @@ type ConnectEnvResult = { }; export const useConnectEnv = () => { + const { createSources } = useSourceCRUD(); const { createDestination } = useDestinationCRUD(); - const { createSourcesForNamespace, persistNamespaceItems } = useActualSources(); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); @@ -28,48 +28,22 @@ export const useConnectEnv = () => { setResult(null); try { - // Persist namespaces based on namespaceFutureSelectAppsList - const namespaceItems = Object.entries(namespaceFutureSelectAppsList).map(([namespaceName, futureSelected]) => ({ - name: namespaceName, - futureSelected, - })); - - await persistNamespaceItems(namespaceItems); - - // Create sources for each namespace in sourcesList - for (const namespaceName in sourcesList) { - const sources = sourcesList[namespaceName].map((source) => ({ - kind: source.kind, - name: source.name, - selected: true, - })); - await createSourcesForNamespace(namespaceName, sources); - } - + await createSources(sourcesList, namespaceFutureSelectAppsList); resetSources(); - // Create destination const { data } = await createDestination(destination); const destinationId = data?.createNewDestination.id; - if (!destinationId) { - throw new Error('Error creating destination.'); - } callback && callback(); - setResult({ - success: true, - destinationId, - }); + setResult({ success: true, destinationId }); } catch (err) { setError((err as Error).message); - setResult({ - success: false, - }); + setResult({ success: false }); } finally { setLoading(false); } }, - [sourcesList, createDestination, persistNamespaceItems, createSourcesForNamespace, namespaceFutureSelectAppsList] + [sourcesList, namespaceFutureSelectAppsList, createSources, resetSources, createDestination], ); return { diff --git a/frontend/webapp/hooks/sources/index.ts b/frontend/webapp/hooks/sources/index.ts index 909719e76..b0b6b4652 100644 --- a/frontend/webapp/hooks/sources/index.ts +++ b/frontend/webapp/hooks/sources/index.ts @@ -1,5 +1,5 @@ export * from './useSources'; export * from './useConnectSourcesMenuState'; export * from './useConnectSourcesList'; -export * from './usePersistSource'; export * from './useActualSources'; +export * from './useSourceCRUD'; diff --git a/frontend/webapp/hooks/sources/useActualSources.ts b/frontend/webapp/hooks/sources/useActualSources.ts index ed3b59c09..15ef9fc85 100644 --- a/frontend/webapp/hooks/sources/useActualSources.ts +++ b/frontend/webapp/hooks/sources/useActualSources.ts @@ -1,74 +1,9 @@ -import { useCallback } from 'react'; -import { usePersistSource } from '../sources'; -import { useNamespace } from '../compute-platform'; -import { useUpdateSource } from './useUpdateSource'; import { useComputePlatform } from '../compute-platform'; -import { useBooleanStore } from '@/store/useBooleanStore'; -import { PatchSourceRequestInput, PersistSourcesArray, WorkloadId } from '@/types'; export function useActualSources() { - const { data, refetch } = useComputePlatform(); - const { isPolling, togglePolling } = useBooleanStore(); - const { persistSource, error: sourceError } = usePersistSource(); - const { updateSource, error: updateError } = useUpdateSource(); - - const { persistNamespace } = useNamespace(undefined); - - const startPolling = useCallback(async () => { - togglePolling(true); - const maxRetries = 5; - const retryInterval = 1000; // Poll every second - let retries = 0; - - while (retries < maxRetries) { - await new Promise((resolve) => setTimeout(resolve, retryInterval)); - refetch(); - retries++; - } - - togglePolling(false); - }, [refetch, togglePolling]); - - const createSourcesForNamespace = async (namespaceName: string, sources: PersistSourcesArray[]) => { - await persistSource(namespaceName, sources); - - startPolling(); - if (sourceError) { - throw new Error(`Error creating sources for namespace: ${namespaceName}`); - } - }; - - const deleteSourcesForNamespace = async (namespaceName: string, sources: PersistSourcesArray[]) => { - await persistSource(namespaceName, sources); - - startPolling(); - if (sourceError) { - throw new Error(`Error creating sources for namespace: ${namespaceName}`); - } - }; - - const updateActualSource = async (sourceId: WorkloadId, patchRequest: PatchSourceRequestInput) => { - try { - await updateSource(sourceId, patchRequest); - refetch(); - } catch (error) { - console.error('Error updating source:', error); - throw error; - } - }; - - const persistNamespaceItems = async (namespaceItems) => { - for (const namespace of namespaceItems) { - await persistNamespace(namespace); - } - }; + const { data } = useComputePlatform(); return { sources: data?.computePlatform.k8sActualSources || [], - deleteSourcesForNamespace, - createSourcesForNamespace, - persistNamespaceItems, - updateActualSource, - isPolling, }; } diff --git a/frontend/webapp/hooks/sources/usePersistSource.ts b/frontend/webapp/hooks/sources/usePersistSource.ts deleted file mode 100644 index 85c4c6820..000000000 --- a/frontend/webapp/hooks/sources/usePersistSource.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useState } from 'react'; -import { PERSIST_SOURCE } from '@/graphql'; -import { useMutation } from '@apollo/client'; -import { PersistSourcesArray } from '@/types'; - -type PersistSourceResponse = { - persistK8sSources: boolean; -}; - -type PersistSourceVariables = { - namespace: string; - sources: PersistSourcesArray[]; -}; - -export const usePersistSource = () => { - const [persistSourceMutation, { data, loading, error }] = useMutation< - PersistSourceResponse, - PersistSourceVariables - >(PERSIST_SOURCE); - - const [success, setSuccess] = useState(null); - - const persistSource = async ( - namespace: string, - sources: PersistSourcesArray[] - ) => { - try { - const result = await persistSourceMutation({ - variables: { - namespace, - sources, - }, - }); - setSuccess(result.data?.persistK8sSources ?? false); - } catch (err) { - setSuccess(false); - } - }; - - return { - persistSource, - success, - loading, - error, - }; -}; diff --git a/frontend/webapp/hooks/sources/useSourceCRUD.ts b/frontend/webapp/hooks/sources/useSourceCRUD.ts new file mode 100644 index 000000000..0a02dbee5 --- /dev/null +++ b/frontend/webapp/hooks/sources/useSourceCRUD.ts @@ -0,0 +1,100 @@ +import { useDrawerStore } from '@/store'; +import { useNotify } from '../useNotify'; +import { useMutation } from '@apollo/client'; +import { ACTION, getSseTargetFromId, NOTIFICATION } from '@/utils'; +import { PERSIST_SOURCE, UPDATE_K8S_ACTUAL_SOURCE } from '@/graphql'; +import { useComputePlatform, useNamespace } from '../compute-platform'; +import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type NotificationType, PersistSourcesArray, PatchSourceRequestInput, K8sActualSource } from '@/types'; + +interface Params { + onSuccess?: () => void; + onError?: () => void; +} + +export const useSourceCRUD = (params?: Params) => { + const { setSelectedItem: setDrawerItem } = useDrawerStore((store) => store); + const { persistNamespace } = useNamespace(); + const { startPolling } = useComputePlatform(); + const notify = useNotify(); + + const notifyUser = (type: NotificationType, title: string, message: string, id?: WorkloadId) => { + notify({ + type, + title, + message, + crdType: OVERVIEW_ENTITY_TYPES.SOURCE, + target: id ? getSseTargetFromId(id, OVERVIEW_ENTITY_TYPES.SOURCE) : undefined, + }); + }; + + const handleError = (title: string, message: string, id?: WorkloadId) => { + notifyUser(NOTIFICATION.ERROR, title, message, id); + params?.onError?.(); + }; + + const handleComplete = (title: string, message: string, id?: WorkloadId) => { + notifyUser(NOTIFICATION.SUCCESS, title, message, id); + setDrawerItem(null); + startPolling(); + params?.onSuccess?.(); + }; + + const [createOrDeleteSources, cdState] = useMutation<{ persistK8sSources: boolean }>(PERSIST_SOURCE, { + onError: (error) => handleError(ACTION.CREATE, error.message), + onCompleted: (res, req) => { + const namespace = req?.variables?.namespace; + const { name, kind, selected } = req?.variables?.sources[0]; + + const count = req?.variables?.sources.length; + const action = selected ? ACTION.CREATE : ACTION.DELETE; + const fromOrIn = selected ? 'in' : 'from'; + + if (count > 1) { + handleComplete(action, `${count} sources were ${action.toLowerCase()}d ${fromOrIn} "${namespace}"`); + } else { + const id = { kind, name, namespace }; + handleComplete(action, `source "${name}" was ${action.toLowerCase()}d ${fromOrIn} "${namespace}"`, id); + } + }, + }); + + const [updateSource, uState] = useMutation<{ updateK8sActualSource: boolean }>(UPDATE_K8S_ACTUAL_SOURCE, { + onError: (error) => handleError(ACTION.UPDATE, error.message), + onCompleted: (res, req) => { + const id = req?.variables?.sourceId; + const name = id?.name; + handleComplete(ACTION.UPDATE, `source "${name}" was updated`, id); + }, + }); + + const persistNamespaces = async (items: { [key: string]: boolean }) => { + for (const [namespace, futureSelected] of Object.entries(items)) { + await persistNamespace({ name: namespace, futureSelected }); + } + }; + + const persistSources = async (items: { [key: string]: K8sActualSource[] }, selected: boolean) => { + for (const [namespace, sources] of Object.entries(items)) { + await createOrDeleteSources({ + variables: { + namespace, + sources: sources.map((source) => ({ + kind: source.kind, + name: source.name, + selected, + })), + }, + }); + } + }; + + return { + loading: cdState.loading || uState.loading, + createSources: async (selectAppsList: { [key: string]: K8sActualSource[] }, futureSelectAppsList: { [key: string]: boolean }) => { + await persistNamespaces(futureSelectAppsList); + await persistSources(selectAppsList, true); + }, + updateSource: async (sourceId: WorkloadId, patchSourceRequest: PatchSourceRequestInput) => await updateSource({ variables: { sourceId, patchSourceRequest } }), + deleteSources: async (selectAppsList: { [key: string]: K8sActualSource[] }) => await persistSources(selectAppsList, false), + }; +}; diff --git a/frontend/webapp/hooks/sources/useUpdateSource.ts b/frontend/webapp/hooks/sources/useUpdateSource.ts deleted file mode 100644 index 48c60c030..000000000 --- a/frontend/webapp/hooks/sources/useUpdateSource.ts +++ /dev/null @@ -1,35 +0,0 @@ -// sources.ts -import { useMutation } from '@apollo/client'; -import { UPDATE_K8S_ACTUAL_SOURCE } from '@/graphql'; -import { WorkloadId, PatchSourceRequestInput } from '@/types'; - -export function useUpdateSource() { - const [updateSourceMutation, { data, loading, error }] = useMutation( - UPDATE_K8S_ACTUAL_SOURCE - ); - - const updateSource = async ( - sourceId: WorkloadId, - patchSourceRequest: PatchSourceRequestInput - ) => { - try { - const response = await updateSourceMutation({ - variables: { - sourceId, - patchSourceRequest, - }, - }); - return response; - } catch (err) { - console.error('Error updating source:', err); - throw err; - } - }; - - return { - updateSource, - data, - loading, - error, - }; -} diff --git a/frontend/webapp/store/index.ts b/frontend/webapp/store/index.ts index d9e795740..6c820834c 100644 --- a/frontend/webapp/store/index.ts +++ b/frontend/webapp/store/index.ts @@ -1,4 +1,5 @@ export * from './useAppStore'; +export * from './useBooleanStore'; export * from './useDrawerStore'; export * from './useModalStore'; export * from './useNotificationStore'; From 00503261f39659484e1ccdcc4e8ace7d9a677555 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Sun, 10 Nov 2024 11:48:48 +0200 Subject: [PATCH 2/4] perf: onClose via hook params --- .../sources/choose-sources/choose-source-modal/index.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/webapp/containers/main/sources/choose-sources/choose-source-modal/index.tsx b/frontend/webapp/containers/main/sources/choose-sources/choose-source-modal/index.tsx index ffd2e8cc9..b55e15bd5 100644 --- a/frontend/webapp/containers/main/sources/choose-sources/choose-source-modal/index.tsx +++ b/frontend/webapp/containers/main/sources/choose-sources/choose-source-modal/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback } from 'react'; +import React, { useState } from 'react'; import type { K8sActualSource } from '@/types'; import { ChooseSourcesBody } from '../choose-sources-body'; import { Modal, NavigationButtons } from '@/reuseable-components'; @@ -12,12 +12,11 @@ interface AddSourceModalProps { export const AddSourceModal: React.FC = ({ isOpen, onClose }) => { const [sourcesList, setSourcesList] = useState([]); const { stateMenu, stateHandlers } = useConnectSourcesMenuState({ sourcesList }); - const { createSources } = useSourceCRUD(); + const { createSources } = useSourceCRUD({ onSuccess: onClose }); - const handleNextClick = useCallback(async () => { + const handleNextClick = async () => { await createSources(stateMenu.selectedItems, stateMenu.futureAppsCheckbox); - onClose(); - }, [stateMenu, createSources, onClose]); + }; return ( Date: Sun, 10 Nov 2024 11:58:32 +0200 Subject: [PATCH 3/4] perf: read items from within CRUD hooks --- .../components/notification/toast-list.tsx | 19 ++++------- .../overview/overview-data-flow/index.tsx | 21 ++++-------- frontend/webapp/hooks/actions/index.ts | 1 - .../webapp/hooks/actions/useActionCRUD.ts | 13 ++++++-- .../webapp/hooks/actions/useGetActions.ts | 20 ------------ frontend/webapp/hooks/destinations/index.ts | 1 - .../destinations/useActualDestinations.ts | 32 ------------------- .../hooks/destinations/useDestinationCRUD.ts | 4 ++- .../hooks/instrumentation-rules/index.ts | 1 - .../useGetInstrumentationRules.ts | 18 ----------- .../useInstrumentationRuleCRUD.ts | 9 +++++- frontend/webapp/hooks/sources/index.ts | 1 - .../webapp/hooks/sources/useActualSources.ts | 9 ------ .../webapp/hooks/sources/useSourceCRUD.ts | 6 ++-- 14 files changed, 38 insertions(+), 117 deletions(-) delete mode 100644 frontend/webapp/hooks/actions/useGetActions.ts delete mode 100644 frontend/webapp/hooks/destinations/useActualDestinations.ts delete mode 100644 frontend/webapp/hooks/instrumentation-rules/useGetInstrumentationRules.ts delete mode 100644 frontend/webapp/hooks/sources/useActualSources.ts diff --git a/frontend/webapp/components/notification/toast-list.tsx b/frontend/webapp/components/notification/toast-list.tsx index 8a6a3dc54..1c71d4864 100644 --- a/frontend/webapp/components/notification/toast-list.tsx +++ b/frontend/webapp/components/notification/toast-list.tsx @@ -1,10 +1,10 @@ import React from 'react'; import styled from 'styled-components'; +import { getIdFromSseTarget } from '@/utils'; import { NotificationNote } from '@/reuseable-components'; import { Notification, OVERVIEW_ENTITY_TYPES } from '@/types'; import { DrawerBaseItem, useDrawerStore, useNotificationStore } from '@/store'; -import { useActualDestination, useActualSources, useGetActions, useGetInstrumentationRules } from '@/hooks'; -import { getIdFromSseTarget } from '@/utils'; +import { useActionCRUD, useDestinationCRUD, useInstrumentationRuleCRUD, useSourceCRUD } from '@/hooks'; const Container = styled.div` position: fixed; @@ -35,10 +35,10 @@ export const ToastList: React.FC = () => { const Toast: React.FC = ({ id, type, title, message, crdType, target }) => { const { markAsDismissed, markAsSeen } = useNotificationStore(); - const { actions } = useGetActions(); - const { sources } = useActualSources(); - const { destinations } = useActualDestination(); - const { instrumentationRules } = useGetInstrumentationRules(); + const { sources } = useSourceCRUD(); + const { actions } = useActionCRUD(); + const { destinations } = useDestinationCRUD(); + const { instrumentationRules } = useInstrumentationRuleCRUD(); const setSelectedItem = useDrawerStore(({ setSelectedItem }) => setSelectedItem); const onClick = () => { @@ -57,12 +57,7 @@ const Toast: React.FC = ({ id, type, title, message, crdType, targ case 'InstrumentationInstance': drawerItem['type'] = OVERVIEW_ENTITY_TYPES.SOURCE; drawerItem['id'] = getIdFromSseTarget(target, OVERVIEW_ENTITY_TYPES.SOURCE); - drawerItem['item'] = sources.find( - (item) => - item.kind === drawerItem['id']?.['kind'] && - item.name === drawerItem['id']?.['name'] && - item.namespace === drawerItem['id']?.['namespace'] - ); + drawerItem['item'] = sources.find((item) => item.kind === drawerItem['id']?.['kind'] && item.name === drawerItem['id']?.['name'] && item.namespace === drawerItem['id']?.['namespace']); break; case OVERVIEW_ENTITY_TYPES.ACTION: diff --git a/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx b/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx index b5f7285e3..ef747659a 100644 --- a/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx +++ b/frontend/webapp/containers/main/overview/overview-data-flow/index.tsx @@ -4,17 +4,8 @@ import dynamic from 'next/dynamic'; import styled from 'styled-components'; import { ToastList } from '@/components'; import { OverviewActionMenuContainer } from '../overview-actions-menu'; -import { buildNodesAndEdges, NodeBaseDataFlow, } from '@/reuseable-components'; -import { - useMetrics, - useGetActions, - useActualSources, - useContainerWidth, - useActualDestination, - useNodeDataFlowHandlers, - useGetInstrumentationRules, -} from '@/hooks'; - +import { buildNodesAndEdges, NodeBaseDataFlow } from '@/reuseable-components'; +import { useMetrics, useContainerWidth, useNodeDataFlowHandlers, useSourceCRUD, useDestinationCRUD, useInstrumentationRuleCRUD, useActionCRUD } from '@/hooks'; const AllDrawers = dynamic(() => import('../all-drawers'), { ssr: false, @@ -35,10 +26,10 @@ const NODE_HEIGHT = 80; export function OverviewDataFlowContainer() { const { metrics } = useMetrics(); - const { actions } = useGetActions(); - const { sources } = useActualSources(); - const { destinations } = useActualDestination(); - const { instrumentationRules } = useGetInstrumentationRules(); + const { sources } = useSourceCRUD(); + const { actions } = useActionCRUD(); + const { destinations } = useDestinationCRUD(); + const { instrumentationRules } = useInstrumentationRuleCRUD(); const { containerRef, containerWidth } = useContainerWidth(); const { handleNodeClick } = useNodeDataFlowHandlers({ rules: instrumentationRules, diff --git a/frontend/webapp/hooks/actions/index.ts b/frontend/webapp/hooks/actions/index.ts index 8d94dd9e0..691b55c67 100644 --- a/frontend/webapp/hooks/actions/index.ts +++ b/frontend/webapp/hooks/actions/index.ts @@ -1,5 +1,4 @@ export * from './useActionFormData'; export * from './useActions'; export * from './useActionState'; -export * from './useGetActions'; export * from './useActionCRUD'; diff --git a/frontend/webapp/hooks/actions/useActionCRUD.ts b/frontend/webapp/hooks/actions/useActionCRUD.ts index 0b95f506f..d28dd7d75 100644 --- a/frontend/webapp/hooks/actions/useActionCRUD.ts +++ b/frontend/webapp/hooks/actions/useActionCRUD.ts @@ -2,9 +2,9 @@ import { useDrawerStore } from '@/store'; import { useNotify } from '../useNotify'; import { useMutation } from '@apollo/client'; import { useComputePlatform } from '../compute-platform'; -import { ACTION, getSseTargetFromId, NOTIFICATION } from '@/utils'; +import { ACTION, getSseTargetFromId, NOTIFICATION, safeJsonParse } from '@/utils'; import { CREATE_ACTION, DELETE_ACTION, UPDATE_ACTION } from '@/graphql/mutations'; -import { OVERVIEW_ENTITY_TYPES, type ActionInput, type ActionsType, type NotificationType } from '@/types'; +import { type ActionItem, OVERVIEW_ENTITY_TYPES, type ActionInput, type ActionsType, type NotificationType } from '@/types'; interface UseActionCrudParams { onSuccess?: () => void; @@ -13,7 +13,7 @@ interface UseActionCrudParams { export const useActionCRUD = (params?: UseActionCrudParams) => { const { setSelectedItem: setDrawerItem } = useDrawerStore((store) => store); - const { refetch } = useComputePlatform(); + const { data, refetch } = useComputePlatform(); const notify = useNotify(); const notifyUser = (type: NotificationType, title: string, message: string, id?: string) => { @@ -64,6 +64,13 @@ export const useActionCRUD = (params?: UseActionCrudParams) => { return { loading: cState.loading || uState.loading || dState.loading, + actions: + data?.computePlatform?.actions?.map((item) => { + const parsedSpec = typeof item.spec === 'string' ? safeJsonParse(item.spec, {} as ActionItem) : item.spec; + + return { ...item, spec: parsedSpec }; + }) || [], + createAction: (action: ActionInput) => createAction({ variables: { action } }), updateAction: (id: string, action: ActionInput) => updateAction({ variables: { id, action } }), deleteAction: (id: string, actionType: ActionsType) => deleteAction({ variables: { id, actionType } }), diff --git a/frontend/webapp/hooks/actions/useGetActions.ts b/frontend/webapp/hooks/actions/useGetActions.ts deleted file mode 100644 index 302e483de..000000000 --- a/frontend/webapp/hooks/actions/useGetActions.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { safeJsonParse } from '@/utils'; -import type { ActionItem } from '@/types'; -import { useComputePlatform } from '../compute-platform'; - -// Define the hook -export const useGetActions = () => { - const { data } = useComputePlatform(); - - return { - actions: - data?.computePlatform?.actions?.map((item) => { - const parsedSpec = typeof item.spec === 'string' ? safeJsonParse(item.spec, {} as ActionItem) : item.spec; - - return { - ...item, - spec: parsedSpec, - }; - }) || [], - }; -}; diff --git a/frontend/webapp/hooks/destinations/index.ts b/frontend/webapp/hooks/destinations/index.ts index 4677f854d..97df326cb 100644 --- a/frontend/webapp/hooks/destinations/index.ts +++ b/frontend/webapp/hooks/destinations/index.ts @@ -2,7 +2,6 @@ export * from './useDestinations'; export * from './useTestConnection'; export * from './useConnectDestinationForm'; export * from './usePotentialDestinations'; -export * from './useActualDestinations'; export * from './useDestinationCRUD'; export * from './useDestinationFormData'; export * from './useEditDestinationFormHandlers'; diff --git a/frontend/webapp/hooks/destinations/useActualDestinations.ts b/frontend/webapp/hooks/destinations/useActualDestinations.ts deleted file mode 100644 index 0cdf41d05..000000000 --- a/frontend/webapp/hooks/destinations/useActualDestinations.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useComputePlatform } from '../compute-platform'; -import { ActualDestination } from '@/types'; - -// Function to map raw data to the ActualDestination interface -const mapToActualDestination = (data: any): ActualDestination => ({ - id: data.id, - name: data.name, - type: data.type, - exportedSignals: data.exportedSignals, - fields: data.fields, - conditions: data.conditions, - destinationType: { - type: data.destinationType.type, - displayName: data.destinationType.displayName, - imageUrl: data.destinationType.imageUrl, - supportedSignals: data.destinationType.supportedSignals, - }, -}); - -export const useActualDestination = () => { - const { data } = useComputePlatform(); - - // Use the mapToActualDestination function to transform raw data - const destinations = - data?.computePlatform.destinations.map((destination: any) => - mapToActualDestination(destination) - ) || []; - - return { - destinations, - }; -}; diff --git a/frontend/webapp/hooks/destinations/useDestinationCRUD.ts b/frontend/webapp/hooks/destinations/useDestinationCRUD.ts index 3668cfbe6..e902fdeeb 100644 --- a/frontend/webapp/hooks/destinations/useDestinationCRUD.ts +++ b/frontend/webapp/hooks/destinations/useDestinationCRUD.ts @@ -13,7 +13,7 @@ interface Params { export const useDestinationCRUD = (params?: Params) => { const { setSelectedItem: setDrawerItem } = useDrawerStore((store) => store); - const { refetch } = useComputePlatform(); + const { data, refetch } = useComputePlatform(); const notify = useNotify(); const notifyUser = (type: NotificationType, title: string, message: string, id?: string) => { @@ -64,6 +64,8 @@ export const useDestinationCRUD = (params?: Params) => { return { loading: cState.loading || uState.loading || dState.loading, + destinations: data?.computePlatform.destinations || [], + createDestination: (destination: DestinationInput) => createDestination({ variables: { destination } }), updateDestination: (id: string, destination: DestinationInput) => updateDestination({ variables: { id, destination } }), deleteDestination: (id: string) => deleteDestination({ variables: { id } }), diff --git a/frontend/webapp/hooks/instrumentation-rules/index.ts b/frontend/webapp/hooks/instrumentation-rules/index.ts index 7cbf34e7b..e71cd62cd 100644 --- a/frontend/webapp/hooks/instrumentation-rules/index.ts +++ b/frontend/webapp/hooks/instrumentation-rules/index.ts @@ -1,4 +1,3 @@ export * from './useInstrumentationRule'; -export * from './useGetInstrumentationRules'; export * from './useInstrumentationRuleCRUD'; export * from './useInstrumentationRuleFormData'; diff --git a/frontend/webapp/hooks/instrumentation-rules/useGetInstrumentationRules.ts b/frontend/webapp/hooks/instrumentation-rules/useGetInstrumentationRules.ts deleted file mode 100644 index fa9cb1444..000000000 --- a/frontend/webapp/hooks/instrumentation-rules/useGetInstrumentationRules.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { deriveTypeFromRule } from '@/utils'; -import { useComputePlatform } from '../compute-platform'; - -export const useGetInstrumentationRules = () => { - const { data } = useComputePlatform(); - - return { - instrumentationRules: - data?.computePlatform?.instrumentationRules?.map((item) => { - const type = deriveTypeFromRule(item); - - return { - ...item, - type, - }; - }) || [], - }; -}; diff --git a/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleCRUD.ts b/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleCRUD.ts index aac125189..082be89fc 100644 --- a/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleCRUD.ts +++ b/frontend/webapp/hooks/instrumentation-rules/useInstrumentationRuleCRUD.ts @@ -13,7 +13,7 @@ interface Params { export const useInstrumentationRuleCRUD = (params?: Params) => { const { setSelectedItem: setDrawerItem } = useDrawerStore((store) => store); - const { refetch } = useComputePlatform(); + const { data, refetch } = useComputePlatform(); const notify = useNotify(); const notifyUser = (type: NotificationType, title: string, message: string, id?: string) => { @@ -64,6 +64,13 @@ export const useInstrumentationRuleCRUD = (params?: Params) => { return { loading: cState.loading || uState.loading || dState.loading, + instrumentationRules: + data?.computePlatform?.instrumentationRules?.map((item) => { + const type = deriveTypeFromRule(item); + + return { ...item, type }; + }) || [], + createInstrumentationRule: (instrumentationRule: InstrumentationRuleInput) => createInstrumentationRule({ variables: { instrumentationRule } }), updateInstrumentationRule: (ruleId: string, instrumentationRule: InstrumentationRuleInput) => updateInstrumentationRule({ variables: { ruleId, instrumentationRule } }), deleteInstrumentationRule: (ruleId: string) => deleteInstrumentationRule({ variables: { ruleId } }), diff --git a/frontend/webapp/hooks/sources/index.ts b/frontend/webapp/hooks/sources/index.ts index b0b6b4652..c98e0a1d7 100644 --- a/frontend/webapp/hooks/sources/index.ts +++ b/frontend/webapp/hooks/sources/index.ts @@ -1,5 +1,4 @@ export * from './useSources'; export * from './useConnectSourcesMenuState'; export * from './useConnectSourcesList'; -export * from './useActualSources'; export * from './useSourceCRUD'; diff --git a/frontend/webapp/hooks/sources/useActualSources.ts b/frontend/webapp/hooks/sources/useActualSources.ts deleted file mode 100644 index 15ef9fc85..000000000 --- a/frontend/webapp/hooks/sources/useActualSources.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useComputePlatform } from '../compute-platform'; - -export function useActualSources() { - const { data } = useComputePlatform(); - - return { - sources: data?.computePlatform.k8sActualSources || [], - }; -} diff --git a/frontend/webapp/hooks/sources/useSourceCRUD.ts b/frontend/webapp/hooks/sources/useSourceCRUD.ts index 0a02dbee5..49571ba46 100644 --- a/frontend/webapp/hooks/sources/useSourceCRUD.ts +++ b/frontend/webapp/hooks/sources/useSourceCRUD.ts @@ -4,7 +4,7 @@ import { useMutation } from '@apollo/client'; import { ACTION, getSseTargetFromId, NOTIFICATION } from '@/utils'; import { PERSIST_SOURCE, UPDATE_K8S_ACTUAL_SOURCE } from '@/graphql'; import { useComputePlatform, useNamespace } from '../compute-platform'; -import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type NotificationType, PersistSourcesArray, PatchSourceRequestInput, K8sActualSource } from '@/types'; +import { OVERVIEW_ENTITY_TYPES, type WorkloadId, type NotificationType, type PatchSourceRequestInput, type K8sActualSource } from '@/types'; interface Params { onSuccess?: () => void; @@ -13,8 +13,8 @@ interface Params { export const useSourceCRUD = (params?: Params) => { const { setSelectedItem: setDrawerItem } = useDrawerStore((store) => store); + const { data, startPolling } = useComputePlatform(); const { persistNamespace } = useNamespace(); - const { startPolling } = useComputePlatform(); const notify = useNotify(); const notifyUser = (type: NotificationType, title: string, message: string, id?: WorkloadId) => { @@ -90,6 +90,8 @@ export const useSourceCRUD = (params?: Params) => { return { loading: cdState.loading || uState.loading, + sources: data?.computePlatform.k8sActualSources || [], + createSources: async (selectAppsList: { [key: string]: K8sActualSource[] }, futureSelectAppsList: { [key: string]: boolean }) => { await persistNamespaces(futureSelectAppsList); await persistSources(selectAppsList, true); From 2a8fc100172a55c587eca0b5c23a9bd317dd1a79 Mon Sep 17 00:00:00 2001 From: Ben Elferink Date: Sun, 10 Nov 2024 12:09:01 +0200 Subject: [PATCH 4/4] fix: action for source CRUD error message --- frontend/webapp/hooks/sources/useSourceCRUD.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/webapp/hooks/sources/useSourceCRUD.ts b/frontend/webapp/hooks/sources/useSourceCRUD.ts index 49571ba46..67b7543de 100644 --- a/frontend/webapp/hooks/sources/useSourceCRUD.ts +++ b/frontend/webapp/hooks/sources/useSourceCRUD.ts @@ -40,7 +40,12 @@ export const useSourceCRUD = (params?: Params) => { }; const [createOrDeleteSources, cdState] = useMutation<{ persistK8sSources: boolean }>(PERSIST_SOURCE, { - onError: (error) => handleError(ACTION.CREATE, error.message), + onError: (error, req) => { + const { selected } = req?.variables?.sources[0]; + const action = selected ? ACTION.CREATE : ACTION.DELETE; + + handleError(action, error.message); + }, onCompleted: (res, req) => { const namespace = req?.variables?.namespace; const { name, kind, selected } = req?.variables?.sources[0];