better keyboard shortcutrs

This commit is contained in:
Nate Kelley 2025-08-30 19:54:29 -06:00
parent d31859b316
commit b029c909d7
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
13 changed files with 70 additions and 56 deletions

View File

@ -122,22 +122,29 @@ const useCheckIfWeHaveAFollowupDashboard = (messageId: string) => {
for (const file of allFiles) {
const fileType = (file as ChatMessageResponseMessage_File).file_type;
if (fileType === 'dashboard') {
const queryKey = dashboardQueryKeys
.dashboardGetDashboard(file.id, file.version_number)
.queryKey.slice(0, 3);
queryClient.invalidateQueries({
...dashboardQueryKeys.dashboardGetDashboard(file.id, file.version_number),
});
queryClient.invalidateQueries({
...dashboardQueryKeys.dashboardGetDashboard(file.id, 'LATEST'),
exact: false,
queryKey,
});
} else if (fileType === 'metric') {
const queryKey = metricsQueryKeys
.metricsGetMetric(file.id, file.version_number)
.queryKey.slice(0, 3);
queryClient.invalidateQueries({
...metricsQueryKeys.metricsGetMetric(file.id, file.version_number),
});
queryClient.invalidateQueries({
...metricsQueryKeys.metricsGetMetric(file.id, 'LATEST'),
exact: false,
queryKey,
});
} else if (fileType === 'report') {
const { queryKey } = reportsQueryKeys.reportsGetReport(file.id, file.version_number);
queryClient.invalidateQueries({ queryKey });
const queryKey = reportsQueryKeys
.reportsGetReport(file.id, file.version_number)
.queryKey.slice(0, 3);
queryClient.invalidateQueries({
exact: false,
queryKey,
});
} else {
const _exhaustiveCheck: 'reasoning' = fileType;
}

View File

@ -28,8 +28,7 @@ export const useEnsureDashboardConfig = (params?: { prefetchData?: boolean }) =>
});
if (res) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(res.dashboard.id, res.dashboard.version_number)
.queryKey,
dashboardQueryKeys.dashboardGetDashboard(res.dashboard.id, 'LATEST').queryKey,
res
);
dashboardResponse = res;
@ -112,6 +111,10 @@ export const getDashboardAndInitializeMetrics = async ({
if (isLatestVersion) {
setOriginalDashboardFn?.(data.dashboard);
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, 'LATEST').queryKey,
data
);
}
if (data.dashboard.version_number) {

View File

@ -104,8 +104,7 @@ export const useAddAndRemoveMetricsFromDashboard = () => {
onSuccess: (data) => {
if (data) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, data.dashboard.version_number)
.queryKey,
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, 'LATEST').queryKey,
data
);
setOriginalDashboard(data.dashboard);
@ -175,8 +174,7 @@ export const useAddMetricsToDashboard = () => {
onSuccess: (data) => {
if (data) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, data.dashboard.version_number)
.queryKey,
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, 'LATEST').queryKey,
data
);
for (const metric of Object.values(data.metrics)) {
@ -207,7 +205,6 @@ export const useRemoveMetricsFromDashboard = () => {
const { openConfirmModal, openErrorMessage } = useBusterNotifications();
const queryClient = useQueryClient();
const ensureDashboardConfig = useEnsureDashboardConfig({ prefetchData: false });
const getLatestMetricVersion = useGetLatestMetricVersionMemoized();
const removeMetricFromDashboard = async ({
metricIds,
@ -234,7 +231,7 @@ export const useRemoveMetricsFromDashboard = () => {
if (dashboardResponse) {
const versionedOptions = dashboardQueryKeys.dashboardGetDashboard(
dashboardResponse.dashboard.id,
dashboardResponse.dashboard.version_number
'LATEST'
);
const newConfig = removeMetricFromDashboardConfig(
@ -252,8 +249,7 @@ export const useRemoveMetricsFromDashboard = () => {
const data = await dashboardsUpdateDashboard({ id: dashboardId, config: newConfig });
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, data.dashboard.version_number)
.queryKey,
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, 'LATEST').queryKey,
data
);
@ -281,8 +277,9 @@ export const useRemoveMetricsFromDashboard = () => {
mutationFn: removeMetricFromDashboard,
onSuccess: (_, variables) => {
variables.metricIds.forEach((id) => {
const queryKey = metricsQueryKeys.metricsGetMetric(id, 'LATEST').queryKey.slice(0, 3);
queryClient.invalidateQueries({
queryKey: metricsQueryKeys.metricsGetMetric(id, 'LATEST').queryKey.slice(0, 3),
queryKey,
refetchType: 'all',
exact: false,
});

View File

@ -16,13 +16,13 @@ export const useSaveDashboard = (params?: { updateOnSave?: boolean }) => {
onSuccess: (data, variables) => {
if (updateOnSave && data) {
queryClient.setQueryData(
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, data.dashboard.version_number)
.queryKey,
dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, 'LATEST').queryKey,
data
);
setOriginalDashboard(data.dashboard);
if (variables.restore_to_version) {
console.warn('TODO check if this is correct');
queryClient.invalidateQueries({
queryKey: dashboardQueryKeys.dashboardGetDashboard(data.dashboard.id, 'LATEST')
.queryKey,

View File

@ -9,12 +9,11 @@ import { shareDashboard, unshareDashboard, updateDashboardShare } from '../reque
*/
export const useShareDashboard = () => {
const queryClient = useQueryClient();
const getLatestDashboardVersion = useGetLatestDashboardVersionMemoized();
return useMutation({
mutationFn: shareDashboard,
onMutate: ({ id, params }) => {
const latestVersionNumber = getLatestDashboardVersion(id) ?? 'LATEST';
const queryKey = dashboardQueryKeys.dashboardGetDashboard(id, latestVersionNumber).queryKey;
const queryKey = dashboardQueryKeys.dashboardGetDashboard(id, 'LATEST').queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
if (!previousData) return previousData;
@ -47,15 +46,10 @@ export const useShareDashboard = () => {
*/
export const useUnshareDashboard = () => {
const queryClient = useQueryClient();
const getLatestDashboardVersion = useGetLatestDashboardVersionMemoized();
return useMutation({
mutationFn: unshareDashboard,
onMutate: (variables) => {
const latestVersionNumber = getLatestDashboardVersion(variables.id) ?? 'LATEST';
const queryKey = dashboardQueryKeys.dashboardGetDashboard(
variables.id,
latestVersionNumber
).queryKey;
const queryKey = dashboardQueryKeys.dashboardGetDashboard(variables.id, 'LATEST').queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
if (!previousData) return previousData;
return create(previousData, (draft) => {
@ -73,12 +67,11 @@ export const useUnshareDashboard = () => {
*/
export const useUpdateDashboardShare = () => {
const queryClient = useQueryClient();
const getLatestDashboardVersion = useGetLatestDashboardVersionMemoized();
return useMutation({
mutationFn: updateDashboardShare,
onMutate: ({ id, params }) => {
const latestVersionNumber = getLatestDashboardVersion(id) ?? 'LATEST';
const queryKey = dashboardQueryKeys.dashboardGetDashboard(id, latestVersionNumber).queryKey;
const queryKey = dashboardQueryKeys.dashboardGetDashboard(id, 'LATEST').queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
if (!previousData) return previousData;
return create(previousData, (draft) => {

View File

@ -3,6 +3,7 @@ import { create } from 'mutative';
import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
import { getOriginalDashboard } from '@/context/Dashboards/useOriginalDashboardStore';
import { useGetLatestDashboardVersionMemoized } from '../dashboardVersionNumber';
import type { dashboardsUpdateDashboard } from '../requests';
import { useSaveDashboard } from './useSaveDashboard';
/**
@ -19,9 +20,7 @@ export const useUpdateDashboard = (params?: {
const { mutateAsync: saveDashboard } = useSaveDashboard({ updateOnSave });
const getLatestDashboardVersion = useGetLatestDashboardVersionMemoized();
const mutationFn = async (
variables: Parameters<typeof import('../requests').dashboardsUpdateDashboard>[0]
) => {
const mutationFn = async (variables: Parameters<typeof dashboardsUpdateDashboard>[0]) => {
if (saveToServer) {
return await saveDashboard({
...variables,
@ -33,16 +32,12 @@ export const useUpdateDashboard = (params?: {
return useMutation({
mutationFn,
onMutate: (variables) => {
const latestVersionNumber = getLatestDashboardVersion(variables.id) ?? 'LATEST';
const originalDashboard = getOriginalDashboard(variables.id);
if (!originalDashboard) return;
const updatedDashboard = create(originalDashboard, (draft) => {
Object.assign(draft, variables);
});
const queryKey = dashboardQueryKeys.dashboardGetDashboard(
variables.id,
latestVersionNumber
).queryKey;
const queryKey = dashboardQueryKeys.dashboardGetDashboard(variables.id, 'LATEST').queryKey;
queryClient.setQueryData(queryKey, (previousData) => {
if (!previousData) return previousData;

View File

@ -22,8 +22,7 @@ export const useUpdateDashboardConfig = () => {
}: Partial<BusterDashboard['config']> & {
dashboardId: string;
}) => {
const latestVersionNumber = getLatestDashboardVersion(dashboardId) ?? 'LATEST';
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, latestVersionNumber);
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, 'LATEST');
const previousDashboard = queryClient.getQueryData(options.queryKey);
const previousConfig = previousDashboard?.dashboard?.config;
if (previousConfig) {

View File

@ -149,15 +149,14 @@ export const prefetchGetDashboard = async (
queryClient: QueryClient
) => {
const chosenVersionNumber = version_number || 'LATEST';
const queryFn = async () => {
return getDashboardAndInitializeMetrics({
const queryFn = async () =>
getDashboardAndInitializeMetrics({
id,
version_number: chosenVersionNumber,
queryClient,
prefetchMetricsData: false,
shouldInitializeMetrics: true,
});
};
const queryKey = dashboardQueryKeys.dashboardGetDashboard(id, chosenVersionNumber)?.queryKey;
const existingData = queryClient.getQueryData(queryKey);

View File

@ -363,14 +363,15 @@ const DropdownItem = <
index: number;
showIndex: boolean;
}) => {
const onClickItem = useMemoizedFn((e: React.MouseEvent<HTMLDivElement>) => {
const onClickItem = useMemoizedFn((e: React.MouseEvent<HTMLDivElement> | KeyboardEvent) => {
if (disabled) return;
if (onClick) onClick(e);
if (onSelect) onSelect(value as T);
});
const enabledHotKeys = showIndex && !disabled && !!onSelectItem;
// Add hotkey support when showIndex is true
useHotkeys(showIndex ? `${index}` : '', (_e) => onSelectItem(index), {
useHotkeys(`${index}`, onClickItem, {
enabled: enabledHotKeys,
});
@ -391,7 +392,7 @@ const DropdownItem = <
{shortcut && <DropdownMenuShortcut>{shortcut}</DropdownMenuShortcut>}
{link && (
<DropdownMenuLink<TRouter, TOptions, TFrom>
className="-mr-1 ml-auto opacity-0 group-hover:opacity-50 hover:opacity-100"
className="ml-auto opacity-0 group-hover:opacity-50 hover:opacity-100"
link={isSelectable ? link : null}
linkIcon={linkIcon}
/>

View File

@ -308,7 +308,7 @@ const DropdownMenuLink = <
return (
<div
className={cn('swag', className)}
className={className}
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
>

View File

@ -1,3 +1,4 @@
import { useEffect, useRef } from 'react';
import { cn } from '@/lib/classMerge';
import { Magnifier } from '../icons';
import { Input } from '../inputs';
@ -22,19 +23,38 @@ export const DropdownMenuHeaderSearch = <T,>({
placeholder,
className = '',
}: DropdownMenuHeaderSearchProps<T>) => {
const inputRef = useRef<HTMLInputElement>(null);
const { onChange: onChangePreflight, onKeyDown: onKeyDownPreflight } = useRadixDropdownSearch({
showIndex,
onSelectItem,
onChange,
});
useEffect(() => {
// Use requestAnimationFrame to ensure DOM is ready after animations
const focusInput = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
// Try immediate focus first
focusInput();
// If that doesn't work, try after the next frame
requestAnimationFrame(() => {
focusInput();
});
}, []);
return (
<div className={cn('flex items-center gap-x-0', className)}>
<span className="text-icon-color ml-2 flex">
<Magnifier />
</span>
<Input
autoFocus
ref={inputRef}
autoFocus={true}
variant={'ghost'}
className="pl-1.5!"
size={'small'}

View File

@ -14,7 +14,7 @@ export interface IDropdownItem<
secondaryLabel?: string;
value: T;
shortcut?: string;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
onClick?: (e: React.MouseEvent<HTMLDivElement> | KeyboardEvent) => void;
closeOnSelect?: boolean; //default is true
icon?: React.ReactNode;
disabled?: boolean;

View File

@ -11,7 +11,7 @@ import { canEdit } from '@/lib/share';
import { useGetOriginalDashboard } from './useOriginalDashboardStore';
export const useIsDashboardChanged = ({
dashboardId,
dashboardId = '',
enabled = true,
}: {
dashboardId: string | undefined;
@ -40,7 +40,7 @@ export const useIsDashboardChanged = ({
}, [currentDashboard]);
const onResetToOriginal = useMemoizedFn(() => {
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId || '', 'LATEST');
const options = dashboardQueryKeys.dashboardGetDashboard(dashboardId, 'LATEST');
const currentDashboard = queryClient.getQueryData<BusterDashboardResponse>(options.queryKey);
if (originalDashboard && currentDashboard) {
const resetDashboard = create(currentDashboard, (draft) => {