update dashboard request strucutre

This commit is contained in:
Nate Kelley 2025-04-09 14:00:48 -06:00
parent 0e2fd9a93c
commit c66b1878f3
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
14 changed files with 109 additions and 55 deletions

View File

@ -32,6 +32,7 @@ import { addMetricToDashboardConfig, removeMetricFromDashboardConfig } from './h
import { addAndRemoveMetricsToDashboard } from './helpers/addAndRemoveMetricsToDashboard'; import { addAndRemoveMetricsToDashboard } from './helpers/addAndRemoveMetricsToDashboard';
import { useParams, useSearchParams } from 'next/navigation'; import { useParams, useSearchParams } from 'next/navigation';
import { RustApiError } from '../errors'; import { RustApiError } from '../errors';
import { useOriginalDashboardStore } from '@/context/Dashboards';
export const useGetDashboardsList = ( export const useGetDashboardsList = (
params: Omit<Parameters<typeof dashboardsGetList>[0], 'page_token' | 'page_size'> params: Omit<Parameters<typeof dashboardsGetList>[0], 'page_token' | 'page_size'>
@ -125,7 +126,8 @@ export const useUpdateDashboard = (params?: {
updateVersion?: boolean; updateVersion?: boolean;
saveToServer?: boolean; saveToServer?: boolean;
}) => { }) => {
const { updateVersion = true, saveToServer = false } = params || {}; const setOriginalDashboards = useOriginalDashboardStore((x) => x.setOriginalDashboard);
const { updateVersion = false, saveToServer = false } = params || {};
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -159,6 +161,7 @@ export const useUpdateDashboard = (params?: {
dashboardQueryKeys.dashboardGetDashboard(variables.id).queryKey, dashboardQueryKeys.dashboardGetDashboard(variables.id).queryKey,
data data
); );
setOriginalDashboards(data.dashboard);
} }
} }
}); });
@ -176,12 +179,13 @@ export const useUpdateDashboardConfig = (params?: {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const method = useMemoizedFn( const method = useMemoizedFn(
async ( async ({
newDashboard: Partial<BusterDashboard['config']> & { dashboardId,
id: string; ...newDashboard
} }: Partial<BusterDashboard['config']> & {
) => { dashboardId: string;
const options = dashboardQueryKeys.dashboardGetDashboard(newDashboard.id); }) => {
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId);
const previousDashboard = queryClient.getQueryData(options.queryKey); const previousDashboard = queryClient.getQueryData(options.queryKey);
const previousConfig = previousDashboard?.dashboard?.config; const previousConfig = previousDashboard?.dashboard?.config;
if (previousConfig) { if (previousConfig) {
@ -189,7 +193,7 @@ export const useUpdateDashboardConfig = (params?: {
Object.assign(draft, newDashboard); Object.assign(draft, newDashboard);
}); });
return mutateAsync({ return mutateAsync({
id: newDashboard.id, id: dashboardId,
config: newConfig config: newConfig
}); });
} }

View File

@ -390,7 +390,7 @@ export const useUpdateMetric = (params: {
const { const {
wait = 0, wait = 0,
updateOnSave = false, updateOnSave = false,
updateVersion = true, updateVersion = false,
saveToServer = false saveToServer = false
} = params || {}; } = params || {};
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -408,8 +408,10 @@ export const useUpdateMetric = (params: {
); );
const combineAndSaveMetric = useMemoizedFn( const combineAndSaveMetric = useMemoizedFn(
(newMetricPartial: Omit<Partial<IBusterMetric>, 'status'> & { id: string }) => { ({
const metricId = newMetricPartial.id; id: metricId,
...newMetricPartial
}: Omit<Partial<IBusterMetric>, 'status'> & { id: string }) => {
const options = metricsQueryKeys.metricsGetMetric(metricId); const options = metricsQueryKeys.metricsGetMetric(metricId);
const prevMetric = getOriginalMetric(metricId); const prevMetric = getOriginalMetric(metricId);
const newMetric = create(prevMetric, (draft) => { const newMetric = create(prevMetric, (draft) => {

View File

@ -133,7 +133,7 @@ export const BusterResizeColumns: React.FC<ContainerProps> = ({
useLayoutEffect(() => { useLayoutEffect(() => {
setSizes(columnSpansToPercent(columnSizes)); setSizes(columnSpansToPercent(columnSizes));
}, [items.length, columnSizes?.length]); }, [items.length, columnSizes]);
return ( return (
<SortableContext id={rowId} items={items} disabled={false}> <SortableContext id={rowId} items={items} disabled={false}>

View File

@ -63,7 +63,6 @@ export const BusterResizeableGrid: React.FC<{
if (checkRowEquality(filteredRows, rows)) { if (checkRowEquality(filteredRows, rows)) {
return; return;
} }
onRowLayoutChange(filteredRows); onRowLayoutChange(filteredRows);
setRows(filteredRows); setRows(filteredRows);
}); });

View File

@ -8,8 +8,8 @@ export const useChatUpdate = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const onUpdateChat = useMemoizedFn( const onUpdateChat = useMemoizedFn(
async (newChatConfig: Partial<IBusterChat> & { id: string }) => { async ({ id: chatId, ...newChatConfig }: Partial<IBusterChat> & { id: string }) => {
const options = queryKeys.chatsGetChat(newChatConfig.id); const options = queryKeys.chatsGetChat(chatId);
const queryKey = options.queryKey; const queryKey = options.queryKey;
const currentData = queryClient.getQueryData<IBusterChat>(queryKey); const currentData = queryClient.getQueryData<IBusterChat>(queryKey);
const iChat = create(currentData || ({} as IBusterChat), (draft) => { const iChat = create(currentData || ({} as IBusterChat), (draft) => {

View File

@ -5,6 +5,8 @@ import { useGetDashboard } from '@/api/buster_rest/dashboards';
import { BusterDashboardResponse } from '@/api/asset_interfaces/dashboard'; import { BusterDashboardResponse } from '@/api/asset_interfaces/dashboard';
import { dashboardQueryKeys } from '@/api/query_keys/dashboard'; import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
import { compareObjectsByKeys } from '@/lib/objects'; import { compareObjectsByKeys } from '@/lib/objects';
import { useMemo } from 'react';
import { create } from 'mutative';
export const useIsDashboardChanged = ({ dashboardId }: { dashboardId: string }) => { export const useIsDashboardChanged = ({ dashboardId }: { dashboardId: string }) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -26,17 +28,16 @@ export const useIsDashboardChanged = ({ dashboardId }: { dashboardId: string })
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId); const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId);
const currentDashboard = queryClient.getQueryData<BusterDashboardResponse>(options.queryKey); const currentDashboard = queryClient.getQueryData<BusterDashboardResponse>(options.queryKey);
if (originalDashboard && currentDashboard) { if (originalDashboard && currentDashboard) {
queryClient.setQueryData(options.queryKey, { const resetDashboard = create(currentDashboard, (draft) => {
...currentDashboard, Object.assign(draft, originalDashboard);
dashboard: originalDashboard
}); });
queryClient.setQueryData(options.queryKey, resetDashboard);
} }
refetchCurrentDashboard(); refetchCurrentDashboard();
}); });
return { const isDashboardChanged = useMemo(() => {
onResetDashboardToOriginal, return (
isDashboardChanged:
!originalDashboard || !originalDashboard ||
!currentDashboard || !currentDashboard ||
!compareObjectsByKeys(originalDashboard, currentDashboard, [ !compareObjectsByKeys(originalDashboard, currentDashboard, [
@ -45,5 +46,11 @@ export const useIsDashboardChanged = ({ dashboardId }: { dashboardId: string })
'config', 'config',
'file' 'file'
]) ])
);
}, [originalDashboard, currentDashboard]);
return {
onResetDashboardToOriginal,
isDashboardChanged
}; };
}; };

View File

@ -10,7 +10,6 @@ import { useUpdateMetric } from '@/api/buster_rest/metrics';
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { useGetMetricMemoized } from './useGetMetricMemoized'; import { useGetMetricMemoized } from './useGetMetricMemoized';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
import { useOriginalMetricStore } from './useOriginalMetricStore';
import { timeout } from '@/lib'; import { timeout } from '@/lib';
import { useState } from 'react'; import { useState } from 'react';

View File

@ -94,7 +94,7 @@ export const DashboardContentController: React.FC<{
const onRowLayoutChange = useMemoizedFn((rows: BusterResizeableGridRow[]) => { const onRowLayoutChange = useMemoizedFn((rows: BusterResizeableGridRow[]) => {
if (dashboard) { if (dashboard) {
onUpdateDashboardConfig({ rows: removeChildrenFromItems(rows), id: dashboard.id }); onUpdateDashboardConfig({ rows: removeChildrenFromItems(rows), dashboardId: dashboard.id });
} }
}); });
@ -108,7 +108,7 @@ export const DashboardContentController: React.FC<{
useEffect(() => { useEffect(() => {
if (remapMetrics && dashboard?.id) { if (remapMetrics && dashboard?.id) {
debouncedForInitialRenderOnUpdateDashboardConfig({ rows, id: dashboard.id }); debouncedForInitialRenderOnUpdateDashboardConfig({ rows, dashboardId: dashboard.id });
} }
}, [dashboard?.id, remapMetrics]); }, [dashboard?.id, remapMetrics]);

View File

@ -25,11 +25,10 @@ export const DashboardEditTitles: React.FC<{
if (!readOnly) onUpdateDashboard({ name, id: dashboardId }); if (!readOnly) onUpdateDashboard({ name, id: dashboardId });
}); });
const { run: onChangeDashboardDescription } = useDebounceFn( const onChangeDashboardDescription = useMemoizedFn(
useMemoizedFn((value: React.ChangeEvent<HTMLTextAreaElement>) => { (value: React.ChangeEvent<HTMLTextAreaElement>) => {
if (!readOnly) onUpdateDashboard({ description: value.target.value, id: dashboardId }); if (!readOnly) onUpdateDashboard({ description: value.target.value, id: dashboardId });
}), }
{ wait: 650 }
); );
return ( return (
@ -37,6 +36,7 @@ export const DashboardEditTitles: React.FC<{
<EditableTitle <EditableTitle
className="w-full truncate" className="w-full truncate"
readOnly={readOnly} readOnly={readOnly}
onSetValue={onChangeTitle}
onChange={onChangeTitle} onChange={onChangeTitle}
id={DASHBOARD_TITLE_INPUT_ID} id={DASHBOARD_TITLE_INPUT_ID}
placeholder="New dashboard" placeholder="New dashboard"

View File

@ -0,0 +1,38 @@
import { useGetDashboard, useUpdateDashboard } from '@/api/buster_rest/dashboards';
import { SaveResetFilePopup } from '@/components/features/popups/SaveResetFilePopup';
import { useIsDashboardChanged } from '@/context/Dashboards';
import { useMemoizedFn } from '@/hooks';
import React from 'react';
export const DashboardSaveFilePopup: React.FC<{ dashboardId: string }> = React.memo(
({ dashboardId }) => {
const { data: dashboardResponse } = useGetDashboard({ id: dashboardId });
const { isDashboardChanged, onResetDashboardToOriginal } = useIsDashboardChanged({
dashboardId
});
const { mutateAsync: onSaveDashboard, isPending: isSaving } = useUpdateDashboard({
saveToServer: true
});
const onSaveDashboardToServer = useMemoizedFn(() => {
const dashboard = dashboardResponse?.dashboard;
onSaveDashboard({
id: dashboardId,
name: dashboard?.name,
description: dashboard?.description,
config: dashboard?.config,
file: dashboard?.file
});
});
return (
<SaveResetFilePopup
open={isDashboardChanged}
onReset={onResetDashboardToOriginal}
onSave={onSaveDashboardToServer}
isSaving={isSaving}
showHotsKeys={false}
/>
);
}
);

View File

@ -8,6 +8,8 @@ import { useDashboardContentStore, useIsDashboardChanged } from '@/context/Dashb
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { StatusCard } from '@/components/ui/card/StatusCard'; import { StatusCard } from '@/components/ui/card/StatusCard';
import { useIsDashboardReadOnly } from '@/context/Dashboards/useIsDashboardReadOnly'; import { useIsDashboardReadOnly } from '@/context/Dashboards/useIsDashboardReadOnly';
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout';
import { DashboardSaveFilePopup } from './DashboardSaveFilePopup';
export const DashboardViewDashboardController: React.FC<{ export const DashboardViewDashboardController: React.FC<{
dashboardId: string; dashboardId: string;
@ -22,10 +24,8 @@ export const DashboardViewDashboardController: React.FC<{
} = useGetDashboard({ id: dashboardId }); } = useGetDashboard({ id: dashboardId });
const { mutateAsync: onUpdateDashboardConfig } = useUpdateDashboardConfig(); const { mutateAsync: onUpdateDashboardConfig } = useUpdateDashboardConfig();
const isVersionHistoryMode = useChatLayoutContextSelector((x) => x.isVersionHistoryMode);
const onOpenAddContentModal = useDashboardContentStore((x) => x.onOpenAddContentModal); const onOpenAddContentModal = useDashboardContentStore((x) => x.onOpenAddContentModal);
const { isDashboardChanged, onResetDashboardToOriginal } = useIsDashboardChanged({
dashboardId
});
const metrics = dashboardResponse?.metrics; const metrics = dashboardResponse?.metrics;
const dashboard = dashboardResponse?.dashboard; const dashboard = dashboardResponse?.dashboard;
@ -34,6 +34,10 @@ export const DashboardViewDashboardController: React.FC<{
readOnly: readOnlyProp readOnly: readOnlyProp
}); });
if (!isFetched) {
return <></>;
}
if (isError) { if (isError) {
return ( return (
<div className="p-10"> <div className="p-10">
@ -42,10 +46,6 @@ export const DashboardViewDashboardController: React.FC<{
); );
} }
if (!isFetched) {
return <></>;
}
return ( return (
<ScrollArea className="h-full"> <ScrollArea className="h-full">
<div className="flex h-full flex-col space-y-3 p-10"> <div className="flex h-full flex-col space-y-3 p-10">
@ -64,6 +64,8 @@ export const DashboardViewDashboardController: React.FC<{
onOpenAddContentModal={onOpenAddContentModal} onOpenAddContentModal={onOpenAddContentModal}
readOnly={isReadOnly} readOnly={isReadOnly}
/> />
{!isVersionHistoryMode && <DashboardSaveFilePopup dashboardId={dashboardId} />}
</div> </div>
</ScrollArea> </ScrollArea>
); );

View File

@ -0,0 +1,21 @@
import { SaveResetFilePopup } from '@/components/features/popups/SaveResetFilePopup';
import { useIsMetricChanged } from '@/context/Metrics/useIsMetricChanged';
import { useUpdateMetricChart } from '@/context/Metrics/useUpdateMetricChart';
import React from 'react';
export const MetricSaveFilePopup: React.FC<{ metricId: string }> = React.memo(({ metricId }) => {
const { isMetricChanged, onResetMetricToOriginal } = useIsMetricChanged({ metricId });
const { onSaveMetricToServer, isSaving } = useUpdateMetricChart({ metricId });
return (
<SaveResetFilePopup
open={isMetricChanged}
onReset={onResetMetricToOriginal}
onSave={onSaveMetricToServer}
isSaving={isSaving}
showHotsKeys={false}
/>
);
});
MetricSaveFilePopup.displayName = 'MetricSaveFilePopup';

View File

@ -14,6 +14,7 @@ import { SaveResetFilePopup } from '@/components/features/popups/SaveResetFilePo
import { useIsMetricChanged } from '@/context/Metrics/useIsMetricChanged'; import { useIsMetricChanged } from '@/context/Metrics/useIsMetricChanged';
import { useUpdateMetricChart } from '@/context/Metrics'; import { useUpdateMetricChart } from '@/context/Metrics';
import { useIsMetricReadOnly } from '@/context/Metrics/useIsMetricReadOnly'; import { useIsMetricReadOnly } from '@/context/Metrics/useIsMetricReadOnly';
import { MetricSaveFilePopup } from './MetricSaveFilePopup';
export const MetricViewChart: React.FC<{ export const MetricViewChart: React.FC<{
metricId: string; metricId: string;
@ -161,20 +162,3 @@ const AnimatePresenceWrapper: React.FC<{
</AnimatePresence> </AnimatePresence>
); );
}; };
const MetricSaveFilePopup: React.FC<{ metricId: string }> = React.memo(({ metricId }) => {
const { isMetricChanged, onResetMetricToOriginal } = useIsMetricChanged({ metricId });
const { onSaveMetricToServer, isSaving } = useUpdateMetricChart({ metricId });
return (
<SaveResetFilePopup
open={isMetricChanged}
onReset={onResetMetricToOriginal}
onSave={onSaveMetricToServer}
isSaving={isSaving}
showHotsKeys={false}
/>
);
});
MetricSaveFilePopup.displayName = 'MetricSaveFilePopup';

View File

@ -14,8 +14,7 @@ import {
useBulkUpdateMetricVerificationStatus, useBulkUpdateMetricVerificationStatus,
useDeleteMetric, useDeleteMetric,
useRemoveMetricFromCollection, useRemoveMetricFromCollection,
useSaveMetricToCollections, useSaveMetricToCollections
useUpdateMetric
} from '@/api/buster_rest/metrics'; } from '@/api/buster_rest/metrics';
import { useThreeDotFavoritesOptions } from '@/components/features/dropdowns/useThreeDotFavoritesOptions'; import { useThreeDotFavoritesOptions } from '@/components/features/dropdowns/useThreeDotFavoritesOptions';
@ -123,7 +122,6 @@ const StatusButton: React.FC<{
const isAdmin = useUserConfigContextSelector((state) => state.isAdmin); const isAdmin = useUserConfigContextSelector((state) => state.isAdmin);
const onVerify = useMemoizedFn(async (data: { id: string; status: VerificationStatus }[]) => { const onVerify = useMemoizedFn(async (data: { id: string; status: VerificationStatus }[]) => {
console.log('onVerify', data);
await updateStatus(data); await updateStatus(data);
onSelectChange([]); onSelectChange([]);
}); });