mirror of https://github.com/buster-so/buster.git
Merge remote-tracking branch 'origin/evals' into evals
This commit is contained in:
commit
5ffaf3fc74
|
@ -0,0 +1 @@
|
||||||
|
export const MAX_NUMBER_OF_ITEMS_ON_DASHBOARD = 30;
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './dashboardConfigInterfaces';
|
export * from './dashboardConfigInterfaces';
|
||||||
export * from './interfaces';
|
export * from './interfaces';
|
||||||
|
export * from './config';
|
||||||
|
|
|
@ -10,7 +10,11 @@ import {
|
||||||
unshareDashboard
|
unshareDashboard
|
||||||
} from './requests';
|
} from './requests';
|
||||||
import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
|
import { dashboardQueryKeys } from '@/api/query_keys/dashboard';
|
||||||
import { BusterDashboard, BusterDashboardResponse } from '@/api/asset_interfaces/dashboard';
|
import {
|
||||||
|
BusterDashboard,
|
||||||
|
BusterDashboardResponse,
|
||||||
|
MAX_NUMBER_OF_ITEMS_ON_DASHBOARD
|
||||||
|
} from '@/api/asset_interfaces/dashboard';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useMemoizedFn } from '@/hooks';
|
import { useMemoizedFn } from '@/hooks';
|
||||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||||
|
@ -407,10 +411,23 @@ export const useAddAndRemoveMetricsFromDashboard = () => {
|
||||||
const { openErrorMessage } = useBusterNotifications();
|
const { openErrorMessage } = useBusterNotifications();
|
||||||
const ensureDashboardConfig = useEnsureDashboardConfig();
|
const ensureDashboardConfig = useEnsureDashboardConfig();
|
||||||
|
|
||||||
const addMetricToDashboard = useMemoizedFn(
|
const addAndRemoveMetrics = useMemoizedFn(
|
||||||
async ({ metricIds, dashboardId }: { metricIds: string[]; dashboardId: string }) => {
|
async ({ metricIds, dashboardId }: { metricIds: string[]; dashboardId: string }) => {
|
||||||
const dashboardResponse = await ensureDashboardConfig(dashboardId);
|
const dashboardResponse = await ensureDashboardConfig(dashboardId);
|
||||||
|
|
||||||
|
const numberOfItemsOnDashboard: number =
|
||||||
|
dashboardResponse?.dashboard.config.rows?.reduce(
|
||||||
|
(acc, row) => acc + (row.items?.length || 0),
|
||||||
|
0
|
||||||
|
) || 0;
|
||||||
|
|
||||||
|
if (numberOfItemsOnDashboard > MAX_NUMBER_OF_ITEMS_ON_DASHBOARD) {
|
||||||
|
openErrorMessage(
|
||||||
|
`Dashboard is full, please remove some metrics before adding more. You can only have ${MAX_NUMBER_OF_ITEMS_ON_DASHBOARD} metrics on a dashboard`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (dashboardResponse) {
|
if (dashboardResponse) {
|
||||||
const newConfig = addAndRemoveMetricsToDashboard(
|
const newConfig = addAndRemoveMetricsToDashboard(
|
||||||
metricIds,
|
metricIds,
|
||||||
|
@ -427,7 +444,7 @@ export const useAddAndRemoveMetricsFromDashboard = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: addMetricToDashboard,
|
mutationFn: addAndRemoveMetrics,
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import { useQuery, keepPreviousData } from '@tanstack/react-query';
|
import { useQuery, keepPreviousData, UseQueryOptions } from '@tanstack/react-query';
|
||||||
import { searchQueryKeys } from '@/api/query_keys/search';
|
import { searchQueryKeys } from '@/api/query_keys/search';
|
||||||
import { search } from './requests';
|
import { search } from './requests';
|
||||||
|
|
||||||
export const useSearch = (params: Parameters<typeof search>[0]) => {
|
export const useSearch = (
|
||||||
|
params: Parameters<typeof search>[0],
|
||||||
|
options?: Omit<UseQueryOptions<Awaited<ReturnType<typeof search>>>, 'queryKey' | 'queryFn'>
|
||||||
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
...searchQueryKeys.getSearchResult(params),
|
...searchQueryKeys.getSearchResult(params),
|
||||||
queryFn: () => search(params),
|
queryFn: () => search(params),
|
||||||
placeholderData: keepPreviousData
|
placeholderData: keepPreviousData,
|
||||||
|
...options
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { search } from '../buster_rest/search';
|
||||||
export const getSearchResult = (params: Parameters<typeof search>[0]) =>
|
export const getSearchResult = (params: Parameters<typeof search>[0]) =>
|
||||||
queryOptions<BusterSearchResult[]>({
|
queryOptions<BusterSearchResult[]>({
|
||||||
queryKey: ['search', 'results', params] as const,
|
queryKey: ['search', 'results', params] as const,
|
||||||
staleTime: 1000 * 10 // 10 seconds,
|
staleTime: 1000 * 30 // 30 seconds,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const searchQueryKeys = {
|
export const searchQueryKeys = {
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const ResetPasswordForm: React.FC<{
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [password2, setPassword2] = useState('');
|
const [password2, setPassword2] = useState('');
|
||||||
const [goodPassword, setGoodPassword] = useState(false);
|
const [goodPassword, setGoodPassword] = useState(false);
|
||||||
const { openErrorNotification, openSuccessMessage } = useBusterNotifications();
|
const { openErrorMessage, openSuccessMessage } = useBusterNotifications();
|
||||||
const [countdown, setCountdown] = useState(5);
|
const [countdown, setCountdown] = useState(5);
|
||||||
|
|
||||||
const disabled = !goodPassword || loading || !password || !password2 || password !== password2;
|
const disabled = !goodPassword || loading || !password || !password2 || password !== password2;
|
||||||
|
@ -60,7 +60,7 @@ export const ResetPasswordForm: React.FC<{
|
||||||
startCountdown();
|
startCountdown();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
openErrorNotification(error);
|
openErrorMessage(error as string);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,15 @@ export const AddToDashboardModal: React.FC<{
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [selectedMetrics, setSelectedMetrics] = useState<string[]>([]);
|
const [selectedMetrics, setSelectedMetrics] = useState<string[]>([]);
|
||||||
const debouncedSearchTerm = useDebounce(searchTerm, { wait: 150 });
|
const debouncedSearchTerm = useDebounce(searchTerm, { wait: 150 });
|
||||||
const { data: searchResults } = useSearch({
|
|
||||||
query: debouncedSearchTerm,
|
const { data: searchResults } = useSearch(
|
||||||
asset_types: ['metric'],
|
{
|
||||||
num_results: 100
|
query: debouncedSearchTerm,
|
||||||
});
|
asset_types: ['metric'],
|
||||||
|
num_results: 100
|
||||||
|
},
|
||||||
|
{ enabled: open }
|
||||||
|
);
|
||||||
|
|
||||||
const columns = useMemo<InputSelectModalProps['columns']>(
|
const columns = useMemo<InputSelectModalProps['columns']>(
|
||||||
() => [
|
() => [
|
||||||
|
@ -81,6 +85,63 @@ export const AddToDashboardModal: React.FC<{
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [isFetchedDashboard, rows]);
|
}, [isFetchedDashboard, rows]);
|
||||||
|
|
||||||
|
const addedMetricCount = useMemo(() => {
|
||||||
|
return selectedMetrics.filter((id) => !Object.keys(dashboard?.metrics || {}).includes(id))
|
||||||
|
.length;
|
||||||
|
}, [dashboard?.metrics, selectedMetrics]);
|
||||||
|
|
||||||
|
const removedMetricCount = useMemo(() => {
|
||||||
|
return Object.keys(dashboard?.metrics || {}).filter((id) => !selectedMetrics.includes(id))
|
||||||
|
.length;
|
||||||
|
}, [dashboard?.metrics, selectedMetrics]);
|
||||||
|
|
||||||
|
const primaryButtonText = useMemo(() => {
|
||||||
|
if (!isFetchedDashboard) {
|
||||||
|
return 'Loading metrics...';
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasRemovedItems = removedMetricCount > 0;
|
||||||
|
const hasAddedItems = addedMetricCount > 0;
|
||||||
|
|
||||||
|
if (hasRemovedItems && hasAddedItems) {
|
||||||
|
return `Update dashboard`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasRemovedItems) {
|
||||||
|
return `Remove metrics`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAddedItems) {
|
||||||
|
return `Add metrics`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Update dashboard`;
|
||||||
|
}, [isFetchedDashboard, removedMetricCount, addedMetricCount]);
|
||||||
|
|
||||||
|
const primaryButtonTooltipText = useMemo(() => {
|
||||||
|
if (!isFetchedDashboard) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasRemovedItems = removedMetricCount > 0;
|
||||||
|
const hasAddedItems = addedMetricCount > 0;
|
||||||
|
const returnText: string[] = [];
|
||||||
|
|
||||||
|
if (!hasRemovedItems && !hasAddedItems) {
|
||||||
|
return 'No changes to update';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasRemovedItems) {
|
||||||
|
returnText.push(`Removing ${removedMetricCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAddedItems) {
|
||||||
|
returnText.push(`Adding ${addedMetricCount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnText.join(', ');
|
||||||
|
}, [isFetchedDashboard, addedMetricCount, removedMetricCount]);
|
||||||
|
|
||||||
const footer: NonNullable<InputSelectModalProps['footer']> = useMemo(() => {
|
const footer: NonNullable<InputSelectModalProps['footer']> = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
left:
|
left:
|
||||||
|
@ -94,15 +155,19 @@ export const AddToDashboardModal: React.FC<{
|
||||||
onClick: onClose
|
onClick: onClose
|
||||||
},
|
},
|
||||||
primaryButton: {
|
primaryButton: {
|
||||||
text: `Update metrics`,
|
text: primaryButtonText,
|
||||||
onClick: handleAddAndRemoveMetrics,
|
onClick: handleAddAndRemoveMetrics,
|
||||||
disabled: !isSelectedChanged,
|
disabled: !isSelectedChanged,
|
||||||
tooltip: isSelectedChanged
|
tooltip: primaryButtonTooltipText
|
||||||
? `Adding ${selectedMetrics.length} metrics`
|
|
||||||
: 'No changes to update'
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [selectedMetrics.length, isSelectedChanged, handleAddAndRemoveMetrics]);
|
}, [
|
||||||
|
selectedMetrics.length,
|
||||||
|
primaryButtonTooltipText,
|
||||||
|
primaryButtonText,
|
||||||
|
isSelectedChanged,
|
||||||
|
handleAddAndRemoveMetrics
|
||||||
|
]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (isFetchedDashboard) {
|
if (isFetchedDashboard) {
|
||||||
|
@ -113,7 +178,7 @@ export const AddToDashboardModal: React.FC<{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InputSelectModal
|
<InputSelectModal
|
||||||
width={665}
|
width={675}
|
||||||
open={open}
|
open={open}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|
Loading…
Reference in New Issue