From 6a30100f3ff46c00c056c99915bdc257ead498f2 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 20 Mar 2025 15:03:57 -0600 Subject: [PATCH] make new saving to dashboard endpoints --- .../buster_rest/dashboards/helpers/index.ts | 2 + .../helpers/removeMetricFromDashboard.test.ts | 266 ++++++++++++++++++ .../helpers/removeMetricFromDashboard.ts | 38 +++ .../buster_rest/dashboards/queryRequests.ts | 142 ++++++---- .../api/buster_rest/metrics/queryRequests.ts | 2 +- .../buttons/SaveMetricToCollectionButton.tsx | 4 +- .../buttons/SaveMetricToDashboardButton.tsx | 19 +- .../dropdowns/SaveToDashboardDropdown.tsx | 9 +- .../ChatItemsSelectedPopup.tsx | 4 +- .../DashboardMetricItem/MetricTitle.tsx | 6 +- .../MetricItemsSelectedPopup.tsx | 4 +- .../MetricThreeDotMenu.tsx | 28 +- 12 files changed, 443 insertions(+), 81 deletions(-) create mode 100644 web/src/api/buster_rest/dashboards/helpers/index.ts create mode 100644 web/src/api/buster_rest/dashboards/helpers/removeMetricFromDashboard.test.ts create mode 100644 web/src/api/buster_rest/dashboards/helpers/removeMetricFromDashboard.ts diff --git a/web/src/api/buster_rest/dashboards/helpers/index.ts b/web/src/api/buster_rest/dashboards/helpers/index.ts new file mode 100644 index 000000000..612e8a65b --- /dev/null +++ b/web/src/api/buster_rest/dashboards/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './addMetricToDashboard'; +export * from './removeMetricFromDashboard'; diff --git a/web/src/api/buster_rest/dashboards/helpers/removeMetricFromDashboard.test.ts b/web/src/api/buster_rest/dashboards/helpers/removeMetricFromDashboard.test.ts new file mode 100644 index 000000000..3e91d53cf --- /dev/null +++ b/web/src/api/buster_rest/dashboards/helpers/removeMetricFromDashboard.test.ts @@ -0,0 +1,266 @@ +import { removeMetricFromDashboardConfig } from './removeMetricFromDashboard'; +import type { BusterDashboard } from '@/api/asset_interfaces/dashboard'; + +describe('removeMetricFromDashboardConfig', () => { + const createEmptyConfig = (): BusterDashboard['config'] => ({ + rows: [] + }); + + const createConfigWithRows = ( + rows: BusterDashboard['config']['rows'] + ): BusterDashboard['config'] => ({ + rows + }); + + it('should return the same config if no metrics to remove are provided', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }], + columnSizes: [12], + rowHeight: 320 + } + ]); + const result = removeMetricFromDashboardConfig([], config); + expect(result.rows).toHaveLength(1); + expect(result.rows![0].items).toHaveLength(1); + }); + + it('should remove a single metric from a single row', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }], + columnSizes: [6, 6], + rowHeight: 320 + } + ]); + const result = removeMetricFromDashboardConfig(['metric1'], config); + + expect(result.rows).toHaveLength(1); + expect(result.rows![0].items).toHaveLength(1); + expect(result.rows![0].items[0].id).toBe('metric2'); + expect(result.rows![0].items.length).toBe(1); + expect(result.rows![0].columnSizes).toEqual([12]); // Single column takes full width + }); + + it('should remove multiple metrics from a single row', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }, { id: 'metric3' }, { id: 'metric4' }], + columnSizes: [3, 3, 3, 3], + rowHeight: 320 + } + ]); + const result = removeMetricFromDashboardConfig(['metric1', 'metric3'], config); + + expect(result.rows).toHaveLength(1); + expect(result.rows![0].items).toHaveLength(2); + expect(result.rows![0].items.map((item) => item.id)).toEqual(['metric2', 'metric4']); + expect(result.rows![0].columnSizes).toEqual([6, 6]); // Two equal columns + }); + + it('should remove entire row when all metrics in the row are removed', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }], + columnSizes: [6, 6], + rowHeight: 320 + }, + { + id: 'row2', + items: [{ id: 'metric3' }], + columnSizes: [12], + rowHeight: 320 + } + ]); + const result = removeMetricFromDashboardConfig(['metric1', 'metric2'], config); + + expect(result.rows).toHaveLength(1); + expect(result.rows![0].items).toHaveLength(1); + expect(result.rows![0].items.length).toBe(1); + expect(result.rows![0].columnSizes).toEqual([12]); + expect(result.rows![0].id).toBe('row2'); + expect(result.rows![0].items[0].id).toBe('metric3'); + }); + + it('should handle removing metrics from multiple rows', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }], + columnSizes: [6, 6], + rowHeight: 320 + }, + { + id: 'row2', + items: [{ id: 'metric3' }, { id: 'metric4' }], + columnSizes: [6, 6], + rowHeight: 320 + } + ]); + const result = removeMetricFromDashboardConfig(['metric1', 'metric4'], config); + + expect(result.rows).toHaveLength(2); + expect(result.rows![0].items).toHaveLength(1); + expect(result.rows![1].items).toHaveLength(1); + expect(result.rows![0].items[0].id).toBe('metric2'); + expect(result.rows![1].items[0].id).toBe('metric3'); + expect(result.rows![0].columnSizes).toEqual([12]); + expect(result.rows![1].columnSizes).toEqual([12]); + }); + + it('should handle removing non-existent metrics', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }], + columnSizes: [6, 6], + rowHeight: 320 + } + ]); + const result = removeMetricFromDashboardConfig(['nonexistent1', 'nonexistent2'], config); + + expect(result.rows).toHaveLength(1); + expect(result.rows![0].items).toHaveLength(2); + expect(result.rows![0].columnSizes).toEqual([6, 6]); + }); + + it('should handle empty config', () => { + const config = createEmptyConfig(); + const result = removeMetricFromDashboardConfig(['metric1'], config); + + expect(result.rows).toHaveLength(0); + }); + + it('should preserve row properties while updating items and columnSizes', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }, { id: 'metric3' }], + columnSizes: [4, 4, 4], + rowHeight: 320 + } + ]); + const result = removeMetricFromDashboardConfig(['metric2'], config); + + expect(result.rows![0]).toMatchObject({ + id: 'row1', + rowHeight: 320 + }); + expect(result.rows![0].items).toHaveLength(2); + expect(result.rows![0].columnSizes).toEqual([6, 6]); + }); + + it('should handle removing all metrics from config', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }], + columnSizes: [6, 6], + rowHeight: 320 + }, + { + id: 'row2', + items: [{ id: 'metric3' }], + columnSizes: [12], + rowHeight: 320 + } + ]); + const result = removeMetricFromDashboardConfig(['metric1', 'metric2', 'metric3'], config); + + expect(result.rows).toHaveLength(0); + }); + + it('should correctly remove metrics from a large config with multiple rows', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }], + columnSizes: [6, 6], + rowHeight: 320 + }, + { + id: 'row2', + items: [{ id: 'metric3' }, { id: 'metric4' }, { id: 'metric5' }], + columnSizes: [4, 4, 4], + rowHeight: 320 + }, + { + id: 'row3', + items: [{ id: 'metric6' }], + columnSizes: [12], + rowHeight: 320 + }, + { + id: 'row4', + items: [{ id: 'metric7' }, { id: 'metric8' }, { id: 'metric9' }, { id: 'metric10' }], + columnSizes: [3, 3, 3, 3], + rowHeight: 320 + }, + { + id: 'row5', + items: [{ id: 'metric11' }, { id: 'metric12' }], + columnSizes: [6, 6], + rowHeight: 320 + } + ]); + + const result = removeMetricFromDashboardConfig(['metric2', 'metric4', 'metric11'], config); + + expect(result.rows).toHaveLength(5); + // Check row 1 + expect(result.rows![0].items).toHaveLength(1); + expect(result.rows![0].items[0].id).toBe('metric1'); + expect(result.rows![0].columnSizes).toEqual([12]); + // Check row 2 + expect(result.rows![1].items).toHaveLength(2); + expect(result.rows![1].items.map((item) => item.id)).toEqual(['metric3', 'metric5']); + expect(result.rows![1].columnSizes).toEqual([6, 6]); + // Check row 3 (unchanged) + expect(result.rows![2].items).toHaveLength(1); + expect(result.rows![2].items[0].id).toBe('metric6'); + // Check row 4 (unchanged) + expect(result.rows![3].items).toHaveLength(4); + expect(result.rows![3].columnSizes).toEqual([3, 3, 3, 3]); + // Check row 5 + expect(result.rows![4].items).toHaveLength(1); + expect(result.rows![4].items[0].id).toBe('metric12'); + expect(result.rows![4].columnSizes).toEqual([12]); + }); + + it('should handle removing non-existent metrics while preserving existing structure', () => { + const config = createConfigWithRows([ + { + id: 'row1', + items: [{ id: 'metric1' }, { id: 'metric2' }, { id: 'metric3' }], + columnSizes: [4, 4, 4], + rowHeight: 320 + }, + { + id: 'row2', + items: [{ id: 'metric4' }, { id: 'metric5' }], + columnSizes: [6, 6], + rowHeight: 320 + } + ]); + + const result = removeMetricFromDashboardConfig( + ['nonexistent1', 'nonexistent2', 'metric1'], + config + ); + + // Should only remove metric1 and ignore non-existent metrics + expect(result.rows).toHaveLength(2); + // Check first row + expect(result.rows![0].items).toHaveLength(2); + expect(result.rows![0].items.map((item) => item.id)).toEqual(['metric2', 'metric3']); + expect(result.rows![0].columnSizes).toEqual([6, 6]); + // Check second row (should be unchanged) + expect(result.rows![1].items).toHaveLength(2); + expect(result.rows![1].items.map((item) => item.id)).toEqual(['metric4', 'metric5']); + expect(result.rows![1].columnSizes).toEqual([6, 6]); + }); +}); diff --git a/web/src/api/buster_rest/dashboards/helpers/removeMetricFromDashboard.ts b/web/src/api/buster_rest/dashboards/helpers/removeMetricFromDashboard.ts new file mode 100644 index 000000000..4527dd8d5 --- /dev/null +++ b/web/src/api/buster_rest/dashboards/helpers/removeMetricFromDashboard.ts @@ -0,0 +1,38 @@ +import type { BusterDashboard } from '@/api/asset_interfaces/dashboard'; +import { NUMBER_OF_COLUMNS } from '@/components/ui/grid/helpers'; + +export const removeMetricFromDashboardConfig = ( + metricIds: string[], + existingConfig: BusterDashboard['config'] +) => { + // Create a new config object to avoid mutating the original + const newConfig = { + ...existingConfig, + rows: [...(existingConfig.rows || [])] + }; + + // Filter out rows that contain metrics to be removed + newConfig.rows = newConfig.rows + .map((row) => { + // Remove the specified metrics from the row + const filteredItems = row.items.filter((item) => !metricIds.includes(item.id)); + + // If no items left in the row, return null to filter out later + if (filteredItems.length === 0) { + return null; + } + + // Recalculate column sizes for remaining items + const columnSize = NUMBER_OF_COLUMNS / filteredItems.length; + const columnSizes = Array(filteredItems.length).fill(columnSize); + + return { + ...row, + items: filteredItems, + columnSizes + }; + }) + .filter((row): row is NonNullable => row !== null); + + return newConfig; +}; diff --git a/web/src/api/buster_rest/dashboards/queryRequests.ts b/web/src/api/buster_rest/dashboards/queryRequests.ts index 696bb753c..45feb0e9a 100644 --- a/web/src/api/buster_rest/dashboards/queryRequests.ts +++ b/web/src/api/buster_rest/dashboards/queryRequests.ts @@ -25,6 +25,7 @@ import { useRemoveAssetFromCollection } from '../collections/queryRequests'; import { collectionQueryKeys } from '@/api/query_keys/collection'; +import { addMetricToDashboardConfig, removeMetricFromDashboardConfig } from './helpers'; export const useGetDashboardsList = ( params: Omit @@ -43,33 +44,38 @@ export const useGetDashboardsList = ( }); }; -export const useGetDashboard = ( - id: string | undefined, - select?: (data: BusterDashboardResponse) => TData -) => { +const useGetDashboardAndInitializeMetrics = () => { const queryClient = useQueryClient(); const getAssetPassword = useBusterAssetsContextSelector((state) => state.getAssetPassword); - const { password } = getAssetPassword(id!); - const initializeMetrics = useMemoizedFn((metrics: BusterDashboardResponse['metrics']) => { - for (const metric of Object.values(metrics)) { - const prevMetric = queryClient.getQueryData(queryKeys.metricsGetMetric(metric.id).queryKey); - const upgradedMetric = upgradeMetricToIMetric(metric, prevMetric); - queryClient.setQueryData(queryKeys.metricsGetMetric(metric.id).queryKey, upgradedMetric); - prefetchGetMetricDataClient({ id: metric.id }, queryClient); - } - }); + return useMemoizedFn(async (id: string) => { + const { password } = getAssetPassword(id); + + const initializeMetrics = useMemoizedFn((metrics: BusterDashboardResponse['metrics']) => { + for (const metric of Object.values(metrics)) { + const prevMetric = queryClient.getQueryData(queryKeys.metricsGetMetric(metric.id).queryKey); + const upgradedMetric = upgradeMetricToIMetric(metric, prevMetric); + queryClient.setQueryData(queryKeys.metricsGetMetric(metric.id).queryKey, upgradedMetric); + prefetchGetMetricDataClient({ id: metric.id }, queryClient); + } + }); - const queryFn = useMemoizedFn(async () => { return dashboardsGetDashboard({ id: id!, password }).then((data) => { initializeMetrics(data.metrics); return data; }); }); +}; + +export const useGetDashboard = ( + id: string | undefined, + select?: (data: BusterDashboardResponse) => TData +) => { + const queryFn = useGetDashboardAndInitializeMetrics(); return useQuery({ ...dashboardQueryKeys.dashboardGetDashboard(id!), - queryFn: queryFn, + queryFn: () => queryFn(id!), enabled: !!id, select }); @@ -318,46 +324,87 @@ export const useUpdateDashboardShare = () => { export const useSaveMetricsToDashboard = () => { const queryClient = useQueryClient(); + const prefetchDashboard = useGetDashboardAndInitializeMetrics(); + const { openErrorMessage } = useBusterNotifications(); const saveMetricToDashboard = useMemoizedFn( async ({ metricIds, dashboardId }: { metricIds: string[]; dashboardId: string }) => { - // await saveMetric({ - // id: metricId, - // save_to_dashboard: dashboardIds - // }); + const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId); + let dashboardResponse = queryClient.getQueryData(options.queryKey); + if (!dashboardResponse) { + const res = await prefetchDashboard(dashboardId).catch((e) => { + openErrorMessage('Failed to save metrics to dashboard. Dashboard not found'); + return null; + }); + if (res) { + queryClient.setQueryData(options.queryKey, res); + dashboardResponse = res; + } + } + + if (dashboardResponse) { + const newConfig = addMetricToDashboardConfig(metricIds, dashboardResponse.dashboard.config); + return dashboardsUpdateDashboard({ + id: dashboardId, + config: newConfig + }); + } + + openErrorMessage('Failed to save metrics to dashboard'); } ); return useMutation({ mutationFn: saveMetricToDashboard, onSuccess: (data, variables) => { - // queryClient.invalidateQueries({ - // queryKey: variables.dashboardIds.map( - // (id) => dashboardQueryKeys.dashboardGetDashboard(id).queryKey - // ) - // }); + queryClient.invalidateQueries({ + queryKey: dashboardQueryKeys.dashboardGetDashboard(variables.dashboardId).queryKey + }); } }); }; -export const useRemoveMetricFromDashboard = () => { - const { openConfirmModal } = useBusterNotifications(); +export const useRemoveMetricsFromDashboard = () => { + const { openConfirmModal, openErrorMessage } = useBusterNotifications(); const queryClient = useQueryClient(); + const prefetchDashboard = useGetDashboardAndInitializeMetrics(); const removeMetricFromDashboard = useMemoizedFn( async ({ - metricId, + metricIds, dashboardId, useConfirmModal = true }: { - metricId: string; + metricIds: string[]; dashboardId: string; useConfirmModal?: boolean; }) => { const method = async () => { - // await saveMetric({ - // id: metricId, - // remove_from_dashboard: [dashboardId] - // }); + const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId); + let dashboardResponse = queryClient.getQueryData(options.queryKey); + if (!dashboardResponse) { + const res = await prefetchDashboard(dashboardId).catch((e) => { + openErrorMessage('Failed to remove metrics from dashboard. Dashboard not found'); + return null; + }); + if (res) { + queryClient.setQueryData(options.queryKey, res); + dashboardResponse = res; + } + } + + if (dashboardResponse) { + const newConfig = removeMetricFromDashboardConfig( + metricIds, + dashboardResponse.dashboard.config + ); + await dashboardsUpdateDashboard({ + id: dashboardId, + config: newConfig + }); + return; + } + + openErrorMessage('Failed to remove metrics from dashboard'); }; if (!useConfirmModal) return await method(); @@ -372,24 +419,25 @@ export const useRemoveMetricFromDashboard = () => { return useMutation({ mutationFn: removeMetricFromDashboard, - onMutate: async (variables) => { - const currentDashboard = queryClient.getQueryData( - dashboardQueryKeys.dashboardGetDashboard(variables.dashboardId).queryKey - ); + onMutate: async ({ metricIds, dashboardId }) => { + const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId); + const currentDashboard = queryClient.getQueryData(options.queryKey); if (currentDashboard) { - queryClient.setQueryData( - dashboardQueryKeys.dashboardGetDashboard(variables.dashboardId).queryKey, - (currentDashboard) => { - if (currentDashboard?.dashboard.config.rows) { - currentDashboard.dashboard.config.rows.forEach((row) => { - row.items = row.items.filter((item) => item.id !== variables.metricId); - }); - } - delete currentDashboard!.metrics[variables.metricId]; - return currentDashboard; - } + const newConfig = removeMetricFromDashboardConfig( + metricIds, + currentDashboard.dashboard.config ); + queryClient.setQueryData(options.queryKey, (currentDashboard) => { + return create(currentDashboard!, (draft) => { + draft.dashboard.config = newConfig; + }); + }); } + }, + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ + queryKey: dashboardQueryKeys.dashboardGetDashboard(variables.dashboardId).queryKey + }); } }); }; diff --git a/web/src/api/buster_rest/metrics/queryRequests.ts b/web/src/api/buster_rest/metrics/queryRequests.ts index 38a09c4ec..e6b4b44c3 100644 --- a/web/src/api/buster_rest/metrics/queryRequests.ts +++ b/web/src/api/buster_rest/metrics/queryRequests.ts @@ -170,7 +170,7 @@ export const useDeleteMetric = () => { }); }; -export const useSaveMetricToCollection = () => { +export const useSaveMetricToCollections = () => { const queryClient = useQueryClient(); const { data: userFavorites, refetch: refreshFavoritesList } = useGetUserFavorites(); const { mutateAsync: addAssetToCollection } = useAddAssetToCollection(); diff --git a/web/src/components/features/buttons/SaveMetricToCollectionButton.tsx b/web/src/components/features/buttons/SaveMetricToCollectionButton.tsx index a32a593d4..888078750 100644 --- a/web/src/components/features/buttons/SaveMetricToCollectionButton.tsx +++ b/web/src/components/features/buttons/SaveMetricToCollectionButton.tsx @@ -5,7 +5,7 @@ import { SaveToCollectionsDropdown } from '../dropdowns/SaveToCollectionsDropdow import { CollectionButton } from './CollectionsButton'; import { useRemoveMetricFromCollection, - useSaveMetricToCollection + useSaveMetricToCollections } from '@/api/buster_rest/metrics'; export const SaveMetricToCollectionButton: React.FC<{ @@ -14,7 +14,7 @@ export const SaveMetricToCollectionButton: React.FC<{ useText?: boolean; }> = ({ metricIds, buttonType = 'ghost', useText = false }) => { const { openInfoMessage } = useBusterNotifications(); - const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollection(); + const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollections(); const { mutateAsync: removeMetricFromCollection } = useRemoveMetricFromCollection(); const [selectedCollections, setSelectedCollections] = useState< diff --git a/web/src/components/features/buttons/SaveMetricToDashboardButton.tsx b/web/src/components/features/buttons/SaveMetricToDashboardButton.tsx index 16ad73250..b010f0ee3 100644 --- a/web/src/components/features/buttons/SaveMetricToDashboardButton.tsx +++ b/web/src/components/features/buttons/SaveMetricToDashboardButton.tsx @@ -4,7 +4,10 @@ import { SaveToDashboardDropdown } from '../dropdowns/SaveToDashboardDropdown'; import type { BusterMetric } from '@/api/asset_interfaces'; import { Button } from '@/components/ui/buttons'; import { ASSET_ICONS } from '../config/assetIcons'; -import { useRemoveMetricFromDashboard, useSaveMetricToDashboard } from '@/api/buster_rest/metrics'; +import { + useRemoveMetricsFromDashboard, + useSaveMetricsToDashboard +} from '@/api/buster_rest/dashboards'; const EMPTY_SELECTED_DASHBOARDS: BusterMetric['dashboards'] = []; @@ -14,22 +17,18 @@ export const SaveMetricToDashboardButton: React.FC<{ selectedDashboards?: BusterMetric['dashboards']; }> = React.memo( ({ metricIds, disabled = false, selectedDashboards = EMPTY_SELECTED_DASHBOARDS }) => { - const { mutateAsync: saveMetricToDashboard } = useSaveMetricToDashboard(); - const { mutateAsync: removeMetricFromDashboard } = useRemoveMetricFromDashboard(); + const { mutateAsync: saveMetricsToDashboard } = useSaveMetricsToDashboard(); + const { mutateAsync: removeMetricsFromDashboard } = useRemoveMetricsFromDashboard(); const onSaveToDashboard = useMemoizedFn(async (dashboardIds: string[]) => { await Promise.all( - metricIds.map((metricId) => { - return saveMetricToDashboard({ metricId, dashboardIds }); - }) + dashboardIds.map((dashboardId) => saveMetricsToDashboard({ metricIds, dashboardId })) ); }); - const onRemoveFromDashboard = useMemoizedFn(async (dashboardId: string) => { + const onRemoveFromDashboard = useMemoizedFn(async (dashboardIds: string[]) => { await Promise.all( - metricIds.map((metricId) => { - return removeMetricFromDashboard({ metricId, dashboardId }); - }) + dashboardIds.map((dashboardId) => removeMetricsFromDashboard({ metricIds, dashboardId })) ); }); diff --git a/web/src/components/features/dropdowns/SaveToDashboardDropdown.tsx b/web/src/components/features/dropdowns/SaveToDashboardDropdown.tsx index ae437e8e8..8a30eeb6f 100644 --- a/web/src/components/features/dropdowns/SaveToDashboardDropdown.tsx +++ b/web/src/components/features/dropdowns/SaveToDashboardDropdown.tsx @@ -13,7 +13,7 @@ export const SaveToDashboardDropdown: React.FC<{ children: React.ReactNode; selectedDashboards: BusterMetric['dashboards']; onSaveToDashboard: (dashboardId: string[]) => Promise; - onRemoveFromDashboard: (dashboardId: string) => void; + onRemoveFromDashboard: (dashboardId: string[]) => Promise; }> = ({ children, onRemoveFromDashboard, onSaveToDashboard, selectedDashboards }) => { const [showDropdown, setShowDropdown] = useState(false); @@ -46,7 +46,7 @@ export const useSaveToDashboardDropdownContent = ({ }: { selectedDashboards: BusterMetric['dashboards']; onSaveToDashboard: (dashboardId: string[]) => Promise; - onRemoveFromDashboard: (dashboardId: string) => void; + onRemoveFromDashboard: (dashboardId: string[]) => Promise; }): Pick< DropdownProps, 'items' | 'footerContent' | 'menuHeader' | 'selectType' | 'emptyStateText' @@ -58,10 +58,9 @@ export const useSaveToDashboardDropdownContent = ({ const onClickItem = useMemoizedFn(async (dashboard: BusterDashboardListItem) => { const isSelected = selectedDashboards.some((d) => d.id === dashboard.id); if (isSelected) { - onRemoveFromDashboard(dashboard.id); + await onRemoveFromDashboard([dashboard.id]); } else { - const allDashboardsAndSelected = selectedDashboards.map((d) => d.id).concat(dashboard.id); - await onSaveToDashboard(allDashboardsAndSelected); + await onSaveToDashboard([dashboard.id]); } }); diff --git a/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx b/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx index b147a91f2..60804a519 100644 --- a/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx +++ b/web/src/controllers/ChatsListController/ChatItemsSelectedPopup.tsx @@ -12,7 +12,7 @@ import { ASSET_ICONS } from '@/components/features/config/assetIcons'; import { useDeleteMetric, useRemoveMetricFromCollection, - useSaveMetricToCollection + useSaveMetricToCollections } from '@/api/buster_rest/metrics'; import { useAddUserFavorite, @@ -63,7 +63,7 @@ const CollectionsButton: React.FC<{ onSelectChange: (selectedRowKeys: string[]) => void; }> = ({ selectedRowKeys, onSelectChange }) => { const { openInfoMessage } = useBusterNotifications(); - const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollection(); + const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollections(); const { mutateAsync: removeMetricFromCollection } = useRemoveMetricFromCollection(); const [selectedCollections, setSelectedCollections] = useState< diff --git a/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/MetricTitle.tsx b/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/MetricTitle.tsx index d7d19fadb..86a7c54e2 100644 --- a/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/MetricTitle.tsx +++ b/web/src/controllers/DashboardController/DashboardViewDashboardController/DashboardContentController/DashboardMetricItem/MetricTitle.tsx @@ -7,7 +7,7 @@ import { Dropdown, DropdownItems } from '@/components/ui/dropdown'; import { Button } from '@/components/ui/buttons'; import Link from 'next/link'; import React, { useContext, useMemo } from 'react'; -import { useRemoveMetricFromDashboard } from '@/api/buster_rest/metrics'; +import { useRemoveMetricsFromDashboard } from '@/api/buster_rest/dashboards'; export const MetricTitle: React.FC<{ title: BusterMetric['title']; @@ -105,7 +105,7 @@ const ThreeDotMenu: React.FC<{ dashboardId: string; metricId: string; }> = React.memo(({ dashboardId, metricId, className }) => { - const { mutateAsync: removeMetricFromDashboard } = useRemoveMetricFromDashboard(); + const { mutateAsync: removeMetricFromDashboard } = useRemoveMetricsFromDashboard(); const dropdownItems: DropdownItems = useMemo( () => [ @@ -117,7 +117,7 @@ const ThreeDotMenu: React.FC<{ try { await removeMetricFromDashboard({ dashboardId, - metricId + metricIds: [metricId] }); } catch (error) { // diff --git a/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx b/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx index 583b6900f..48f442b38 100644 --- a/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx +++ b/web/src/controllers/MetricListContainer/MetricItemsSelectedPopup.tsx @@ -13,7 +13,7 @@ import { Dots, Star, Trash, Xmark } from '@/components/ui/icons'; import { useDeleteMetric, useRemoveMetricFromCollection, - useSaveMetricToCollection, + useSaveMetricToCollections, useUpdateMetric } from '@/api/buster_rest/metrics'; import { @@ -68,7 +68,7 @@ const CollectionsButton: React.FC<{ onSelectChange: (selectedRowKeys: string[]) => void; }> = ({ selectedRowKeys, onSelectChange }) => { const { openInfoMessage } = useBusterNotifications(); - const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollection(); + const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollections(); const { mutateAsync: removeMetricFromCollection } = useRemoveMetricFromCollection(); const [selectedCollections, setSelectedCollections] = useState< diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx index 08302a5b2..a93c7e0b5 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricThreeDotMenu.tsx @@ -3,11 +3,13 @@ import { useGetMetric, useGetMetricData, useRemoveMetricFromCollection, - useRemoveMetricFromDashboard, - useSaveMetricToCollection, - useSaveMetricToDashboard, + useSaveMetricToCollections, useUpdateMetric } from '@/api/buster_rest/metrics'; +import { + useSaveMetricsToDashboard, + useRemoveMetricsFromDashboard +} from '@/api/buster_rest/dashboards'; import { DropdownContent, DropdownItem, DropdownItems } from '@/components/ui/dropdown'; import { Trash, @@ -123,16 +125,24 @@ export const ThreeDotMenuButton = React.memo(({ metricId }: { metricId: string } ThreeDotMenuButton.displayName = 'ThreeDotMenuButton'; const useDashboardSelectMenu = ({ metricId }: { metricId: string }) => { - const { mutateAsync: saveMetricToDashboard } = useSaveMetricToDashboard(); - const { mutateAsync: removeMetricFromDashboard } = useRemoveMetricFromDashboard(); + const { mutateAsync: saveMetricsToDashboard } = useSaveMetricsToDashboard(); + const { mutateAsync: removeMetricsFromDashboard } = useRemoveMetricsFromDashboard(); const { data: dashboards } = useGetMetric({ id: metricId }, (x) => x.dashboards); const onSaveToDashboard = useMemoizedFn(async (dashboardIds: string[]) => { - await saveMetricToDashboard({ metricId, dashboardIds }); + await Promise.all( + dashboardIds.map((dashboardId) => + saveMetricsToDashboard({ metricIds: [metricId], dashboardId }) + ) + ); }); - const onRemoveFromDashboard = useMemoizedFn(async (dashboardId: string) => { - await removeMetricFromDashboard({ metricId, dashboardId }); + const onRemoveFromDashboard = useMemoizedFn(async (dashboardIds: string[]) => { + await Promise.all( + dashboardIds.map((dashboardId) => + removeMetricsFromDashboard({ metricIds: [metricId], dashboardId }) + ) + ); }); const { items, footerContent, selectType, menuHeader } = useSaveToDashboardDropdownContent({ @@ -193,7 +203,7 @@ const useVersionHistorySelectMenu = ({ metricId }: { metricId: string }) => { }; const useCollectionSelectMenu = ({ metricId }: { metricId: string }) => { - const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollection(); + const { mutateAsync: saveMetricToCollection } = useSaveMetricToCollections(); const { mutateAsync: removeMetricFromCollection } = useRemoveMetricFromCollection(); const { data: collections } = useGetMetric({ id: metricId }, (x) => x.collections); const { openInfoMessage } = useBusterNotifications();