update update functions

This commit is contained in:
Nate Kelley 2025-03-21 15:43:29 -06:00
parent b7db8dc1ee
commit 9bc97db50c
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
8 changed files with 255 additions and 41 deletions

View File

@ -0,0 +1,118 @@
import { addAndRemoveMetricsToDashboard } from './addAndRemoveMetricsToDashboard';
import type { BusterDashboard } from '@/api/asset_interfaces/dashboard';
describe('addAndRemoveMetricsToDashboard', () => {
const createMockConfig = (metricIds: string[]): BusterDashboard['config'] => ({
rows: [
{
id: 'row-1',
items: metricIds.map((id) => ({ id })),
columnSizes: Array(metricIds.length).fill(12 / metricIds.length),
rowHeight: 320
}
]
});
it('should return existing config when no changes are needed', () => {
const existingConfig = createMockConfig(['metric-1', 'metric-2']);
const result = addAndRemoveMetricsToDashboard(['metric-1', 'metric-2'], existingConfig);
expect(result).toEqual(existingConfig);
// Verify order is maintained
expect(result.rows?.[0].items.map((item) => item.id)).toEqual(['metric-1', 'metric-2']);
});
it('should add new metrics when they dont exist in the dashboard', () => {
const existingConfig = createMockConfig(['metric-1']);
const result = addAndRemoveMetricsToDashboard(
['metric-1', 'metric-2', 'metric-3'],
existingConfig
);
// Verify metrics were added in the correct order
const resultMetricIds = result.rows?.[0].items.map((item) => item.id);
expect(result.rows?.[0].items.map((item) => item.id)).toEqual(['metric-1']);
expect(result.rows?.[1].items.map((item) => item.id)).toEqual(['metric-2', 'metric-3']);
});
it('should remove metrics that are not in the provided array while maintaining order', () => {
const existingConfig = createMockConfig(['metric-1', 'metric-2', 'metric-3']);
const result = addAndRemoveMetricsToDashboard(['metric-1', 'metric-3'], existingConfig);
// Verify metric was removed while maintaining order of remaining metrics
const resultMetricIds = result.rows?.[0].items.map((item) => item.id);
expect(resultMetricIds).toEqual(['metric-1', 'metric-3']);
expect(resultMetricIds?.length).toBe(2);
});
it('should handle both adding and removing metrics simultaneously while maintaining order', () => {
const existingConfig = createMockConfig(['metric-1', 'metric-2', 'metric-3']);
const result = addAndRemoveMetricsToDashboard(
['metric-1', 'metric-4', 'metric-5'],
existingConfig
);
// Verify correct metrics were added and removed in the right order
expect(result.rows?.[0].items.map((item) => item.id)).toEqual(['metric-1']); // Order should match input array
expect(result.rows?.[1].items.map((item) => item.id)).toEqual(['metric-4', 'metric-5']); // Order should match input array
});
it('should handle empty input array by removing all metrics', () => {
const existingConfig = createMockConfig(['metric-1', 'metric-2']);
const result = addAndRemoveMetricsToDashboard([], existingConfig);
// Verify all metrics were removed
expect(result.rows?.length).toBe(0);
});
it('should handle empty existing config by adding all metrics in order', () => {
const emptyConfig: BusterDashboard['config'] = { rows: [] };
const result = addAndRemoveMetricsToDashboard(['metric-1', 'metric-2'], emptyConfig);
// Verify all metrics were added in the correct order
const resultMetricIds = result.rows?.[0].items.map((item) => item.id);
expect(resultMetricIds).toEqual(['metric-1', 'metric-2']);
});
it('should maintain correct column sizes and order when removing metrics', () => {
const existingConfig = createMockConfig(['metric-1', 'metric-2', 'metric-3']);
const result = addAndRemoveMetricsToDashboard(['metric-1', 'metric-3'], existingConfig);
// Verify column sizes are updated correctly while maintaining order
expect(result.rows?.[0].columnSizes).toEqual([6, 6]); // 12/2 = 6 for each column
expect(result.rows?.[0].items.map((item) => item.id)).toEqual(['metric-1', 'metric-3']);
});
it('should handle multiple rows and maintain order within each row', () => {
const existingConfig: BusterDashboard['config'] = {
rows: [
{
id: 'row-1',
items: [{ id: 'metric-1' }, { id: 'metric-2' }],
columnSizes: [6, 6],
rowHeight: 320
},
{
id: 'row-2',
items: [{ id: 'metric-3' }, { id: 'metric-4' }],
columnSizes: [6, 6],
rowHeight: 320
}
]
};
const result = addAndRemoveMetricsToDashboard(
['metric-1', 'metric-3', 'metric-5'],
existingConfig
);
// Verify order is maintained in each row after modifications
expect(result.rows?.[0].items.map((item) => item.id)).toEqual(['metric-1']);
expect(result.rows?.[0].columnSizes).toEqual([12]);
expect(result.rows?.[1].items.map((item) => item.id)).toEqual(['metric-3']);
expect(result.rows?.[1].columnSizes).toEqual([12]);
expect(result.rows?.[2].items.map((item) => item.id)).toEqual(['metric-5']);
expect(result.rows?.[2].columnSizes).toEqual([12]);
});
});

View File

@ -0,0 +1,36 @@
import type { BusterDashboard } from '@/api/asset_interfaces/dashboard';
import { addMetricToDashboardConfig } from './addMetricToDashboard';
import { removeMetricFromDashboardConfig } from './removeMetricFromDashboard';
export const addAndRemoveMetricsToDashboard = (
metricIds: string[],
existingConfig: BusterDashboard['config']
): BusterDashboard['config'] => {
// Get all existing metric IDs from the dashboard
const existingMetricIds = new Set(
existingConfig.rows?.flatMap((row) => row.items.map((item) => item.id)) || []
);
// Determine which metrics to add and remove
const metricsToAdd = metricIds.filter((id) => !existingMetricIds.has(id));
const metricsToRemove = Array.from(existingMetricIds).filter((id) => !metricIds.includes(id));
// If no changes needed, return existing config
if (metricsToAdd.length === 0 && metricsToRemove.length === 0) {
return existingConfig;
}
// First remove metrics if any
const configAfterRemoval =
metricsToRemove.length > 0
? removeMetricFromDashboardConfig(metricsToRemove, existingConfig)
: existingConfig;
// Then add new metrics if any
const finalConfig =
metricsToAdd.length > 0
? addMetricToDashboardConfig(metricsToAdd, configAfterRemoval)
: configAfterRemoval;
return finalConfig;
};

View File

@ -26,6 +26,7 @@ import {
} from '../collections/queryRequests';
import { collectionQueryKeys } from '@/api/query_keys/collection';
import { addMetricToDashboardConfig, removeMetricFromDashboardConfig } from './helpers';
import { addAndRemoveMetricsToDashboard } from './helpers/addAndRemoveMetricsToDashboard';
export const useGetDashboardsList = (
params: Omit<DashboardsListRequest, 'page_token' | 'page_size'>
@ -129,6 +130,7 @@ export const useUpdateDashboardConfig = () => {
const newConfig = create(previousConfig!, (draft) => {
Object.assign(draft, newDashboard);
});
console.log('update', newConfig);
return mutateAsync({
id: newDashboard.id,
config: newConfig
@ -330,29 +332,75 @@ export const useUpdateDashboardShare = () => {
});
};
/**
* Hook for adding metrics to a dashboard. This function also supports removing metrics via the addMetricToDashboardConfig
*/
export const useAddMetricsToDashboard = () => {
const useEnsureDashboardConfig = () => {
const queryClient = useQueryClient();
const prefetchDashboard = useGetDashboardAndInitializeMetrics();
const { openErrorMessage } = useBusterNotifications();
const method = useMemoizedFn(async (dashboardId: string) => {
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;
}
}
return dashboardResponse;
});
return method;
};
export const useAddAndRemoveMetricsFromDashboard = () => {
const queryClient = useQueryClient();
const { openErrorMessage } = useBusterNotifications();
const ensureDashboardConfig = useEnsureDashboardConfig();
const addMetricToDashboard = useMemoizedFn(
async ({ metricIds, dashboardId }: { metricIds: string[]; dashboardId: string }) => {
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;
const dashboardResponse = await ensureDashboardConfig(dashboardId);
if (dashboardResponse) {
const newConfig = addAndRemoveMetricsToDashboard(
metricIds,
dashboardResponse.dashboard.config
);
console.log('add/remove', newConfig);
return dashboardsUpdateDashboard({
id: dashboardId,
config: newConfig
});
if (res) {
queryClient.setQueryData(options.queryKey, res);
dashboardResponse = res;
}
}
openErrorMessage('Failed to save metrics to dashboard');
}
);
return useMutation({
mutationFn: addMetricToDashboard,
onSuccess: (data, variables) => {
queryClient.invalidateQueries({
queryKey: dashboardQueryKeys.dashboardGetDashboard(variables.dashboardId).queryKey
});
}
});
};
export const useAddMetricsToDashboard = () => {
const queryClient = useQueryClient();
const { openErrorMessage } = useBusterNotifications();
const ensureDashboardConfig = useEnsureDashboardConfig();
const addMetricToDashboard = useMemoizedFn(
async ({ metricIds, dashboardId }: { metricIds: string[]; dashboardId: string }) => {
const dashboardResponse = await ensureDashboardConfig(dashboardId);
if (dashboardResponse) {
const newConfig = addMetricToDashboardConfig(metricIds, dashboardResponse.dashboard.config);
return dashboardsUpdateDashboard({
@ -378,7 +426,8 @@ export const useAddMetricsToDashboard = () => {
export const useRemoveMetricsFromDashboard = () => {
const { openConfirmModal, openErrorMessage } = useBusterNotifications();
const queryClient = useQueryClient();
const prefetchDashboard = useGetDashboardAndInitializeMetrics();
const ensureDashboardConfig = useEnsureDashboardConfig();
const removeMetricFromDashboard = useMemoizedFn(
async ({
metricIds,
@ -390,18 +439,7 @@ export const useRemoveMetricsFromDashboard = () => {
useConfirmModal?: boolean;
}) => {
const method = async () => {
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;
}
}
const dashboardResponse = await ensureDashboardConfig(dashboardId);
if (dashboardResponse) {
const newConfig = removeMetricFromDashboardConfig(

View File

@ -4,7 +4,11 @@ import React, { useLayoutEffect, useMemo, useState } from 'react';
import { InputSelectModal, InputSelectModalProps } from '@/components/ui/modal/InputSelectModal';
import { formatDate } from '@/lib';
import { Button } from '@/components/ui/buttons';
import { useAddMetricsToDashboard, useGetDashboard } from '@/api/buster_rest/dashboards';
import {
useAddAndRemoveMetricsFromDashboard,
useAddMetricsToDashboard,
useGetDashboard
} from '@/api/buster_rest/dashboards';
export const AddToDashboardModal: React.FC<{
open: boolean;
@ -13,7 +17,7 @@ export const AddToDashboardModal: React.FC<{
}> = React.memo(({ open, onClose, dashboardId }) => {
const { data: dashboard, isFetched: isFetchedDashboard } = useGetDashboard(dashboardId);
const { data: metrics, isFetched: isFetchedMetrics } = useGetMetricsList({});
const { mutateAsync: addMetricsToDashboard } = useAddMetricsToDashboard();
const { mutateAsync: addAndRemoveMetricsFromDashboard } = useAddAndRemoveMetricsFromDashboard();
const [selectedMetrics, setSelectedMetrics] = useState<string[]>([]);
@ -43,7 +47,7 @@ export const AddToDashboardModal: React.FC<{
}, [metrics.length]);
const handleAddAndRemoveMetrics = useMemoizedFn(async () => {
await addMetricsToDashboard({
await addAndRemoveMetricsFromDashboard({
dashboardId: dashboardId,
metricIds: selectedMetrics
});
@ -102,7 +106,7 @@ export const AddToDashboardModal: React.FC<{
return (
<InputSelectModal
width={650}
width={665}
open={open}
onClose={onClose}
columns={columns}

View File

@ -23,7 +23,9 @@ import { v4 as uuidv4 } from 'uuid';
import { useMemoizedFn } from '@/hooks';
import isEqual from 'lodash/isEqual';
import { BusterResizeRows } from './BusterResizeRows';
import omit from 'lodash/omit';
import { NUMBER_OF_COLUMNS, NEW_ROW_ID, MIN_ROW_HEIGHT, TOP_SASH_ID } from './helpers';
import { create } from 'mutative';
const measuringConfig = {
droppable: {
@ -60,6 +62,8 @@ export const BusterResizeableGrid: React.FC<{
const onRowLayoutChangePreflight = useMemoizedFn((newLayout: BusterResizeableGridRow[]) => {
const filteredRows = newRowPreflight(newLayout);
console.log(filteredRows);
if (checkRowEquality(filteredRows, rows)) {
return;
}
@ -399,9 +403,7 @@ const newRowPreflight = (newRows: BusterResizeableGridRow[]) => {
columnSizes: newColumnSizes
};
}
return {
...row
};
return row;
});
return newRowsCopy;

View File

@ -2,7 +2,7 @@ import React from 'react';
export type ResizeableGridDragItem = {
id: string;
children?: React.ReactNode;
children: React.ReactNode;
};
export type BusterResizeableGridRow = {

View File

@ -4,7 +4,12 @@ import React, { useEffect, useMemo, useState } from 'react';
import isEmpty from 'lodash/isEmpty';
import { BusterResizeableGrid, BusterResizeableGridRow } from '@/components/ui/grid';
import { useDebounceFn, useMemoizedFn } from '@/hooks';
import { hasRemovedMetrics, hasUnmappedMetrics, normalizeNewMetricsIntoGrid } from './helpers';
import {
hasRemovedMetrics,
hasUnmappedMetrics,
normalizeNewMetricsIntoGrid,
removeChildrenFromItems
} from './helpers';
import { DashboardMetricItem } from './DashboardMetricItem';
import { DashboardContentControllerProvider } from './DashboardContentControllerContext';
import type {
@ -14,6 +19,7 @@ import type {
} from '@/api/asset_interfaces';
import { DashboardEmptyState } from './DashboardEmptyState';
import { type useUpdateDashboardConfig } from '@/api/buster_rest/dashboards';
import omit from 'lodash/omit';
const DEFAULT_EMPTY_ROWS: DashboardConfig['rows'] = [];
const DEFAULT_EMPTY_METRICS: Record<string, BusterMetric> = {};
@ -77,7 +83,9 @@ export const DashboardContentController: React.FC<{
);
const onRowLayoutChange = useMemoizedFn((rows: BusterResizeableGridRow[]) => {
if (dashboard) onUpdateDashboardConfig({ rows, id: dashboard.id });
if (dashboard) {
onUpdateDashboardConfig({ rows: removeChildrenFromItems(rows), id: dashboard.id });
}
});
const onDragEnd = useMemoizedFn(() => {

View File

@ -1,6 +1,7 @@
import { DashboardConfig } from '@/api/asset_interfaces/dashboard';
import { BusterMetric } from '@/api/asset_interfaces/metric';
import { BusterResizeableGridRow } from '@/components/ui/grid/interfaces';
import type { DashboardConfig } from '@/api/asset_interfaces/dashboard';
import type { BusterMetric } from '@/api/asset_interfaces/metric';
import { BusterResizeableGridRow } from '@/components/ui/grid';
import omit from 'lodash/omit';
export const hasUnmappedMetrics = (
metrics: Record<string, BusterMetric>,
@ -13,7 +14,7 @@ export const hasUnmappedMetrics = (
export const hasRemovedMetrics = (
metrics: Record<string, BusterMetric>,
configRows: BusterResizeableGridRow[]
configRows: DashboardConfig['rows'] = []
) => {
const allGridItemsLength = configRows.flatMap((r) => r.items).length;
@ -25,3 +26,10 @@ export const hasRemovedMetrics = (
r.items.some((t) => Object.values(metrics).some((m) => t.id === m.id))
);
};
export const removeChildrenFromItems = (row: BusterResizeableGridRow[]) => {
return row.map((r) => ({
...r,
items: r.items.map((i) => omit(i, 'children'))
}));
};