From 0e2fd9a93c1bfbedcb04351d27656991fbf0335b Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Wed, 9 Apr 2025 13:03:31 -0600 Subject: [PATCH] max number on dash --- .../api/asset_interfaces/dashboard/config.ts | 1 + .../api/asset_interfaces/dashboard/index.ts | 1 + .../buster_rest/dashboards/queryRequests.ts | 23 ++++- .../api/buster_rest/search/queryRequests.ts | 10 ++- web/src/api/query_keys/search.ts | 2 +- .../features/auth/ResetPasswordForm.tsx | 4 +- .../features/modal/AddToDashboardModal.tsx | 87 ++++++++++++++++--- 7 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 web/src/api/asset_interfaces/dashboard/config.ts diff --git a/web/src/api/asset_interfaces/dashboard/config.ts b/web/src/api/asset_interfaces/dashboard/config.ts new file mode 100644 index 000000000..41766dca7 --- /dev/null +++ b/web/src/api/asset_interfaces/dashboard/config.ts @@ -0,0 +1 @@ +export const MAX_NUMBER_OF_ITEMS_ON_DASHBOARD = 30; diff --git a/web/src/api/asset_interfaces/dashboard/index.ts b/web/src/api/asset_interfaces/dashboard/index.ts index cbfb6299b..8f8e00a03 100644 --- a/web/src/api/asset_interfaces/dashboard/index.ts +++ b/web/src/api/asset_interfaces/dashboard/index.ts @@ -1,2 +1,3 @@ export * from './dashboardConfigInterfaces'; export * from './interfaces'; +export * from './config'; diff --git a/web/src/api/buster_rest/dashboards/queryRequests.ts b/web/src/api/buster_rest/dashboards/queryRequests.ts index c6fc5c0c0..bb6c644a8 100644 --- a/web/src/api/buster_rest/dashboards/queryRequests.ts +++ b/web/src/api/buster_rest/dashboards/queryRequests.ts @@ -10,7 +10,11 @@ import { unshareDashboard } from './requests'; import { dashboardQueryKeys } from '@/api/query_keys/dashboard'; -import { BusterDashboard, BusterDashboardResponse } from '@/api/asset_interfaces/dashboard'; +import { + BusterDashboard, + BusterDashboardResponse, + MAX_NUMBER_OF_ITEMS_ON_DASHBOARD +} from '@/api/asset_interfaces/dashboard'; import { useMemo } from 'react'; import { useMemoizedFn } from '@/hooks'; import { useBusterNotifications } from '@/context/BusterNotifications'; @@ -407,10 +411,23 @@ export const useAddAndRemoveMetricsFromDashboard = () => { const { openErrorMessage } = useBusterNotifications(); const ensureDashboardConfig = useEnsureDashboardConfig(); - const addMetricToDashboard = useMemoizedFn( + const addAndRemoveMetrics = useMemoizedFn( async ({ metricIds, dashboardId }: { metricIds: string[]; dashboardId: string }) => { const dashboardResponse = await ensureDashboardConfig(dashboardId); + const numberOfItemsOnDashboard: number = + dashboardResponse?.dashboard.config.rows?.reduce( + (acc, row) => acc + (row.items?.length || 0), + 0 + ) || 0; + + if (numberOfItemsOnDashboard > MAX_NUMBER_OF_ITEMS_ON_DASHBOARD) { + openErrorMessage( + `Dashboard is full, please remove some metrics before adding more. You can only have ${MAX_NUMBER_OF_ITEMS_ON_DASHBOARD} metrics on a dashboard` + ); + return; + } + if (dashboardResponse) { const newConfig = addAndRemoveMetricsToDashboard( metricIds, @@ -427,7 +444,7 @@ export const useAddAndRemoveMetricsFromDashboard = () => { ); return useMutation({ - mutationFn: addMetricToDashboard, + mutationFn: addAndRemoveMetrics, onSuccess: (data, variables) => { if (data) { queryClient.setQueryData( diff --git a/web/src/api/buster_rest/search/queryRequests.ts b/web/src/api/buster_rest/search/queryRequests.ts index 267a95c4e..5ab955010 100644 --- a/web/src/api/buster_rest/search/queryRequests.ts +++ b/web/src/api/buster_rest/search/queryRequests.ts @@ -1,11 +1,15 @@ -import { useQuery, keepPreviousData } from '@tanstack/react-query'; +import { useQuery, keepPreviousData, UseQueryOptions } from '@tanstack/react-query'; import { searchQueryKeys } from '@/api/query_keys/search'; import { search } from './requests'; -export const useSearch = (params: Parameters[0]) => { +export const useSearch = ( + params: Parameters[0], + options?: Omit>>, 'queryKey' | 'queryFn'> +) => { return useQuery({ ...searchQueryKeys.getSearchResult(params), queryFn: () => search(params), - placeholderData: keepPreviousData + placeholderData: keepPreviousData, + ...options }); }; diff --git a/web/src/api/query_keys/search.ts b/web/src/api/query_keys/search.ts index 8f2e86bbc..88b3bf92a 100644 --- a/web/src/api/query_keys/search.ts +++ b/web/src/api/query_keys/search.ts @@ -5,7 +5,7 @@ import { search } from '../buster_rest/search'; export const getSearchResult = (params: Parameters[0]) => queryOptions({ queryKey: ['search', 'results', params] as const, - staleTime: 1000 * 10 // 10 seconds, + staleTime: 1000 * 30 // 30 seconds, }); export const searchQueryKeys = { diff --git a/web/src/components/features/auth/ResetPasswordForm.tsx b/web/src/components/features/auth/ResetPasswordForm.tsx index 22946310e..370c8cdb8 100644 --- a/web/src/components/features/auth/ResetPasswordForm.tsx +++ b/web/src/components/features/auth/ResetPasswordForm.tsx @@ -26,7 +26,7 @@ export const ResetPasswordForm: React.FC<{ const [password, setPassword] = useState(''); const [password2, setPassword2] = useState(''); const [goodPassword, setGoodPassword] = useState(false); - const { openErrorNotification, openSuccessMessage } = useBusterNotifications(); + const { openErrorMessage, openSuccessMessage } = useBusterNotifications(); const [countdown, setCountdown] = useState(5); const disabled = !goodPassword || loading || !password || !password2 || password !== password2; @@ -60,7 +60,7 @@ export const ResetPasswordForm: React.FC<{ startCountdown(); } } catch (error) { - openErrorNotification(error); + openErrorMessage(error as string); } }); diff --git a/web/src/components/features/modal/AddToDashboardModal.tsx b/web/src/components/features/modal/AddToDashboardModal.tsx index 42ec1a7d8..ab259e506 100644 --- a/web/src/components/features/modal/AddToDashboardModal.tsx +++ b/web/src/components/features/modal/AddToDashboardModal.tsx @@ -17,11 +17,15 @@ export const AddToDashboardModal: React.FC<{ const [searchTerm, setSearchTerm] = useState(''); const [selectedMetrics, setSelectedMetrics] = useState([]); const debouncedSearchTerm = useDebounce(searchTerm, { wait: 150 }); - const { data: searchResults } = useSearch({ - query: debouncedSearchTerm, - asset_types: ['metric'], - num_results: 100 - }); + + const { data: searchResults } = useSearch( + { + query: debouncedSearchTerm, + asset_types: ['metric'], + num_results: 100 + }, + { enabled: open } + ); const columns = useMemo( () => [ @@ -81,6 +85,63 @@ export const AddToDashboardModal: React.FC<{ return undefined; }, [isFetchedDashboard, rows]); + const addedMetricCount = useMemo(() => { + return selectedMetrics.filter((id) => !Object.keys(dashboard?.metrics || {}).includes(id)) + .length; + }, [dashboard?.metrics, selectedMetrics]); + + const removedMetricCount = useMemo(() => { + return Object.keys(dashboard?.metrics || {}).filter((id) => !selectedMetrics.includes(id)) + .length; + }, [dashboard?.metrics, selectedMetrics]); + + const primaryButtonText = useMemo(() => { + if (!isFetchedDashboard) { + return 'Loading metrics...'; + } + + const hasRemovedItems = removedMetricCount > 0; + const hasAddedItems = addedMetricCount > 0; + + if (hasRemovedItems && hasAddedItems) { + return `Update dashboard`; + } + + if (hasRemovedItems) { + return `Remove metrics`; + } + + if (hasAddedItems) { + return `Add metrics`; + } + + return `Update dashboard`; + }, [isFetchedDashboard, removedMetricCount, addedMetricCount]); + + const primaryButtonTooltipText = useMemo(() => { + if (!isFetchedDashboard) { + return ''; + } + + const hasRemovedItems = removedMetricCount > 0; + const hasAddedItems = addedMetricCount > 0; + const returnText: string[] = []; + + if (!hasRemovedItems && !hasAddedItems) { + return 'No changes to update'; + } + + if (hasRemovedItems) { + returnText.push(`Removing ${removedMetricCount}`); + } + + if (hasAddedItems) { + returnText.push(`Adding ${addedMetricCount}`); + } + + return returnText.join(', '); + }, [isFetchedDashboard, addedMetricCount, removedMetricCount]); + const footer: NonNullable = useMemo(() => { return { left: @@ -94,15 +155,19 @@ export const AddToDashboardModal: React.FC<{ onClick: onClose }, primaryButton: { - text: `Update metrics`, + text: primaryButtonText, onClick: handleAddAndRemoveMetrics, disabled: !isSelectedChanged, - tooltip: isSelectedChanged - ? `Adding ${selectedMetrics.length} metrics` - : 'No changes to update' + tooltip: primaryButtonTooltipText } }; - }, [selectedMetrics.length, isSelectedChanged, handleAddAndRemoveMetrics]); + }, [ + selectedMetrics.length, + primaryButtonTooltipText, + primaryButtonText, + isSelectedChanged, + handleAddAndRemoveMetrics + ]); useLayoutEffect(() => { if (isFetchedDashboard) { @@ -113,7 +178,7 @@ export const AddToDashboardModal: React.FC<{ return (