From e2f5e64475f210289539e6c06d3aa3dcccdcd538 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Tue, 1 Apr 2025 12:34:26 -0600 Subject: [PATCH] update timeouts for switching layouts --- web/src/components/ui/dropdown/Dropdown.tsx | 10 +- .../ui/select/SelectMultiple.stories.tsx | 42 ++++-- .../components/ui/select/SelectMultiple.tsx | 5 +- .../ChatLayout/ChatLayoutContext/helpers.ts | 137 +++++++++++++++++- .../useLayoutConfig/useLayoutConfig.ts | 2 +- .../useCloseVersionHistory.ts | 3 +- .../MetricContainerHeaderButtons.tsx | 64 +++++--- 7 files changed, 225 insertions(+), 38 deletions(-) diff --git a/web/src/components/ui/dropdown/Dropdown.tsx b/web/src/components/ui/dropdown/Dropdown.tsx index 6b3274117..68df9cbe3 100644 --- a/web/src/components/ui/dropdown/Dropdown.tsx +++ b/web/src/components/ui/dropdown/Dropdown.tsx @@ -20,7 +20,7 @@ import { DropdownMenuLink } from './DropdownBase'; import { CircleSpinnerLoader } from '../loaders/CircleSpinnerLoader'; -import { useMemoizedFn, useMount } from '@/hooks'; +import { useMemoizedFn } from '@/hooks'; import { cn } from '@/lib/classMerge'; import { Input } from '../inputs/Input'; import { useDebounceSearch } from '@/hooks'; @@ -159,6 +159,8 @@ export const DropdownContent = ({ debounceTime: 50 }); + console.log(filteredItems, searchText); + const hasShownItem = useMemo(() => { return filteredItems.length > 0 && filteredItems.some((item) => (item as DropdownItem).value); }, [filteredItems]); @@ -264,13 +266,13 @@ export const DropdownContent = ({ return ( [number]} + key={dropdownItemKey(item, hotkeyIndex)} + item={item} index={hotkeyIndex} selectType={selectType} onSelect={onSelect} onSelectItem={onSelectItem} closeOnSelect={closeOnSelect} - key={dropdownItemKey(item, hotkeyIndex)} showIndex={showIndex} /> ); @@ -419,7 +421,7 @@ const DropdownItem = ({ } //I do not think this selected check is stable... look into refactoring - if (selectType === 'single' || selected) { + if (selectType === 'single') { return ( { + const [value, setValue] = useState([]); + + const items = useMemo( + () => + Array.from({ length: 100 }, (_, index) => ({ + value: index.toString(), + label: faker.company.name() + })), + [] + ); + + const handleSelect = (selectedValues: string[]) => { + setValue(selectedValues); + }; + + return ( +
+ +
+ ); +}; + export const WithHundredItems: Story = { args: { - items: Array.from({ length: 100 }, (_, index) => ({ - value: index.toString(), - label: `Option ${index + 1}` - })), + items: [], value: [], onChange: fn(), placeholder: 'Select multiple options...' }, - render: (args) => ( -
- -
- ) + render: () => }; diff --git a/web/src/components/ui/select/SelectMultiple.tsx b/web/src/components/ui/select/SelectMultiple.tsx index 69ccdc4de..7a9ac3a43 100644 --- a/web/src/components/ui/select/SelectMultiple.tsx +++ b/web/src/components/ui/select/SelectMultiple.tsx @@ -16,6 +16,7 @@ interface SelectMultipleProps extends VariantProps { placeholder?: string; value: string[]; disabled?: boolean; + useSearch?: boolean; } export const SelectMultiple: React.FC = React.memo( @@ -27,7 +28,8 @@ export const SelectMultiple: React.FC = React.memo( size = 'default', variant = 'default', value, - disabled + disabled, + useSearch = true }) => { const selectedRecord = useMemo(() => { return itemsProp.reduce>((acc, item) => { @@ -72,6 +74,7 @@ export const SelectMultiple: React.FC = React.memo( string> = { +const chatRouteRecord: Record string | null> = { collection: (chatId, assetId) => createBusterRoute({ route: BusterRoutes.APP_CHAT_ID_COLLECTION_ID, @@ -48,18 +53,57 @@ const chatRouteRecord: Record empty: () => '' }; +const assetRouteRecord: Record string | null> = { + collection: (assetId) => + createBusterRoute({ + route: BusterRoutes.APP_COLLECTIONS_ID, + collectionId: assetId + }), + dataset: (assetId) => + createBusterRoute({ + route: BusterRoutes.APP_DATASETS_ID, + datasetId: assetId + }), + metric: (assetId) => + createBusterRoute({ + route: BusterRoutes.APP_METRIC_ID, + metricId: assetId + }), + dashboard: (assetId) => + createBusterRoute({ + route: BusterRoutes.APP_DASHBOARD_ID, + dashboardId: assetId + }), + term: (assetId) => + createBusterRoute({ + route: BusterRoutes.APP_TERMS_ID, + termId: assetId + }), + value: (assetId) => + createBusterRoute({ + route: BusterRoutes.APP_VALUE_ID, + valueId: assetId + }), + reasoning: () => null, + empty: () => null +}; + export const createChatAssetRoute = ({ chatId, assetId, type }: { - chatId: string; + chatId: string | undefined; assetId: string; type: FileType; }) => { const routeBuilder = chatRouteRecord[type]; if (!routeBuilder) return null; - return routeBuilder(chatId, assetId); + if (chatId) return routeBuilder(chatId, assetId); + + const assetRouteBuilder = assetRouteRecord[type]; + if (!assetRouteBuilder) return null; + return assetRouteBuilder(assetId); }; const routeToFileView: Partial> = { @@ -86,3 +130,88 @@ export const DEFAULT_FILE_VIEW: Record = { // term: 'results', // dataset: 'results', }; + +export const assetParamsToRoute = ({ + chatId, + assetId, + type, + secondaryView: secondaryViewProp +}: { + chatId: string | undefined; + assetId: string; + type: FileType; + secondaryView?: FileViewSecondary; +}) => { + if (type === 'metric') { + const secondaryView = secondaryViewProp as MetricFileViewSecondary | undefined; + if (chatId) { + switch (secondaryView) { + case 'chart-edit': + return createBusterRoute({ + route: BusterRoutes.APP_CHAT_ID_METRIC_ID_CHART, + chatId, + metricId: assetId + }); + case 'sql-edit': + return createBusterRoute({ + route: BusterRoutes.APP_CHAT_ID_METRIC_ID_RESULTS, + chatId, + metricId: assetId + }); + case 'version-history': + return createBusterRoute({ + route: BusterRoutes.APP_CHAT_ID_METRIC_ID_CHART, + chatId, + metricId: assetId + }); + default: + const test: never | undefined = secondaryView; + return ''; + } + } + + switch (secondaryView) { + case 'chart-edit': + return createBusterRoute({ + route: BusterRoutes.APP_METRIC_ID_CHART, + metricId: assetId + }); + case 'sql-edit': + return createBusterRoute({ + route: BusterRoutes.APP_METRIC_ID_RESULTS, + metricId: assetId + }); + case 'version-history': + return createBusterRoute({ + route: BusterRoutes.APP_METRIC_ID_CHART, + metricId: assetId + }); + default: + const test: never | undefined = secondaryView; + return ''; + } + } + + if (type === 'dashboard') { + const secondaryView = secondaryViewProp as DashboardFileViewSecondary | undefined; + if (chatId) { + switch (secondaryView) { + case 'version-history': + return createBusterRoute({ + route: BusterRoutes.APP_CHAT_ID_DASHBOARD_ID, + chatId, + dashboardId: assetId + }); + } + } + + return createBusterRoute({ + route: BusterRoutes.APP_DASHBOARD_ID, + dashboardId: assetId + }); + } + + console.warn('Asset params to route has not been implemented for this file type', type); + + return createChatAssetRoute({ chatId, assetId, type }) || ''; +}; diff --git a/web/src/layouts/ChatLayout/ChatLayoutContext/useLayoutConfig/useLayoutConfig.ts b/web/src/layouts/ChatLayout/ChatLayoutContext/useLayoutConfig/useLayoutConfig.ts index 05535f7d0..2204caf69 100644 --- a/web/src/layouts/ChatLayout/ChatLayoutContext/useLayoutConfig/useLayoutConfig.ts +++ b/web/src/layouts/ChatLayout/ChatLayoutContext/useLayoutConfig/useLayoutConfig.ts @@ -88,7 +88,7 @@ export const useLayoutConfig = ({ if (secondaryView) { animateOpenSplitter('right'); - await timeout(250); //wait for splitter to close before opening secondary view + await timeout(chatId ? 250 : 0); //wait for splitter to close before opening secondary view } else if (chatId) { animateOpenSplitter('both'); } diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory/useCloseVersionHistory.ts b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory/useCloseVersionHistory.ts index 83e07af5c..7a14dad00 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory/useCloseVersionHistory.ts +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/FileContainerHeaderVersionHistory/useCloseVersionHistory.ts @@ -6,12 +6,13 @@ import { useTransition } from 'react'; export const useCloseVersionHistory = () => { const [isPending, startTransition] = useTransition(); + const chatId = useChatLayoutContextSelector((x) => x.chatId); const onChangeQueryParams = useAppLayoutContextSelector((x) => x.onChangeQueryParams); const closeSecondaryView = useChatLayoutContextSelector((x) => x.closeSecondaryView); const removeVersionHistoryQueryParams = useMemoizedFn(async () => { closeSecondaryView(); - await timeout(250); + await timeout(chatId ? 250 : 0); startTransition(() => { onChangeQueryParams({ metric_version_number: null, dashboard_version_number: null }); }); diff --git a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx index 821e6390e..91fc7ea00 100644 --- a/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx +++ b/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/MetricContainerHeaderButtons/MetricContainerHeaderButtons.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { FileContainerButtonsProps } from '../interfaces'; import { MetricFileViewSecondary, useChatLayoutContextSelector } from '../../../ChatLayoutContext'; import { useMemoizedFn } from '@/hooks'; @@ -14,6 +14,12 @@ import { SquareChartPen, SquareCode } from '@/components/ui/icons'; import { useGetMetric } from '@/api/buster_rest/metrics'; import { ThreeDotMenuButton } from './MetricThreeDotMenu'; import { canEdit, getIsEffectiveOwner } from '@/lib/share'; +import Link from 'next/link'; +import { BusterRoutes, createBusterRoute } from '@/routes'; +import { + assetParamsToRoute, + createChatAssetRoute +} from '@/layouts/ChatLayout/ChatLayoutContext/helpers'; export const MetricContainerHeaderButtons: React.FC = React.memo(() => { const selectedLayout = useChatLayoutContextSelector((x) => x.selectedLayout); @@ -32,8 +38,8 @@ export const MetricContainerHeaderButtons: React.FC = return ( - {isEditor && } - {isEffectiveOwner && } + {isEditor && } + {isEffectiveOwner && } {isEffectiveOwner && } @@ -47,50 +53,74 @@ export const MetricContainerHeaderButtons: React.FC = MetricContainerHeaderButtons.displayName = 'MetricContainerHeaderButtons'; -const EditChartButton = React.memo(() => { +const EditChartButton = React.memo(({ metricId }: { metricId: string }) => { const selectedFileViewSecondary = useChatLayoutContextSelector( (x) => x.selectedFileViewSecondary ); + const chatId = useChatIndividualContextSelector((x) => x.chatId); const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView); const editableSecondaryView: MetricFileViewSecondary = 'chart-edit'; const isSelectedView = selectedFileViewSecondary === editableSecondaryView; + const href = useMemo(() => { + return assetParamsToRoute({ + chatId, + assetId: metricId, + type: 'metric', + secondaryView: 'chart-edit' + }); + }, [chatId, metricId]); + const onClickButton = useMemoizedFn(() => { const secondaryView = isSelectedView ? null : editableSecondaryView; onSetFileView({ secondaryView, fileView: 'chart' }); }); return ( - } - onClick={onClickButton} - selected={isSelectedView} - /> + + } + onClick={onClickButton} + selected={isSelectedView} + /> + ); }); EditChartButton.displayName = 'EditChartButton'; -const EditSQLButton = React.memo(() => { +const EditSQLButton = React.memo(({ metricId }: { metricId: string }) => { const selectedFileViewSecondary = useChatLayoutContextSelector( (x) => x.selectedFileViewSecondary ); const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView); + const chatId = useChatIndividualContextSelector((x) => x.chatId); const editableSecondaryView: MetricFileViewSecondary = 'sql-edit'; const isSelectedView = selectedFileViewSecondary === editableSecondaryView; + const href = useMemo(() => { + return assetParamsToRoute({ + chatId, + assetId: metricId, + type: 'metric', + secondaryView: 'sql-edit' + }); + }, [chatId, metricId]); + const onClickButton = useMemoizedFn(() => { const secondaryView = isSelectedView ? null : editableSecondaryView; onSetFileView({ secondaryView, fileView: 'results' }); }); return ( - } - onClick={onClickButton} - selected={isSelectedView} - /> + + } + onClick={onClickButton} + selected={isSelectedView} + /> + ); }); EditSQLButton.displayName = 'EditSQLButton';