download metric file in three dot

This commit is contained in:
Nate Kelley 2025-09-23 13:29:29 -06:00
parent 3488f8798b
commit 5708592a51
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
5 changed files with 24 additions and 79 deletions

View File

@ -18,6 +18,7 @@ import {
setProtectedAssetPasswordError,
useProtectedAssetPassword,
} from '@/context/BusterAssets/useProtectedAssetStore';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { setOriginalMetric } from '@/context/Metrics/useOriginalMetricStore';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { upgradeMetricToIMetric } from '@/lib/metrics';
@ -226,8 +227,20 @@ export const usePrefetchGetMetricDataClient = () => {
);
};
export const useDownloadMetricFile = () => {
export const useDownloadMetricFile = (downloadImmediate = true) => {
const { openInfoMessage } = useBusterNotifications();
return useMutation({
mutationFn: downloadMetricFile,
onSuccess: (data) => {
if (downloadImmediate) {
const link = document.createElement('a');
link.href = data.downloadUrl;
link.download = ''; // This will use the filename from the response-content-disposition header
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
openInfoMessage(`Downloading ${data.rowCount} records...`);
}
},
});
};

View File

@ -6,7 +6,7 @@ import { Route as AuthRoute } from '@/routes/auth.login';
import { BASE_URL_V2 } from './config';
import { rustErrorHandler } from './errors';
const AXIOS_TIMEOUT = 180000; // 3 minutes
const AXIOS_TIMEOUT = 120000; // 2 minutes
export const createAxiosInstance = (baseURL = BASE_URL_V2) => {
const apiInstance = axios.create({

View File

@ -1,5 +1,4 @@
import type React from 'react';
import { useState } from 'react';
import { useDownloadMetricFile } from '@/api/buster_rest/metrics/getMetricQueryRequests';
import { Button } from '@/components/ui/buttons';
import { CircleWarning, Download4 } from '@/components/ui/icons';
@ -16,38 +15,13 @@ export const MetricDataTruncatedWarning: React.FC<MetricDataTruncatedWarningProp
metricId,
}) => {
const {
mutateAsync: downloadMetricFile,
mutateAsync: handleDownload,
isPending: isGettingFile,
error: downloadError,
} = useDownloadMetricFile();
const hasError = !!downloadError;
const handleDownload = async () => {
try {
// Create a timeout promise that rejects after 2 minutes (matching backend timeout)
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Download timeout')), 2 * 60 * 1000); // 2 minutes
});
// Race between the API call and the timeout
const response = (await Promise.race([
downloadMetricFile(metricId),
timeoutPromise,
])) as Awaited<ReturnType<typeof downloadMetricFile>>;
// Create a temporary anchor element to trigger download without navigation
const link = document.createElement('a');
link.href = response.downloadUrl;
link.download = ''; // This will use the filename from the response-content-disposition header
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
console.error('Failed to download metric file:', error);
}
};
return (
<div
className={cn(
@ -67,7 +41,7 @@ export const MetricDataTruncatedWarning: React.FC<MetricDataTruncatedWarningProp
</Text>
</div>
<Button
onClick={handleDownload}
onClick={() => handleDownload(metricId)}
loading={isGettingFile}
variant={hasError ? 'danger' : 'default'}
className="ml-4"

View File

@ -1,16 +1,10 @@
import { AnimatePresence, motion } from 'framer-motion';
import React from 'react';
import type { BusterMetric, BusterMetricData } from '@/api/asset_interfaces/metric';
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
import type { BusterMetricData } from '@/api/asset_interfaces/metric';
import { useGetMetricData } from '@/api/buster_rest/metrics';
import { cn } from '@/lib/utils';
import { MetricChartCard } from '../MetricChartCard';
import { MetricChartEvaluation } from './MetricChartEvaluation';
import { MetricDataTruncatedWarning } from './MetricDataTruncatedWarning';
const stableMetricSelect = ({ evaluation_score, evaluation_summary }: BusterMetric) => ({
evaluation_score,
evaluation_summary,
});
const stableMetricDataSelect = (x: BusterMetricData) => x?.has_more_records;
export const MetricViewChart: React.FC<{
@ -21,10 +15,6 @@ export const MetricViewChart: React.FC<{
cardClassName?: string;
}> = React.memo(
({ metricId, versionNumber, readOnly = false, className = '', cardClassName = '' }) => {
const { data: metric } = useGetMetric(
{ id: metricId, versionNumber },
{ select: stableMetricSelect, enabled: true }
);
const { data: hasMoreRecords } = useGetMetricData(
{ id: metricId, versionNumber },
{ select: stableMetricDataSelect }
@ -41,11 +31,6 @@ export const MetricViewChart: React.FC<{
/>
{hasMoreRecords && <MetricDataTruncatedWarning metricId={metricId} />}
</div>
<MetricChartEvaluationWrapper
evaluationScore={metric?.evaluation_score}
evaluationSummary={metric?.evaluation_summary}
/>
</div>
);
}
@ -59,22 +44,3 @@ const animation = {
exit: { opacity: 0 },
transition: { duration: 0.4 },
};
const MetricChartEvaluationWrapper: React.FC<{
evaluationScore: BusterMetric['evaluation_score'] | undefined;
evaluationSummary: string | undefined;
}> = ({ evaluationScore, evaluationSummary }) => {
const show = !!evaluationScore && !!evaluationSummary;
return (
<AnimatePresence initial={false}>
{show && (
<motion.div {...animation}>
<MetricChartEvaluation
evaluationScore={evaluationScore}
evaluationSummary={evaluationSummary}
/>
</motion.div>
)}
</AnimatePresence>
);
};

View File

@ -1,7 +1,7 @@
import { useNavigate } from '@tanstack/react-router';
import React, { useCallback, useMemo, useState } from 'react';
import type { BusterMetric } from '@/api/asset_interfaces/metric';
import { useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
import { useDownloadMetricFile, useGetMetric, useGetMetricData } from '@/api/buster_rest/metrics';
import {
createDropdownItem,
createDropdownItems,
@ -25,11 +25,10 @@ import { Star as StarFilled } from '@/components/ui/icons/NucleoIconFilled';
import { useStartChatFromAsset } from '@/context/BusterAssets/useStartChatFromAsset';
import { useBusterNotifications } from '@/context/BusterNotifications';
import { ensureElementExists } from '@/lib/element';
import { downloadElementToImage, exportJSONToCSV } from '@/lib/exportUtils';
import { downloadElementToImage } from '@/lib/exportUtils';
import { canEdit } from '../../../lib/share';
import { FollowUpWithAssetContent } from '../assets/FollowUpWithAsset';
import { useFavoriteStar } from '../favorites';
import { ASSET_ICONS } from '../icons/assetIcons';
import { getShareAssetConfig } from '../ShareMenu/helpers';
import { useListMetricVersionDropdownItems } from '../versionHistory/useListMetricVersionDropdownItems';
import { METRIC_CHART_CONTAINER_ID } from './MetricChartCard/config';
@ -165,15 +164,10 @@ export const useDownloadMetricDataCSV = ({
metricVersionNumber: number | undefined;
cacheDataId?: string;
}) => {
const [isDownloading, setIsDownloading] = useState(false);
const { data: metricData } = useGetMetricData(
{ id: metricId, versionNumber: metricVersionNumber, cacheDataId },
{ enabled: false }
);
const { data: name } = useGetMetric(
{ id: metricId },
{ select: useCallback((x: BusterMetric) => x.name, []) }
);
return useMemo(
() => ({
@ -183,14 +177,12 @@ export const useDownloadMetricDataCSV = ({
loading: isDownloading,
onClick: async () => {
const data = metricData?.data;
if (data && name) {
setIsDownloading(true);
await exportJSONToCSV(data, name);
setIsDownloading(false);
if (data) {
await handleDownload(metricId);
}
},
}),
[metricData, isDownloading, name]
[metricData, isDownloading]
);
};