From 26ec6c45898295c6debfab95f9f1d4cc4ddf8960 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Fri, 28 Mar 2025 15:15:49 -0600 Subject: [PATCH] restore version updates --- .../versionHistory/VersionHistoryPanel.tsx | 83 ++++++---- .../useListVersionHistories.tsx | 145 +++++++++++++++--- .../FileContainerHeaderVersionHistory.tsx | 74 +++------ 3 files changed, 204 insertions(+), 98 deletions(-) diff --git a/web/src/components/features/versionHistory/VersionHistoryPanel.tsx b/web/src/components/features/versionHistory/VersionHistoryPanel.tsx index 7617b8e4c..d5e13bc53 100644 --- a/web/src/components/features/versionHistory/VersionHistoryPanel.tsx +++ b/web/src/components/features/versionHistory/VersionHistoryPanel.tsx @@ -1,22 +1,26 @@ -import { useGetDashboard } from '@/api/buster_rest/dashboards'; -import { useGetMetric } from '@/api/buster_rest/metrics'; import React, { useMemo, useRef } from 'react'; import { Button } from '@/components/ui/buttons'; -import { Xmark } from '@/components/ui/icons'; +import { Xmark, History } from '@/components/ui/icons'; import { Check3 } from '@/components/ui/icons/NucleoIconFilled'; import { Text } from '@/components/ui/typography'; import { useCloseVersionHistory } from '@/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory'; import { cn } from '@/lib/classMerge'; import { timeFromNow, timeout } from '@/lib'; import { AppPageLayout } from '@/components/ui/layouts'; -import { useSearchParams } from 'next/navigation'; -import last from 'lodash/last'; import { useListVersionHistories } from './useListVersionHistories'; import { useMount } from '@/hooks'; +import { AppTooltip } from '@/components/ui/tooltip'; export const VersionHistoryPanel = React.memo( ({ assetId, type }: { assetId: string; type: 'metric' | 'dashboard' }) => { - const { listItems, selectedVersion, onClickVersionHistory } = useListVersionHistories({ + const removeVersionHistoryQueryParams = useCloseVersionHistory(); + const { + listItems, + selectedVersion, + selectedQueryVersion, + onClickRestoreVersion, + onClickVersionHistory + } = useListVersionHistories({ assetId, type }); @@ -24,7 +28,7 @@ export const VersionHistoryPanel = React.memo( useMount(async () => { if (bodyRef.current) { - await timeout(200); + await timeout(250); const selectedNode = bodyRef.current.querySelector('.selected-version'); if (selectedNode) { selectedNode.scrollIntoView({ behavior: 'smooth', block: 'start' }); @@ -34,22 +38,24 @@ export const VersionHistoryPanel = React.memo( return ( ( - + ), [] )} scrollable - headerBorderVariant="ghost" - headerClassName="border-l"> + headerBorderVariant="ghost">
{listItems?.map((item) => ( ))}
@@ -63,18 +69,22 @@ const ListItem = React.memo( version_number, updated_at, selected, - onClickVersionHistory + showRestoreButton, + onClickVersionHistory, + onClickRestoreVersion }: { version_number: number; updated_at: string; selected: boolean; + showRestoreButton: boolean; onClickVersionHistory: (versionNumber: number) => void; + onClickRestoreVersion: (versionNumber: number) => void; }) => { return (
onClickVersionHistory(version_number)} className={cn( - 'hover:bg-item-hover flex cursor-pointer items-center justify-between space-x-2 rounded px-2.5 py-1.5', + 'group hover:bg-item-hover flex cursor-pointer items-center justify-between space-x-2 rounded px-2.5 py-1.5', selected && 'bg-item-select hover:bg-item-select selected-version' )}>
@@ -83,27 +93,44 @@ const ListItem = React.memo( {timeFromNow(updated_at, false)}
- {selected && ( -
- -
- )} + +
+ {showRestoreButton && ( + +
{ + e.stopPropagation(); + e.preventDefault(); + onClickRestoreVersion(version_number); + }} + className="hover:bg-item-select -mr-1 rounded p-1 opacity-0 group-hover:block group-hover:opacity-100"> + +
+
+ )} + + {selected && ( +
+ +
+ )} +
); } ); ListItem.displayName = 'ListItem'; -const PanelHeader = React.memo(() => { - const removeVersionHistoryQueryParams = useCloseVersionHistory(); - - return ( -
- Version History -
- ); -}); +const PanelHeader = React.memo( + ({ removeVersionHistoryQueryParams }: { removeVersionHistoryQueryParams: () => void }) => { + return ( +
+ Version History +
+ ); + } +); PanelHeader.displayName = 'PanelHeader'; VersionHistoryPanel.displayName = 'VersionHistoryPanel'; diff --git a/web/src/components/features/versionHistory/useListVersionHistories.tsx b/web/src/components/features/versionHistory/useListVersionHistories.tsx index cb03d4b27..90c31f339 100644 --- a/web/src/components/features/versionHistory/useListVersionHistories.tsx +++ b/web/src/components/features/versionHistory/useListVersionHistories.tsx @@ -1,7 +1,11 @@ -import { useGetDashboard } from '@/api/buster_rest/dashboards'; -import { useGetMetric } from '@/api/buster_rest/metrics'; +'use client'; + +import { useGetDashboard, useUpdateDashboard } from '@/api/buster_rest/dashboards'; +import { useGetMetric, useSaveMetric } from '@/api/buster_rest/metrics'; import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; import { useMemoizedFn } from '@/hooks'; +import { useCloseVersionHistory } from '@/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory'; +import { BusterRoutes, createBusterRoute } from '@/routes'; import last from 'lodash/last'; import { useSearchParams } from 'next/navigation'; import { useMemo } from 'react'; @@ -13,14 +17,26 @@ export const useListVersionHistories = ({ assetId: string; type: 'metric' | 'dashboard'; }) => { + const removeVersionHistoryQueryParams = useCloseVersionHistory(); const onChangeQueryParams = useAppLayoutContextSelector((x) => x.onChangeQueryParams); - const { dashboardVersions, selectedVersion: dashboardSelectedVersion } = useListDashboardVersions( - { - assetId, - type - } - ); - const { metricVersions, selectedVersion: metricSelectedVersion } = useListMetricVersions({ + const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage); + const { + dashboardVersions, + selectedQueryVersion: dashboardSelectedQueryVersion, + selectedVersion: dashboardSelectedVersion, + onRestoreVersion: onRestoreDashboardVersion, + isSavingDashboard + } = useListDashboardVersions({ + assetId, + type + }); + const { + metricVersions, + selectedQueryVersion: metricSelectedQueryVersion, + selectedVersion: metricSelectedVersion, + onRestoreVersion: onRestoreMetricVersion, + isSavingMetric + } = useListMetricVersions({ assetId, type }); @@ -34,19 +50,65 @@ export const useListVersionHistories = ({ return type === 'metric' ? metricSelectedVersion : dashboardSelectedVersion; }, [type, dashboardSelectedVersion, metricSelectedVersion]); + const selectedQueryVersion = useMemo(() => { + return type === 'metric' ? metricSelectedQueryVersion : dashboardSelectedQueryVersion; + }, [type, dashboardSelectedQueryVersion, metricSelectedQueryVersion]); + const onClickVersionHistory = useMemoizedFn((versionNumber: number) => { if (type === 'metric') onChangeQueryParams({ metric_version_number: versionNumber.toString() }); if (type === 'dashboard') onChangeQueryParams({ dashboard_version_number: versionNumber.toString() }); }); + const onClickRestoreVersion = useMemoizedFn( + async (versionNumber: number, rereouteToAsset: boolean = true) => { + if (type === 'metric') { + await onRestoreMetricVersion(versionNumber); + if (rereouteToAsset) { + await onChangePage( + createBusterRoute({ + route: BusterRoutes.APP_METRIC_ID, + metricId: assetId + }) + ); + } + } + if (type === 'dashboard') { + await onRestoreDashboardVersion(versionNumber); + if (rereouteToAsset) { + await onChangePage( + createBusterRoute({ + route: BusterRoutes.APP_DASHBOARD_ID, + dashboardId: assetId + }) + ); + } + } + + removeVersionHistoryQueryParams(); + } + ); + + const isRestoringVersion = useMemo(() => { + return isSavingDashboard || isSavingMetric; + }, [isSavingDashboard, isSavingMetric]); + return useMemo(() => { return { listItems, selectedVersion, - onClickVersionHistory + selectedQueryVersion, + onClickVersionHistory, + onClickRestoreVersion, + isRestoringVersion }; - }, [listItems, selectedVersion, onClickVersionHistory]); + }, [ + listItems, + selectedVersion, + selectedQueryVersion, + onClickVersionHistory, + onClickRestoreVersion + ]); }; const useListDashboardVersions = ({ @@ -57,25 +119,48 @@ const useListDashboardVersions = ({ type: 'metric' | 'dashboard'; }) => { const selectedVersionParam = useSearchParams().get('dashboard_version_number'); - const { data: dashboardVersions } = useGetDashboard( + const { mutateAsync: updateDashboard, isPending: isSavingDashboard } = useUpdateDashboard(); + const { data: dashData } = useGetDashboard( { id: type === 'dashboard' ? assetId : undefined, version_number: null }, - (x) => x.dashboard.versions + (x) => ({ + versions: x.dashboard.versions, + version_number: x.dashboard.version_number + }) ); - const selectedVersion = useMemo(() => { + const dashboardVersions = dashData?.versions; + const selectedVersion = dashData?.version_number; + + const selectedQueryVersion = useMemo(() => { if (selectedVersionParam) return parseInt(selectedVersionParam); return last(dashboardVersions)?.version_number; }, [dashboardVersions, selectedVersionParam]); + const onRestoreVersion = useMemoizedFn(async (versionNumber: number) => { + await updateDashboard({ + id: assetId, + restore_to_version: versionNumber + }); + }); + return useMemo(() => { return { dashboardVersions, - selectedVersion + selectedQueryVersion, + onRestoreVersion, + selectedVersion, + isSavingDashboard }; - }, [dashboardVersions, selectedVersion]); + }, [ + dashboardVersions, + selectedVersion, + onRestoreVersion, + selectedQueryVersion, + isSavingDashboard + ]); }; const useListMetricVersions = ({ @@ -85,16 +170,31 @@ const useListMetricVersions = ({ assetId: string; type: 'metric' | 'dashboard'; }) => { + const { mutateAsync: updateMetric, isPending: isSavingMetric } = useSaveMetric({ + updateOnSave: true + }); const selectedVersionParam = useSearchParams().get('metric_version_number'); - const { data: metricVersions } = useGetMetric( + const { data: metricData } = useGetMetric( { id: type === 'metric' ? assetId : undefined, version_number: null }, - (x) => x.versions + (x) => ({ + versions: x.versions, + version_number: x.version_number + }) ); + const metricVersions = metricData?.versions; + const selectedVersion = metricData?.version_number; - const selectedVersion = useMemo(() => { + const onRestoreVersion = useMemoizedFn(async (versionNumber: number) => { + await updateMetric({ + id: assetId, + restore_to_version: versionNumber + }); + }); + + const selectedQueryVersion = useMemo(() => { if (selectedVersionParam) return parseInt(selectedVersionParam); return last(metricVersions)?.version_number; }, [metricVersions, selectedVersionParam]); @@ -102,7 +202,10 @@ const useListMetricVersions = ({ return useMemo(() => { return { metricVersions, - selectedVersion + selectedQueryVersion, + onRestoreVersion, + selectedVersion, + isSavingMetric }; - }, [metricVersions, selectedVersion]); + }, [metricVersions, onRestoreVersion, selectedQueryVersion, selectedVersion, isSavingMetric]); }; diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory/FileContainerHeaderVersionHistory.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory/FileContainerHeaderVersionHistory.tsx index 1799776c4..1c43d3d8a 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory/FileContainerHeaderVersionHistory.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory/FileContainerHeaderVersionHistory.tsx @@ -4,14 +4,10 @@ import { Button } from '@/components/ui/buttons'; import { ArrowLeft, History } from '@/components/ui/icons'; import React from 'react'; import { useChatLayoutContextSelector } from '../../../ChatLayoutContext'; -import last from 'lodash/last'; import first from 'lodash/first'; import { useCloseVersionHistory } from './useCloseVersionHistory'; import { useListVersionHistories } from '@/components/features/versionHistory'; import { useMemoizedFn } from '@/hooks'; -import { useBusterNotifications } from '@/context/BusterNotifications'; -import { useSaveMetric } from '@/api/buster_rest/metrics'; -import { useUpdateDashboard } from '@/api/buster_rest/dashboards'; export const FileContainerHeaderVersionHistory = React.memo(() => { const removeVersionHistoryQueryParams = useCloseVersionHistory(); @@ -19,63 +15,43 @@ export const FileContainerHeaderVersionHistory = React.memo(() => { return (
- +
); }); FileContainerHeaderVersionHistory.displayName = 'FileContainerHeaderVersionHistory'; -const DisplayVersionHistory = React.memo( - ({ removeVersionHistoryQueryParams }: { removeVersionHistoryQueryParams: () => void }) => { - const { openSuccessMessage } = useBusterNotifications(); - const selectedFile = useChatLayoutContextSelector((x) => x.selectedFile); - const { listItems, selectedVersion } = useListVersionHistories({ +const DisplayVersionHistory = React.memo(({}: {}) => { + const selectedFile = useChatLayoutContextSelector((x) => x.selectedFile); + const { listItems, isRestoringVersion, selectedQueryVersion, onClickRestoreVersion } = + useListVersionHistories({ assetId: selectedFile?.id || '', type: selectedFile?.type as 'metric' | 'dashboard' }); - const { mutateAsync: saveMetric, isPending: isSavingMetric } = useSaveMetric({ - updateOnSave: true - }); - const { mutateAsync: saveDashboard, isPending: isSavingDashboard } = useUpdateDashboard(); + const currentVersion = first(listItems)?.version_number; + const isSelectedVersionCurrent = selectedQueryVersion === currentVersion; - const isSaving = isSavingMetric || isSavingDashboard; + const onClickRestoreVersionPreflight = useMemoizedFn(async () => { + if (selectedQueryVersion) { + await onClickRestoreVersion(selectedQueryVersion, true); + } + }); - const currentVersion = first(listItems)?.version_number; - const isSelectedVersionCurrent = selectedVersion === currentVersion; - - const onMakeCurrentVersion = useMemoizedFn(async () => { - const params = { - id: selectedFile?.id || '', - update_version: true, - restore_to_version: selectedVersion - }; - - if (selectedFile?.type === 'metric') { - await saveMetric(params); - } - if (selectedFile?.type === 'dashboard') { - await saveDashboard(params); - } - removeVersionHistoryQueryParams(); - openSuccessMessage('Successfully made current version'); - }); - - return ( -
- - -
- ); - } -); + return ( +
+ + +
+ ); +}); DisplayVersionHistory.displayName = 'DisplayVersionHistory'; const ExitVersionHistoryButton = React.memo(