diff --git a/apps/server/src/api/v2/metric_files/[id]/download/GET.ts b/apps/server/src/api/v2/metric_files/[id]/download/GET.ts index 386a43ccd..0578f88a8 100644 --- a/apps/server/src/api/v2/metric_files/[id]/download/GET.ts +++ b/apps/server/src/api/v2/metric_files/[id]/download/GET.ts @@ -1,4 +1,4 @@ -import { MetricDownloadParamsSchema } from '@buster/server-shared'; +import { MetricDownloadParamsSchema, MetricDownloadQueryParamsSchema } from '@buster/server-shared'; import { zValidator } from '@hono/zod-validator'; import { Hono } from 'hono'; import { standardErrorHandler } from '../../../../../utils/response'; @@ -6,14 +6,20 @@ import { downloadMetricFileHandler } from './download-metric-file'; const app = new Hono() // GET /metric_files/:id/download - Download metric file data as CSV - .get('/', zValidator('param', MetricDownloadParamsSchema), async (c) => { - const { id } = c.req.valid('param'); - const user = c.get('busterUser'); + .get( + '/', + zValidator('param', MetricDownloadParamsSchema), + zValidator('query', MetricDownloadQueryParamsSchema), + async (c) => { + const { id } = c.req.valid('param'); + // const { report_file_id } = c.req.valid('query'); + const user = c.get('busterUser'); - const response = await downloadMetricFileHandler(id, user); + const response = await downloadMetricFileHandler(id, user); - return c.json(response); - }) + return c.json(response); + } + ) .onError(standardErrorHandler); export default app; diff --git a/apps/web/src/api/buster_rest/metrics/getMetricQueryRequests.ts b/apps/web/src/api/buster_rest/metrics/getMetricQueryRequests.ts index 463c3b60c..3aba175b0 100644 --- a/apps/web/src/api/buster_rest/metrics/getMetricQueryRequests.ts +++ b/apps/web/src/api/buster_rest/metrics/getMetricQueryRequests.ts @@ -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...`); + } + }, }); }; diff --git a/apps/web/src/api/buster_rest/metrics/requests.ts b/apps/web/src/api/buster_rest/metrics/requests.ts index 4da4ef9a2..8059c8914 100644 --- a/apps/web/src/api/buster_rest/metrics/requests.ts +++ b/apps/web/src/api/buster_rest/metrics/requests.ts @@ -12,6 +12,8 @@ import type { GetMetricResponse, ListMetricsResponse, MetricDataResponse, + MetricDownloadParams, + MetricDownloadQueryParams, MetricDownloadResponse, ShareDeleteResponse, ShareUpdateResponse, @@ -100,8 +102,11 @@ export const updateMetricShare = async ({ }; // Download metric file -export const downloadMetricFile = async (id: string): Promise => { +export const downloadMetricFile = async ({ + id, + ...params +}: MetricDownloadParams & MetricDownloadQueryParams): Promise => { return mainApiV2 - .get(`/metric_files/${id}/download`) + .get(`/metric_files/${id}/download`, { params }) .then((res) => res.data); }; diff --git a/apps/web/src/api/createAxiosInstance.ts b/apps/web/src/api/createAxiosInstance.ts index 513c08f43..3f09d72e9 100644 --- a/apps/web/src/api/createAxiosInstance.ts +++ b/apps/web/src/api/createAxiosInstance.ts @@ -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({ diff --git a/apps/web/src/components/features/metrics/MetricViewChart/MetricDataTruncatedWarning.tsx b/apps/web/src/components/features/metrics/MetricViewChart/MetricDataTruncatedWarning.tsx index 4ffcda2ad..d1c257838 100644 --- a/apps/web/src/components/features/metrics/MetricViewChart/MetricDataTruncatedWarning.tsx +++ b/apps/web/src/components/features/metrics/MetricViewChart/MetricDataTruncatedWarning.tsx @@ -1,53 +1,30 @@ 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'; import { Text } from '@/components/ui/typography'; +import { useGetReportParams } from '@/context/Reports/useGetReportParams'; import { cn } from '@/lib/classMerge'; interface MetricDataTruncatedWarningProps { className?: string; metricId: string; + metricVersionNumber: number | undefined; } export const MetricDataTruncatedWarning: React.FC = ({ className, metricId, + metricVersionNumber, }) => { 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>; - - // 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 (