mirror of https://github.com/buster-so/buster.git
Add 3 dot menu for report
This commit is contained in:
parent
ac818aff47
commit
81bddd5216
|
@ -7,10 +7,10 @@ import { useContext, useState } from 'react';
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
import { Hand } from '../icons';
|
||||
import { SortableItemContext } from './_BusterSortableItemDragContainer';
|
||||
import { BusterResizeableGrid } from './BusterResizeableGrid';
|
||||
import { MIN_ROW_HEIGHT } from './helpers';
|
||||
import type { BusterResizeableGridRow } from './interfaces';
|
||||
import { SortableItemContext } from './SortableItemContext';
|
||||
|
||||
const meta: Meta<typeof BusterResizeableGrid> = {
|
||||
title: 'UI/Grid/BusterResizeableGrid',
|
||||
|
|
|
@ -199,7 +199,7 @@ function Gutter({ children, className, ...props }: React.ComponentProps<'div'>)
|
|||
{...props}
|
||||
className={cn(
|
||||
'slate-gutterLeft',
|
||||
'absolute top-0 z-50 flex h-full -translate-x-full cursor-text hover:opacity-100 sm:opacity-0',
|
||||
'absolute top-0 z-50 flex -translate-x-full cursor-text hover:opacity-100 sm:opacity-0',
|
||||
getPluginByType(editor, element.type)?.node.isContainer
|
||||
? 'group-hover/container:opacity-100'
|
||||
: 'group-hover:opacity-100',
|
||||
|
|
|
@ -86,8 +86,8 @@ const MetricResizeContainer: React.FC<PropsWithChildren> = ({ children }) => {
|
|||
ref={ref}
|
||||
contentEditable={false}
|
||||
className={cn(
|
||||
'group relative m-0 my-1.5 w-full cursor-default',
|
||||
isSelected && 'bg-item-hover/50 ring-ring rounded ring-2 ring-offset-4'
|
||||
'group relative m-0 my-1.5 w-full cursor-default transition-all',
|
||||
isSelected && 'bg-item-hover/10 ring-ring rounded ring-2 ring-offset-4'
|
||||
)}>
|
||||
<Resizable
|
||||
align={align}
|
||||
|
|
|
@ -51,10 +51,7 @@ export const MetricEmbedPlaceholder: React.FC = () => {
|
|||
<div
|
||||
onClick={onOpenAddMetricModal}
|
||||
className={cn(
|
||||
'bg-muted hover:bg-primary/10 flex cursor-pointer items-center rounded-sm p-3 pr-9 select-none',
|
||||
{
|
||||
'shadow-md': focused && selected
|
||||
}
|
||||
'bg-muted hover:bg-primary/10 flex cursor-pointer items-center rounded-sm p-3 pr-9 select-none'
|
||||
)}
|
||||
contentEditable={false}>
|
||||
<div className="text-muted-foreground/80 relative mr-3 flex [&_svg]:size-6">
|
||||
|
@ -134,3 +131,5 @@ const MemoizedAddMetricModal = React.memo(
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
MemoizedAddMetricModal.displayName = 'MemoizedAddMetricModal';
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import type { DropdownItem, DropdownItems } from '@/components/ui/dropdown';
|
||||
import { Trash } from '@/components/ui/icons';
|
||||
import { Code, SquareChartPen, Table, Trash } from '@/components/ui/icons';
|
||||
import { assetParamsToRoute } from '@/lib/assets/assetParamsToRoute';
|
||||
import { ASSET_ICONS } from '@/components/features/config/assetIcons';
|
||||
import { ArrowUpRight } from '@/components/ui/icons';
|
||||
import { useEditorRef, useElement, type PlateEditor } from 'platejs/react';
|
||||
import type { TElement } from 'platejs';
|
||||
import {
|
||||
useDownloadMetricDataCSV,
|
||||
useDownloadPNGSelectMenu,
|
||||
useRenameMetricOnPage
|
||||
} from '@/context/Metrics/metricDropdownItems';
|
||||
|
||||
export const useMetricContentThreeDotMenuItems = ({
|
||||
metricId,
|
||||
|
@ -17,6 +24,9 @@ export const useMetricContentThreeDotMenuItems = ({
|
|||
reportVersionNumber: number | undefined;
|
||||
reportId: string;
|
||||
}): DropdownItems => {
|
||||
const editor = useEditorRef();
|
||||
const element = useElement();
|
||||
|
||||
const openChartItem = useOpenChartItem({
|
||||
reportId,
|
||||
metricId,
|
||||
|
@ -24,8 +34,42 @@ export const useMetricContentThreeDotMenuItems = ({
|
|||
reportVersionNumber,
|
||||
metricVersionNumber
|
||||
});
|
||||
const removeFromReportItem = useRemoveFromReportItem({
|
||||
editor,
|
||||
element
|
||||
});
|
||||
const navigateToMetricItem = useNavigatetoMetricItem({
|
||||
reportId,
|
||||
metricId,
|
||||
chatId,
|
||||
reportVersionNumber,
|
||||
metricVersionNumber
|
||||
});
|
||||
const downloadCSV = useDownloadMetricDataCSV({ metricId, metricVersionNumber });
|
||||
const downloadPNG = useDownloadPNGSelectMenu({ metricId, metricVersionNumber });
|
||||
const renameMetric = useRenameMetricOnPage({ metricId, metricVersionNumber });
|
||||
|
||||
return [openChartItem, { type: 'divider' }];
|
||||
return useMemo(
|
||||
() => [
|
||||
openChartItem,
|
||||
removeFromReportItem,
|
||||
{ type: 'divider' },
|
||||
...navigateToMetricItem,
|
||||
{ type: 'divider' },
|
||||
downloadCSV,
|
||||
downloadPNG,
|
||||
{ type: 'divider' },
|
||||
renameMetric
|
||||
],
|
||||
[
|
||||
openChartItem,
|
||||
removeFromReportItem,
|
||||
navigateToMetricItem,
|
||||
downloadCSV,
|
||||
downloadPNG,
|
||||
renameMetric
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const useOpenChartItem = ({
|
||||
|
@ -54,20 +98,20 @@ const useOpenChartItem = ({
|
|||
() => ({
|
||||
value: 'open-chart',
|
||||
label: 'Open chart',
|
||||
icon: <ASSET_ICONS.metrics />,
|
||||
icon: <ArrowUpRight />,
|
||||
link: route,
|
||||
linkIcon: 'arrow-external'
|
||||
linkIcon: 'arrow-right'
|
||||
}),
|
||||
[route]
|
||||
);
|
||||
};
|
||||
|
||||
const useRemoveFromReportItem = ({
|
||||
reportId,
|
||||
metricId
|
||||
editor,
|
||||
element
|
||||
}: {
|
||||
reportId: string;
|
||||
metricId: string;
|
||||
editor: PlateEditor;
|
||||
element: TElement;
|
||||
}): DropdownItem => {
|
||||
return useMemo(
|
||||
() => ({
|
||||
|
@ -75,9 +119,56 @@ const useRemoveFromReportItem = ({
|
|||
label: 'Remove from report',
|
||||
icon: <Trash />,
|
||||
onClick: () => {
|
||||
console.log('remove from report');
|
||||
const path = editor.api.findPath(element);
|
||||
editor.tf.removeNodes({ at: path });
|
||||
}
|
||||
}),
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
const useNavigatetoMetricItem = ({
|
||||
reportId,
|
||||
metricId,
|
||||
chatId,
|
||||
reportVersionNumber,
|
||||
metricVersionNumber
|
||||
}: {
|
||||
reportId: string;
|
||||
metricId: string;
|
||||
metricVersionNumber: number | undefined;
|
||||
chatId: string | undefined;
|
||||
reportVersionNumber: number | undefined;
|
||||
}): DropdownItem[] => {
|
||||
return useMemo(() => {
|
||||
const baseParams = {
|
||||
assetId: metricId,
|
||||
type: 'metric' as const,
|
||||
reportVersionNumber,
|
||||
metricVersionNumber,
|
||||
reportId,
|
||||
chatId
|
||||
};
|
||||
|
||||
const editChartRoute = assetParamsToRoute({
|
||||
...baseParams,
|
||||
page: 'chart'
|
||||
});
|
||||
|
||||
const resultsChartRoute = assetParamsToRoute({
|
||||
...baseParams,
|
||||
page: 'results'
|
||||
});
|
||||
|
||||
const sqlChartRoute = assetParamsToRoute({
|
||||
...baseParams,
|
||||
page: 'sql'
|
||||
});
|
||||
|
||||
return [
|
||||
{ value: 'edit-chart', label: 'Edit chart', icon: <SquareChartPen />, link: editChartRoute },
|
||||
{ value: 'results-chart', label: 'Results chart', icon: <Table />, link: resultsChartRoute },
|
||||
{ value: 'sql-chart', label: 'SQL chart', icon: <Code />, link: sqlChartRoute }
|
||||
];
|
||||
}, [reportId, metricId, chatId, reportVersionNumber, metricVersionNumber]);
|
||||
};
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
import { type PluginConfig, createTSlatePlugin } from 'platejs';
|
||||
|
||||
export type BannerConfig = PluginConfig<
|
||||
'banner',
|
||||
//api
|
||||
{},
|
||||
//options
|
||||
{},
|
||||
//selectors
|
||||
{},
|
||||
//transforms
|
||||
{}
|
||||
>;
|
||||
export type BannerConfig = PluginConfig<'banner'>;
|
||||
|
||||
export const BannerPlugin = createTSlatePlugin<BannerConfig>({
|
||||
key: 'banner', // unique plugin key
|
||||
|
|
|
@ -25,6 +25,50 @@ const countCharactersInNodes = (nodes: (TNode | Descendant)[]): number => {
|
|||
return count;
|
||||
};
|
||||
|
||||
const CharacterCounterElement = ({
|
||||
attributes,
|
||||
children,
|
||||
...props
|
||||
}: CharacterElementCounterProps) => {
|
||||
const { getOptions, element } = props;
|
||||
const options = getOptions();
|
||||
const selected = useSelected();
|
||||
const { maxLength, showWarning, warningThreshold } = options;
|
||||
|
||||
// Get the character length of only this component's content
|
||||
const characterLength = useMemo(() => {
|
||||
return countCharactersInNodes(element.children);
|
||||
}, [element.children]);
|
||||
|
||||
// Calculate warning state
|
||||
const warningLength = maxLength * warningThreshold;
|
||||
const isOverWarning = characterLength >= warningLength;
|
||||
const isOverLimit = characterLength > maxLength;
|
||||
|
||||
return (
|
||||
<PlateElement
|
||||
className={cn(
|
||||
'rounded-md bg-purple-100 p-2 text-black',
|
||||
selected && 'ring-ring rounded bg-red-200 ring-2 ring-offset-2'
|
||||
)}
|
||||
attributes={{
|
||||
...attributes,
|
||||
'data-plate-open-context-menu': true
|
||||
}}
|
||||
{...props}>
|
||||
<div className="mb-2 text-sm" contentEditable={false}>
|
||||
Character count: {characterLength} / {maxLength}
|
||||
{showWarning && isOverWarning && (
|
||||
<span className={`ml-2 ${isOverLimit ? 'text-red-600' : 'text-yellow-600'}`}>
|
||||
{isOverLimit ? '⚠️ Limit exceeded!' : '⚠️ Approaching limit'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
</PlateElement>
|
||||
);
|
||||
};
|
||||
|
||||
export const CharacterCounterPlugin = createPlatePlugin({
|
||||
key: 'characterCounter',
|
||||
options: {
|
||||
|
@ -80,45 +124,7 @@ export const CharacterCounterPlugin = createPlatePlugin({
|
|||
}
|
||||
},
|
||||
node: {
|
||||
component: ({ attributes, children, ...props }: CharacterElementCounterProps) => {
|
||||
const { getOptions, element } = props;
|
||||
const options = getOptions();
|
||||
const selected = useSelected();
|
||||
const { maxLength, showWarning, warningThreshold } = options;
|
||||
|
||||
// Get the character length of only this component's content
|
||||
const characterLength = useMemo(() => {
|
||||
return countCharactersInNodes(element.children);
|
||||
}, [element.children]);
|
||||
|
||||
// Calculate warning state
|
||||
const warningLength = maxLength * warningThreshold;
|
||||
const isOverWarning = characterLength >= warningLength;
|
||||
const isOverLimit = characterLength > maxLength;
|
||||
|
||||
return (
|
||||
<PlateElement
|
||||
className={cn(
|
||||
'rounded-md bg-purple-100 p-2 text-black',
|
||||
selected && 'ring-ring rounded bg-red-200 ring-2 ring-offset-2'
|
||||
)}
|
||||
attributes={{
|
||||
...attributes,
|
||||
'data-plate-open-context-menu': true
|
||||
}}
|
||||
{...props}>
|
||||
<div className="mb-2 text-sm" contentEditable={false}>
|
||||
Character count: {characterLength} / {maxLength}
|
||||
{showWarning && isOverWarning && (
|
||||
<span className={`ml-2 ${isOverLimit ? 'text-red-600' : 'text-yellow-600'}`}>
|
||||
{isOverLimit ? '⚠️ Limit exceeded!' : '⚠️ Approaching limit'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
</PlateElement>
|
||||
);
|
||||
},
|
||||
component: CharacterCounterElement,
|
||||
isElement: true
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ export const insertMetric = (editor: PlateEditor, options?: InsertNodesOptions)
|
|||
{
|
||||
type: CUSTOM_KEYS.metric,
|
||||
metricId: '',
|
||||
metricVersionNumber: undefined,
|
||||
children: [{ text: '' }]
|
||||
},
|
||||
{ select: true, ...options }
|
||||
|
|
|
@ -7,6 +7,7 @@ export type MetricPluginOptions = {
|
|||
openMetricModal: boolean;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export type MetricPluginApi = {
|
||||
// the methods are defined in the extendApi function
|
||||
};
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export * from './useDownloadMetricDataCSV';
|
||||
export * from './useDownloadMetricDataPNG';
|
||||
export * from './useRenameMetricOnPage';
|
|
@ -0,0 +1,37 @@
|
|||
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
|
||||
import { Download4 } from '@/components/ui/icons';
|
||||
import { exportJSONToCSV } from '@/lib/exportUtils';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
export const useDownloadMetricDataCSV = ({
|
||||
metricId,
|
||||
metricVersionNumber
|
||||
}: {
|
||||
metricId: string;
|
||||
metricVersionNumber: number | undefined;
|
||||
}) => {
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const { data: metricData } = useGetMetricData(
|
||||
{ id: metricId, versionNumber: metricVersionNumber },
|
||||
{ enabled: false }
|
||||
);
|
||||
const { data: name } = useGetMetric({ id: metricId }, { select: (x) => x.name });
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
label: 'Download as CSV',
|
||||
value: 'download-csv',
|
||||
icon: <Download4 />,
|
||||
loading: isDownloading,
|
||||
onClick: async () => {
|
||||
const data = metricData?.data;
|
||||
if (data && name) {
|
||||
setIsDownloading(true);
|
||||
await exportJSONToCSV(data, name);
|
||||
setIsDownloading(false);
|
||||
}
|
||||
}
|
||||
}),
|
||||
[metricData, isDownloading, name]
|
||||
);
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
import { useGetMetric } from '@/api/buster_rest/metrics';
|
||||
import { SquareChart } from '@/components/ui/icons';
|
||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||
import { METRIC_CHART_CONTAINER_ID } from '@/controllers/MetricController/MetricViewChart/config';
|
||||
import { downloadElementToImage } from '@/lib/exportUtils';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useDownloadPNGSelectMenu = ({
|
||||
metricId,
|
||||
metricVersionNumber
|
||||
}: {
|
||||
metricId: string;
|
||||
metricVersionNumber: number | undefined;
|
||||
}) => {
|
||||
const { openErrorMessage } = useBusterNotifications();
|
||||
const { data: name } = useGetMetric(
|
||||
{ id: metricId, versionNumber: metricVersionNumber },
|
||||
{ select: (x) => x.name }
|
||||
);
|
||||
const { data: selectedChartType } = useGetMetric(
|
||||
{ id: metricId },
|
||||
{ select: (x) => x.chart_config?.selectedChartType }
|
||||
);
|
||||
|
||||
const canDownload = selectedChartType && selectedChartType !== 'table';
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
label: 'Download as PNG',
|
||||
value: 'download-png',
|
||||
disabled: !canDownload,
|
||||
icon: <SquareChart />,
|
||||
onClick: async () => {
|
||||
const node = document.getElementById(METRIC_CHART_CONTAINER_ID(metricId)) as HTMLElement;
|
||||
if (node) {
|
||||
try {
|
||||
return await downloadElementToImage(node, `${name}.png`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
openErrorMessage('Failed to download PNG');
|
||||
}
|
||||
}),
|
||||
[canDownload, metricId, name, openErrorMessage]
|
||||
);
|
||||
};
|
|
@ -0,0 +1,68 @@
|
|||
import { Pencil } from '@/components/ui/icons';
|
||||
import { useAppLayoutContextSelector } from '@/context/BusterAppLayout';
|
||||
import { METRIC_CHART_TITLE_INPUT_ID } from '@/controllers/MetricController/MetricViewChart/MetricViewChartHeader';
|
||||
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout/ChatLayoutContext';
|
||||
import { assetParamsToRoute } from '@/lib/assets';
|
||||
import { ensureElementExists } from '@/lib/element';
|
||||
import { timeout } from '@/lib/timeout';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useRenameMetricOnPage = ({
|
||||
metricId,
|
||||
metricVersionNumber
|
||||
}: {
|
||||
metricId: string;
|
||||
metricVersionNumber: number | undefined;
|
||||
}) => {
|
||||
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
const { chatId, dashboardId, reportId, dashboardVersionNumber, reportVersionNumber } =
|
||||
useChatLayoutContextSelector((x) => ({
|
||||
chatId: x.chatId,
|
||||
dashboardId: x.dashboardId,
|
||||
reportId: x.reportId,
|
||||
dashboardVersionNumber: x.dashboardVersionNumber,
|
||||
reportVersionNumber: x.reportVersionNumber
|
||||
}));
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
label: 'Rename metric',
|
||||
value: 'rename-metric',
|
||||
icon: <Pencil />,
|
||||
onClick: async () => {
|
||||
const route = assetParamsToRoute({
|
||||
type: 'metric',
|
||||
assetId: metricId,
|
||||
chatId,
|
||||
dashboardId,
|
||||
reportId,
|
||||
dashboardVersionNumber,
|
||||
reportVersionNumber,
|
||||
metricVersionNumber,
|
||||
page: 'chart'
|
||||
});
|
||||
await onChangePage(route);
|
||||
await timeout(100);
|
||||
const input = await ensureElementExists(
|
||||
() => document.getElementById(METRIC_CHART_TITLE_INPUT_ID) as HTMLInputElement
|
||||
);
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
}
|
||||
}),
|
||||
[
|
||||
onSetFileView,
|
||||
metricId,
|
||||
metricVersionNumber,
|
||||
onChangePage,
|
||||
chatId,
|
||||
dashboardId,
|
||||
reportId,
|
||||
dashboardVersionNumber,
|
||||
reportVersionNumber
|
||||
]
|
||||
);
|
||||
};
|
|
@ -7,7 +7,7 @@ import { assetParamsToRoute } from '@/lib/assets';
|
|||
import { MetricCard } from '@/components/ui/metric';
|
||||
import { useContext } from 'use-context-selector';
|
||||
import { SortableItemContext } from '@/components/ui/grid/SortableItemContext';
|
||||
import { metricCardThreeDotMenuItems } from './metricCardThreeDotMenuItems';
|
||||
import { useMetricCardThreeDotMenuItems } from './metricCardThreeDotMenuItems';
|
||||
|
||||
const DashboardMetricItemBase: React.FC<{
|
||||
metricId: string;
|
||||
|
@ -63,9 +63,7 @@ const DashboardMetricItemBase: React.FC<{
|
|||
});
|
||||
}, [metricId, chatId, dashboardId]);
|
||||
|
||||
const threeDotMenuItems = useMemo(() => {
|
||||
return metricCardThreeDotMenuItems({ dashboardId, metricId });
|
||||
}, [dashboardId, metricId]);
|
||||
const threeDotMenuItems = useMetricCardThreeDotMenuItems({ dashboardId, metricId });
|
||||
|
||||
const onInitialAnimationEndPreflight = useMemoizedFn(() => {
|
||||
setInitialAnimationEnded(metricId);
|
||||
|
|
|
@ -1,18 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useRemoveMetricsFromDashboard } from '@/api/buster_rest/dashboards';
|
||||
import { Button } from '@/components/ui/buttons';
|
||||
import { Dropdown, type DropdownItems, type DropdownItem } from '@/components/ui/dropdown';
|
||||
import {
|
||||
DotsVertical,
|
||||
Trash,
|
||||
ShareRight,
|
||||
PenSparkle,
|
||||
SquareChartPen,
|
||||
Code
|
||||
} from '@/components/ui/icons';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { type DropdownItems, type DropdownItem } from '@/components/ui/dropdown';
|
||||
import { Trash, ShareRight, PenSparkle, SquareChartPen, Code } from '@/components/ui/icons';
|
||||
import { ASSET_ICONS } from '@/components/features/config/assetIcons';
|
||||
import { assetParamsToRoute } from '@/lib/assets/assetParamsToRoute';
|
||||
import { useChatLayoutContextSelector } from '@/layouts/ChatLayout';
|
||||
|
@ -28,7 +19,7 @@ import {
|
|||
useMetricDrilldownItem
|
||||
} from '@/components/features/metrics/ThreeDotMenu';
|
||||
|
||||
export const metricCardThreeDotMenuItems = ({
|
||||
export const useMetricCardThreeDotMenuItems = ({
|
||||
dashboardId,
|
||||
metricId
|
||||
}: {
|
||||
|
|
|
@ -60,6 +60,11 @@ import {
|
|||
useVersionHistorySelectMenu,
|
||||
useMetricDrilldownItem
|
||||
} from '@/components/features/metrics/ThreeDotMenu';
|
||||
import {
|
||||
useDownloadMetricDataCSV,
|
||||
useDownloadPNGSelectMenu,
|
||||
useRenameMetricOnPage
|
||||
} from '@/context/Metrics/metricDropdownItems';
|
||||
|
||||
export const ThreeDotMenuButton = React.memo(
|
||||
({
|
||||
|
@ -82,10 +87,19 @@ export const ThreeDotMenuButton = React.memo(
|
|||
const editChartMenu = useEditChartSelectMenu();
|
||||
const resultsViewMenu = useResultsViewSelectMenu({ chatId, metricId });
|
||||
const sqlEditorMenu = useSQLEditorSelectMenu({ chatId, metricId });
|
||||
const downloadCSVMenu = useDownloadCSVSelectMenu({ metricId });
|
||||
const downloadPNGMenu = useDownloadPNGSelectMenu({ metricId });
|
||||
const downloadCSVMenu = useDownloadMetricDataCSV({
|
||||
metricId,
|
||||
metricVersionNumber: versionNumber
|
||||
});
|
||||
const downloadPNGMenu = useDownloadPNGSelectMenu({
|
||||
metricId,
|
||||
metricVersionNumber: versionNumber
|
||||
});
|
||||
const deleteMetricMenu = useDeleteMetricSelectMenu({ metricId });
|
||||
const renameMetricMenu = useRenameMetricSelectMenu({ metricId });
|
||||
const renameMetricMenu = useRenameMetricOnPage({
|
||||
metricId,
|
||||
metricVersionNumber: versionNumber
|
||||
});
|
||||
const shareMenu = useShareMenuSelectMenu({ metricId });
|
||||
const drilldownItem = useMetricDrilldownItem({ metricId });
|
||||
|
||||
|
@ -359,63 +373,6 @@ const useSQLEditorSelectMenu = ({
|
|||
);
|
||||
};
|
||||
|
||||
const useDownloadCSVSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const { data: metricData } = useGetMetricData({ id: metricId }, { enabled: false });
|
||||
const { data: name } = useGetMetric({ id: metricId }, { select: (x) => x.name });
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
label: 'Download as CSV',
|
||||
value: 'download-csv',
|
||||
icon: <Download4 />,
|
||||
loading: isDownloading,
|
||||
onClick: async () => {
|
||||
const data = metricData?.data;
|
||||
if (data && name) {
|
||||
setIsDownloading(true);
|
||||
await exportJSONToCSV(data, name);
|
||||
setIsDownloading(false);
|
||||
}
|
||||
}
|
||||
}),
|
||||
[metricData, isDownloading, name]
|
||||
);
|
||||
};
|
||||
|
||||
const useDownloadPNGSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||
const { openErrorMessage } = useBusterNotifications();
|
||||
const { data: name } = useGetMetric({ id: metricId }, { select: (x) => x.name });
|
||||
const { data: selectedChartType } = useGetMetric(
|
||||
{ id: metricId },
|
||||
{ select: (x) => x.chart_config?.selectedChartType }
|
||||
);
|
||||
|
||||
const canDownload = selectedChartType && selectedChartType !== 'table';
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
label: 'Download as PNG',
|
||||
value: 'download-png',
|
||||
disabled: !canDownload,
|
||||
icon: <SquareChart />,
|
||||
onClick: async () => {
|
||||
const node = document.getElementById(METRIC_CHART_CONTAINER_ID(metricId)) as HTMLElement;
|
||||
if (node) {
|
||||
try {
|
||||
return await downloadElementToImage(node, `${name}.png`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
openErrorMessage('Failed to download PNG');
|
||||
}
|
||||
}),
|
||||
[canDownload, metricId, name, openErrorMessage]
|
||||
);
|
||||
};
|
||||
|
||||
const useDeleteMetricSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||
const { mutateAsync: deleteMetric } = useDeleteMetric();
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
|
@ -434,39 +391,6 @@ const useDeleteMetricSelectMenu = ({ metricId }: { metricId: string }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const useRenameMetricSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||
const onSetFileView = useChatLayoutContextSelector((x) => x.onSetFileView);
|
||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||
const chatId = useChatLayoutContextSelector((x) => x.chatId);
|
||||
const dashboardId = useChatLayoutContextSelector((x) => x.dashboardId);
|
||||
return useMemo(
|
||||
() => ({
|
||||
label: 'Rename metric',
|
||||
value: 'rename-metric',
|
||||
icon: <Pencil />,
|
||||
onClick: async () => {
|
||||
const route = assetParamsToRoute({
|
||||
type: 'metric',
|
||||
assetId: metricId,
|
||||
chatId,
|
||||
dashboardId,
|
||||
page: 'chart'
|
||||
});
|
||||
await onChangePage(route);
|
||||
await timeout(100);
|
||||
const input = await ensureElementExists(
|
||||
() => document.getElementById(METRIC_CHART_TITLE_INPUT_ID) as HTMLInputElement
|
||||
);
|
||||
if (input) {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
}
|
||||
}),
|
||||
[onSetFileView]
|
||||
);
|
||||
};
|
||||
|
||||
export const useShareMenuSelectMenu = ({ metricId }: { metricId: string }) => {
|
||||
const { data: shareAssetConfig } = useGetMetric(
|
||||
{ id: metricId },
|
||||
|
|
|
@ -252,6 +252,15 @@ export type BusterAppRoutesWithArgs = {
|
|||
metricVersionNumber?: number;
|
||||
reportVersionNumber?: number;
|
||||
};
|
||||
[BusterAppRoutes.APP_CHAT_ID_REPORT_ID_METRIC_ID_RESULTS]: {
|
||||
route: BusterAppRoutes.APP_CHAT_ID_REPORT_ID_METRIC_ID_RESULTS;
|
||||
chatId: string;
|
||||
reportId: string;
|
||||
metricId: string;
|
||||
metricVersionNumber?: number;
|
||||
reportVersionNumber?: number;
|
||||
secondaryView?: MetricFileViewSecondary;
|
||||
};
|
||||
[BusterAppRoutes.APP_CHAT_ID_DATASET_ID]: {
|
||||
route: BusterAppRoutes.APP_CHAT_ID_DATASET_ID;
|
||||
chatId: string;
|
||||
|
|
Loading…
Reference in New Issue