new method for viewing secondary panel

This commit is contained in:
Nate Kelley 2025-03-25 23:14:36 -06:00
parent 9eb70c204f
commit 60d59ff313
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
22 changed files with 304 additions and 28 deletions

View File

@ -0,0 +1,9 @@
import React from 'react';
export const VersionHistoryPanel = React.memo(
({ assetId, type }: { assetId: string; type: 'metric' | 'dashboard' }) => {
return <div>VersionHistoryPanel</div>;
}
);
VersionHistoryPanel.displayName = 'VersionHistoryPanel';

View File

@ -0,0 +1 @@
export * from './VersionHistoryPanel';

View File

@ -14,3 +14,5 @@ export const MetricEditController: React.FC<{
});
MetricEditController.displayName = 'MetricEditController';
export default MetricEditController;

View File

@ -1,12 +1,13 @@
'use client';
import React, { useRef } from 'react';
import React, { useMemo, useRef } from 'react';
import type { MetricViewProps } from '../config';
import { AppSplitter, AppSplitterRef } from '@/components/ui/layouts';
import { MetricViewChart } from './MetricViewChart';
import { MetricEditController } from './MetricEditController';
import { useMetricLayout } from '../useMetricLayout';
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout';
import { VersionHistoryPanel } from '@/components/features/versionHistory';
const autoSaveId = 'metric-edit-chart';
@ -23,12 +24,21 @@ export const MetricViewChartController: React.FC<MetricViewProps> = React.memo((
type: 'chart'
});
const RightChildren = useMemo(() => {
if (!renderSecondary) return null;
if (selectedFileViewSecondary === 'chart-edit')
return <MetricEditController metricId={metricId} />;
if (selectedFileViewSecondary === 'version-history')
return <VersionHistoryPanel assetId={metricId} type="metric" />;
return null;
}, [renderSecondary, metricId]);
return (
<AppSplitter
ref={appSplitterRef}
initialReady={false}
leftChildren={<MetricViewChart metricId={metricId} />}
rightChildren={<MetricEditController metricId={metricId} />}
rightChildren={RightChildren}
rightHidden={!renderSecondary}
autoSaveId={autoSaveId}
defaultLayout={defaultLayout}

View File

@ -1 +1,2 @@
export * from './MetricViewChartController';
export * from './MetricViewChart';

View File

@ -3,13 +3,14 @@ import type { MetricFileView } from '@/layouts/ChatLayout';
import { MetricViewChartController } from './MetricViewChart';
import { MetricViewFile } from './MetricViewFile';
import { MetricViewResults } from './MetricViewResults';
import { MetricViewChart } from './MetricViewChart';
export interface MetricViewProps {
metricId: string;
}
export const MetricViewComponents: Record<MetricFileView, React.FC<MetricViewProps>> = {
chart: MetricViewChartController,
chart: MetricViewChart,
results: MetricViewResults,
file: MetricViewFile
};

View File

@ -48,13 +48,20 @@ export const useChatLayoutContext = ({ appSplitterRef }: UseLayoutConfigProps) =
const onCollapseFileClick = useLayoutCollapse({ onSetSelectedFile });
const { selectedFileView, selectedFileViewSecondary, onSetFileView, closeSecondaryView } =
useLayoutConfig({
selectedFileId: selectedFile?.id,
selectedFileType: selectedFile?.type
});
const {
selectedFileView,
selectedFileViewRenderSecondary,
selectedFileViewSecondary,
onSetFileView,
closeSecondaryView
} = useLayoutConfig({
selectedFileId: selectedFile?.id,
selectedFileType: selectedFile?.type,
isVersionHistoryMode
});
return {
selectedFileViewRenderSecondary,
selectedFileView,
selectedFileViewSecondary,
onSetFileView,

View File

@ -3,7 +3,15 @@ export interface FileConfig {
fileViewConfig?: FileViewConfig;
}
export type FileViewConfig = Partial<Record<FileView, { secondaryView: FileViewSecondary }>>;
export type FileViewConfig = Partial<
Record<
FileView,
{
secondaryView: FileViewSecondary;
renderView?: boolean; //this is really just used for metric because it has a vertical view and we don't want to render a right panel. undefined defaults to true
}
>
>;
export type FileView =
| MetricFileView
@ -21,5 +29,7 @@ export type ValueFileView = 'file';
export type DatasetFileView = 'file';
export type CollectionFileView = 'file' | 'results';
export type ReasoningFileView = 'reasoning';
export type FileViewSecondary = null | MetricFileViewSecondary;
export type MetricFileViewSecondary = 'chart-edit' | 'sql-edit';
export type MetricFileViewSecondary = 'chart-edit' | 'sql-edit' | 'version-history';
export type DashboardFileViewSecondary = 'version-history';
export type FileViewSecondary = null | MetricFileViewSecondary | DashboardFileViewSecondary;

View File

@ -1,17 +1,19 @@
'use client';
import { FileType } from '@/api/asset_interfaces';
import { useMemo, useState } from 'react';
import { useLayoutEffect, useMemo, useState } from 'react';
import { FileConfig, FileView, FileViewConfig, FileViewSecondary } from './interfaces';
import { useMemoizedFn } from '@/hooks';
import { create } from 'mutative';
export const useLayoutConfig = ({
selectedFileId,
selectedFileType
selectedFileType,
isVersionHistoryMode
}: {
selectedFileId: string | undefined;
selectedFileType: FileType | undefined;
isVersionHistoryMode: boolean;
}) => {
const [fileViews, setFileViews] = useState<Record<string, FileConfig>>({});
@ -32,15 +34,25 @@ export const useLayoutConfig = ({
return selectedFileViewConfig?.[selectedFileView]?.secondaryView ?? null;
}, [selectedFileViewConfig, selectedFileId, selectedFileView]);
const selectedFileViewRenderSecondary: boolean = useMemo(() => {
if (!selectedFileId || !selectedFileViewConfig || !selectedFileView) return false;
if (selectedFileViewConfig?.[selectedFileView]?.secondaryView) {
return selectedFileViewConfig?.[selectedFileView]?.renderView !== false;
}
return false;
}, [selectedFileViewConfig]);
const onSetFileView = useMemoizedFn(
({
fileView,
fileId,
secondaryView
secondaryView,
renderView
}: {
fileView?: FileView;
fileId?: string;
secondaryView?: FileViewSecondary;
renderView?: boolean;
}) => {
const id = fileId ?? selectedFileId;
if (!id) return;
@ -60,7 +72,8 @@ export const useLayoutConfig = ({
...newFileConfig.fileViewConfig,
[usedFileView]: {
...newFileConfig.fileViewConfig?.[usedFileView],
secondaryView
secondaryView,
renderView
}
};
}
@ -80,9 +93,25 @@ export const useLayoutConfig = ({
});
});
useLayoutEffect(() => {
if (
isVersionHistoryMode &&
selectedFileId &&
(selectedFileType === 'metric' || selectedFileType === 'dashboard')
) {
const fileView = selectedFileType === 'metric' ? 'chart' : 'dashboard';
onSetFileView({
fileId: selectedFileId,
fileView,
secondaryView: 'version-history'
});
}
}, [isVersionHistoryMode, selectedFileId]);
return {
selectedFileView,
selectedFileViewSecondary,
selectedFileViewRenderSecondary,
onSetFileView,
closeSecondaryView
};

View File

@ -44,6 +44,13 @@ export const useSelectedFile = ({
return false;
}, [selectedFile?.type, metricVersionNumber, dashboardVersionNumber]);
console.log(
'isVersionHistoryMode',
isVersionHistoryMode,
metricVersionNumber,
dashboardVersionNumber
);
const [renderViewLayoutKey, setRenderViewLayoutKey] = useState<ChatLayoutView>(
selectedLayout || 'chat'
);
@ -96,7 +103,14 @@ export const useSelectedFile = ({
renderViewLayoutKey,
setRenderViewLayoutKey
}),
[onSetSelectedFile, selectedFile, selectedLayout, chatId, renderViewLayoutKey]
[
onSetSelectedFile,
isVersionHistoryMode,
selectedFile,
selectedLayout,
chatId,
renderViewLayoutKey
]
);
};

View File

@ -1,15 +1,81 @@
import React from 'react';
'use client';
import React, { useMemo, useRef } from 'react';
import { FileContainerHeader } from './FileContainerHeader';
import { AppPageLayout } from '@/components/ui/layouts';
import { AppPageLayout, AppSplitter, AppSplitterRef } from '@/components/ui/layouts';
import { useChatLayoutContextSelector } from '../ChatLayoutContext';
import { createAutoSaveId } from '@/components/ui/layouts/AppSplitter/helper';
import Cookies from 'js-cookie';
import { useMemoizedFn, useUpdateLayoutEffect } from '@/hooks';
interface FileContainerProps {
children: React.ReactNode;
}
const defaultOpenLayout: [string, string] = ['auto', '310px'];
const defaulClosedLayout: [string, string] = ['auto', '0px'];
const autoSaveId = 'file-container-splitter';
export const FileContainer: React.FC<FileContainerProps> = ({ children }) => {
const appSplitterRef = useRef<AppSplitterRef>(null);
const selectedFileViewSecondary = useChatLayoutContextSelector(
(x) => x.selectedFileViewSecondary
);
const selectedFileViewRenderSecondary = useChatLayoutContextSelector(
(x) => x.selectedFileViewRenderSecondary
);
const isOpenSecondary = selectedFileViewRenderSecondary;
const secondaryLayoutDimensions: [string, string] = useMemo(() => {
const cookieKey = createAutoSaveId(autoSaveId);
const cookieValue = Cookies.get(cookieKey);
if (cookieValue) {
try {
const parsedValue = JSON.parse(cookieValue) as string[];
if (!parsedValue?.some((item) => item === 'auto')) {
return parsedValue as [string, string];
}
} catch (error) {
//
}
}
return defaultOpenLayout as [string, string];
}, []);
const defaultLayout: [string, string] = useMemo(() => {
if (isOpenSecondary) {
return secondaryLayoutDimensions;
}
return defaulClosedLayout;
}, []);
const animateOpenSplitter = useMemoizedFn((side: 'open' | 'closed') => {
if (side === 'open') {
appSplitterRef.current?.animateWidth(defaultOpenLayout[1], 'right');
} else {
appSplitterRef.current?.animateWidth(defaulClosedLayout[1], 'right');
}
});
useUpdateLayoutEffect(() => {
animateOpenSplitter(isOpenSecondary ? 'open' : 'closed');
}, [isOpenSecondary]);
return (
<AppPageLayout className="flex h-full min-w-[380px] flex-col" header={<FileContainerHeader />}>
{children}
<AppSplitter
ref={appSplitterRef}
autoSaveId={autoSaveId}
defaultLayout={defaultLayout}
initialReady={false}
leftChildren={children}
rightChildren={<div>Right {selectedFileViewSecondary}</div>}
allowResize={selectedFileViewRenderSecondary}
preserveSide={'right'}
rightPanelMinSize={250}
rightPanelMaxSize={385}
/>
</AppPageLayout>
);
};

View File

@ -1,16 +1,17 @@
import { Button } from '@/components/ui/buttons';
import { ArrowLeft, History } from '@/components/ui/icons';
import React, { useMemo } from 'react';
import React, { useMemo, useTransition } from 'react';
import { useChatLayoutContextSelector } from '../../ChatLayoutContext';
import { useGetMetric } from '@/api/buster_rest/metrics';
import last from 'lodash/last';
import { useGetDashboard } from '@/api/buster_rest/dashboards';
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
import { useMemoizedFn } from '@/hooks';
import { timeout } from '@/lib';
export const FileContainerVersionHistory = React.memo(() => {
return (
<div className="flex w-full items-center justify-between gap-x-3">
<div className="flex w-full items-center justify-between gap-x-1.5">
<ExitVersionHistoryButton />
<DisplayVersionHistory />
</div>
@ -38,6 +39,7 @@ const DisplayVersionHistory = React.memo(() => {
);
const versionInfo = useMemo(() => {
if (!metric?.version_number && !dashboard?.version_number) return null;
if (type === 'metric') {
return {
isCurrent: metric?.version_number === metric?.latestVersion?.version_number,
@ -67,10 +69,16 @@ const DisplayVersionHistory = React.memo(() => {
DisplayVersionHistory.displayName = 'DisplayVersionHistory';
const ExitVersionHistoryButton = React.memo(() => {
const [isPending, startTransition] = useTransition();
const onChangeQueryParams = useAppLayoutContextSelector((x) => x.onChangeQueryParams);
const closeSecondaryView = useChatLayoutContextSelector((x) => x.closeSecondaryView);
const removeVersionHistoryQueryParams = useMemoizedFn(() => {
onChangeQueryParams({ metric_version_number: null, dashboard_version_number: null });
const removeVersionHistoryQueryParams = useMemoizedFn(async () => {
closeSecondaryView();
await timeout(250);
startTransition(() => {
onChangeQueryParams({ metric_version_number: null, dashboard_version_number: null });
});
});
return (

View File

@ -17,7 +17,8 @@ export const MetricContainerHeaderSegment: React.FC<FileContainerSegmentProps> =
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
const onChange = useMemoizedFn((fileView: SegmentedItem<FileView>) => {
onSetFileView({ fileView: fileView.value });
const renderView = fileView.value === 'results' ? false : true;
onSetFileView({ fileView: fileView.value, renderView });
});
return (

View File

@ -1,10 +1,10 @@
import { FileView } from '../../ChatLayoutContext/useLayoutConfig';
export interface FileContainerSegmentProps {
export type FileContainerSegmentProps = {
selectedFileView: FileView | undefined;
selectedFileId: string | undefined;
}
};
export interface FileContainerButtonsProps {
export type FileContainerButtonsProps = {
selectedFileView: FileView | undefined;
}
};

View File

@ -0,0 +1,31 @@
import type { FileType } from '@/api/asset_interfaces/chat';
import type { FileContainerSecondaryProps } from './interfaces';
import type {
DashboardFileViewSecondary,
MetricFileViewSecondary
} from '../../ChatLayoutContext/useLayoutConfig/interfaces';
const MetricSecondaryRecord: Record<
MetricFileViewSecondary,
React.FC<FileContainerSecondaryProps>
> = {
'chart-edit': () => null,
'sql-edit': () => null,
'version-history': () => null
};
const DashboardSecondaryRecord: Record<
DashboardFileViewSecondary,
React.FC<FileContainerSecondaryProps>
> = {
'version-history': () => null
};
const SelectedFileSecondaryRecord: Record<
FileType,
Record<string, React.FC<FileContainerSecondaryProps>>
> = {
metric: MetricSecondaryRecord,
dashboard: DashboardSecondaryRecord,
reasoning: {}
};

View File

@ -0,0 +1 @@
export * from './FileContainerSecondary';

View File

@ -0,0 +1,6 @@
import type { FileViewSecondary } from '@/layouts/ChatLayout/ChatLayoutContext/useLayoutConfig';
export type FileContainerSecondaryProps = {
selectedFileId: string | undefined;
selectedFileViewSecondary: FileViewSecondary | undefined;
};

View File

@ -0,0 +1,14 @@
import type { FileType } from '@/api/asset_interfaces/chat';
import type { FileContainerSecondaryProps } from '../interfaces';
import React from 'react';
import { MetricSecondaryRecord } from './metricPanels';
import { DashboardSecondaryRecord } from './dashboardPanels';
export const SelectedFileSecondaryRecord: Record<
FileType,
Record<string, React.FC<FileContainerSecondaryProps>>
> = {
metric: MetricSecondaryRecord,
dashboard: DashboardSecondaryRecord,
reasoning: {}
};

View File

@ -0,0 +1,21 @@
import { type DashboardFileViewSecondary } from '@/layouts/ChatLayout/ChatLayoutContext/useLayoutConfig';
import type { FileContainerSecondaryProps } from '../interfaces';
import dynamic from 'next/dynamic';
import { loading } from './loading';
const VersionHistoryPanel = dynamic(
() =>
import('@/components/features/versionHistory/VersionHistoryPanel').then(
(x) => x.VersionHistoryPanel
),
{ loading }
);
export const DashboardSecondaryRecord: Record<
DashboardFileViewSecondary,
React.FC<FileContainerSecondaryProps>
> = {
'version-history': ({ selectedFileId }) => (
<VersionHistoryPanel assetId={selectedFileId || ''} type="dashboard" />
)
};

View File

@ -0,0 +1 @@
export * from './allConfigs';

View File

@ -0,0 +1,9 @@
import { CircleSpinnerLoaderContainer } from '@/components/ui/loaders/CircleSpinnerLoaderContainer';
export const loading = () => {
return (
<div className="flex h-full w-full items-center justify-center">
<CircleSpinnerLoaderContainer />
</div>
);
};

View File

@ -0,0 +1,34 @@
import dynamic from 'next/dynamic';
import { loading } from './loading';
import type { MetricFileViewSecondary } from '@/layouts/ChatLayout/ChatLayoutContext/useLayoutConfig/interfaces';
import type { FileContainerSecondaryProps } from '../interfaces';
const MetricEditController = dynamic(
() =>
import('@/controllers/MetricController/MetricViewChart/MetricEditController').then(
(x) => x.MetricEditController
),
{ loading }
);
const VersionHistoryPanel = dynamic(
() =>
import('@/components/features/versionHistory/VersionHistoryPanel').then(
(x) => x.VersionHistoryPanel
),
{ loading }
);
const MetricViewResults = dynamic(
() => import('@/controllers/MetricController/MetricViewResults').then((x) => x.MetricViewResults),
{ loading }
);
export const MetricSecondaryRecord: Record<
MetricFileViewSecondary,
React.FC<FileContainerSecondaryProps>
> = {
'chart-edit': ({ selectedFileId }) => <MetricEditController metricId={selectedFileId || ''} />,
'sql-edit': ({ selectedFileId }) => <MetricViewResults metricId={selectedFileId || ''} />,
'version-history': ({ selectedFileId }) => (
<VersionHistoryPanel assetId={selectedFileId || ''} type="metric" />
)
};