mirror of https://github.com/buster-so/buster.git
Refactor metric file handling and improve error handling
- Updated type definitions for better clarity and type safety in metric file download and export tasks. - Enhanced error handling in the metric files API to utilize HTTPException for more consistent error responses. - Added environment variable validation in export tasks to ensure necessary credentials are present before execution. - Improved type assertions for content in metric export queries to ensure proper handling of JSONB data.
This commit is contained in:
parent
a3c4238da1
commit
594f08202b
|
@ -43,7 +43,7 @@ export async function downloadMetricFileHandler(
|
|||
const timeout = 120000; // 2 minutes
|
||||
const pollInterval = 2000; // Poll every 2 seconds
|
||||
|
||||
let run;
|
||||
let run: Awaited<ReturnType<typeof runs.retrieve>>;
|
||||
while (true) {
|
||||
run = await runs.retrieve(handle.id);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { MetricDownloadParamsSchema } from '@buster/server-shared/metrics';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { requireAuth } from '../../../middleware/auth';
|
||||
import '../../../types/hono.types';
|
||||
import { downloadMetricFileHandler } from './download-metric-file';
|
||||
|
@ -28,8 +29,8 @@ const app = new Hono()
|
|||
console.error('Metric files API error:', err);
|
||||
|
||||
// Let HTTPException responses pass through
|
||||
if (err instanceof Error && 'getResponse' in err) {
|
||||
return (err as any).getResponse();
|
||||
if (err instanceof HTTPException) {
|
||||
return err.getResponse();
|
||||
}
|
||||
|
||||
// Default error response
|
||||
|
|
|
@ -2,13 +2,24 @@ import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|||
import { logger, task } from '@trigger.dev/sdk';
|
||||
import { CleanupExportFileInputSchema } from './interfaces';
|
||||
|
||||
// Validate required environment variables
|
||||
if (!process.env.R2_ACCOUNT_ID) {
|
||||
throw new Error('R2_ACCOUNT_ID environment variable is missing');
|
||||
}
|
||||
if (!process.env.R2_ACCESS_KEY_ID) {
|
||||
throw new Error('R2_ACCESS_KEY_ID environment variable is missing');
|
||||
}
|
||||
if (!process.env.R2_SECRET_ACCESS_KEY) {
|
||||
throw new Error('R2_SECRET_ACCESS_KEY environment variable is missing');
|
||||
}
|
||||
|
||||
// Initialize R2 client
|
||||
const r2Client = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -12,13 +12,24 @@ import {
|
|||
type ExportMetricDataOutput,
|
||||
} from './interfaces';
|
||||
|
||||
// Validate required environment variables
|
||||
if (!process.env.R2_ACCOUNT_ID) {
|
||||
throw new Error('R2_ACCOUNT_ID environment variable is missing');
|
||||
}
|
||||
if (!process.env.R2_ACCESS_KEY_ID) {
|
||||
throw new Error('R2_ACCESS_KEY_ID environment variable is missing');
|
||||
}
|
||||
if (!process.env.R2_SECRET_ACCESS_KEY) {
|
||||
throw new Error('R2_SECRET_ACCESS_KEY environment variable is missing');
|
||||
}
|
||||
|
||||
// Initialize R2 client (S3-compatible)
|
||||
const r2Client = new S3Client({
|
||||
region: 'auto',
|
||||
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -120,11 +131,14 @@ export const exportMetricData: ReturnType<
|
|||
logger.log('Executing metric query', { sql: `${metric.sql?.substring(0, 100)}...` });
|
||||
|
||||
const adapter = await createAdapter(credentials);
|
||||
let queryResult;
|
||||
let queryResult: Awaited<ReturnType<typeof adapter.query>> | undefined;
|
||||
|
||||
try {
|
||||
if (!metric.sql) {
|
||||
throw new Error('Metric SQL is missing');
|
||||
}
|
||||
queryResult = await adapter.query(
|
||||
metric.sql!,
|
||||
metric.sql,
|
||||
[], // No parameters for metric queries
|
||||
MAX_ROWS,
|
||||
60000 // 60 second query timeout
|
||||
|
|
|
@ -54,7 +54,7 @@ const StreamTokenArrayDemo = ({
|
|||
}, [tokens, autoStream, streamDelay]);
|
||||
|
||||
const { throttledTokens, throttledContent, isDone, flushNow, reset } = useStreamTokenArray({
|
||||
tokens: (autoStream ? currentTokens : tokens).map(t => ({ token: t, delayMs: 0 })),
|
||||
tokens: (autoStream ? currentTokens : tokens).map((t) => ({ token: t, delayMs: 0 })),
|
||||
isStreamFinished: autoStream ? finished : isStreamFinished,
|
||||
...hookProps
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ export type GetMetricForExportInput = z.infer<typeof GetMetricForExportInputSche
|
|||
export interface MetricForExport {
|
||||
id: string;
|
||||
name: string;
|
||||
content: any; // JSONB content containing SQL and other metadata
|
||||
content: Record<string, unknown>; // JSONB content containing SQL and other metadata
|
||||
dataSourceId: string;
|
||||
organizationId: string;
|
||||
secretId: string;
|
||||
|
@ -58,13 +58,18 @@ export async function getMetricForExport(input: GetMetricForExportInput): Promis
|
|||
|
||||
if (typeof result.content === 'object' && result.content !== null) {
|
||||
// Check common locations for SQL in metric content
|
||||
const content = result.content as Record<string, any>;
|
||||
const content = result.content as Record<string, unknown>;
|
||||
sql =
|
||||
content.sql ||
|
||||
content.query ||
|
||||
content.sqlQuery ||
|
||||
content.definition?.sql ||
|
||||
content.definition?.query;
|
||||
(typeof content.sql === 'string' ? content.sql : undefined) ||
|
||||
(typeof content.query === 'string' ? content.query : undefined) ||
|
||||
(typeof content.sqlQuery === 'string' ? content.sqlQuery : undefined) ||
|
||||
(typeof content.definition === 'object' && content.definition !== null
|
||||
? typeof (content.definition as Record<string, unknown>).sql === 'string'
|
||||
? ((content.definition as Record<string, unknown>).sql as string)
|
||||
: typeof (content.definition as Record<string, unknown>).query === 'string'
|
||||
? ((content.definition as Record<string, unknown>).query as string)
|
||||
: undefined
|
||||
: undefined);
|
||||
}
|
||||
|
||||
if (!sql) {
|
||||
|
@ -74,7 +79,7 @@ export async function getMetricForExport(input: GetMetricForExportInput): Promis
|
|||
return {
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
content: result.content,
|
||||
content: result.content as Record<string, unknown>,
|
||||
dataSourceId: result.dataSourceId,
|
||||
organizationId: result.organizationId,
|
||||
secretId: result.secretId,
|
||||
|
|
Loading…
Reference in New Issue