Add metric data retrieval endpoint and update schemas

- Introduced a new endpoint to retrieve metric data with pagination at /metric_files/:id/data.
- Added MetricDataParamsSchema and MetricDataQuerySchema for request validation.
- Updated GetMetricDataRequestSchema to include an optional limit parameter for pagination.
This commit is contained in:
dal 2025-08-21 15:55:19 -06:00
parent cf8610eda4
commit 3c8f1f4615
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
4 changed files with 121 additions and 2 deletions

View File

@ -0,0 +1,78 @@
import { type AssetPermissionCheck, checkPermission } from '@buster/access-controls';
import type { User } from '@buster/database';
import { getUserOrganizationId } from '@buster/database';
import type { MetricDataResponse } from '@buster/server-shared/metrics';
import { HTTPException } from 'hono/http-exception';
/**
* Handler for retrieving metric data
*
* This handler:
* 1. Validates user has access to the organization
* 2. Checks user has permission to view the metric file
* 3. Retrieves the metric definition
* 4. Parses the metric content to extract SQL
* 5. Executes the query against the data source
* 6. Returns the data with metadata and pagination info
*
* @param metricId - The ID of the metric to retrieve data for
* @param limit - Maximum number of rows to return (default 5000, max 5000)
* @param user - The authenticated user
* @returns The metric data with metadata
*/
export async function getMetricDataHandler(
metricId: string,
limit: number = 5000,
user: User
): Promise<MetricDataResponse> {
// Get user's organization
const userOrg = await getUserOrganizationId(user.id);
if (!userOrg) {
throw new HTTPException(403, {
message: 'You must be part of an organization to access metric data',
});
}
const { organizationId } = userOrg;
// Check if user has permission to view this metric file
const permissionCheck: AssetPermissionCheck = {
userId: user.id,
assetId: metricId,
assetType: 'metric_file',
requiredRole: 'can_view',
organizationId,
};
const permissionResult = await checkPermission(permissionCheck);
if (!permissionResult.hasAccess) {
throw new HTTPException(403, {
message: 'You do not have permission to view this metric',
});
}
// Ensure limit is within bounds
const queryLimit = Math.min(Math.max(limit, 1), 5000);
// TODO: Implement the following steps in subsequent tickets:
// 1. Retrieve metric definition from database
// 2. Parse metric content (YAML/JSON) to extract SQL query
// 3. Get data source connection details
// 4. Execute query using appropriate data source adapter
// 5. Process results and build metadata
// 6. Check if there are more records beyond the limit
// Placeholder response for now
return {
data: [],
data_metadata: {
column_count: 0,
column_metadata: [],
row_count: 0,
},
metricId,
has_more_records: false,
};
}

View File

@ -1,15 +1,36 @@
import { MetricDownloadParamsSchema } from '@buster/server-shared/metrics';
import {
MetricDataParamsSchema,
MetricDataQuerySchema,
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';
import { getMetricDataHandler } from './get-metric-data';
const app = new Hono()
// Apply authentication middleware to all routes
.use('*', requireAuth)
// GET /metric_files/:id/data - Get metric data with pagination
.get(
'/:id/data',
zValidator('param', MetricDataParamsSchema),
zValidator('query', MetricDataQuerySchema),
async (c) => {
const { id } = c.req.valid('param');
const { limit } = c.req.valid('query');
const user = c.get('busterUser');
const response = await getMetricDataHandler(id, limit, user);
return c.json(response);
}
)
// GET /metric_files/:id/download - Download metric file data as CSV
.get('/:id/download', zValidator('param', MetricDownloadParamsSchema), async (c) => {
const { id } = c.req.valid('param');

View File

@ -8,7 +8,9 @@ export const GetMetricRequestSchema = z.object({
version_number: z.number().optional(), //api will default to latest if not provided
});
export const GetMetricDataRequestSchema = GetMetricRequestSchema;
export const GetMetricDataRequestSchema = GetMetricRequestSchema.extend({
limit: z.number().min(1).max(5000).default(5000).optional(),
});
export const GetMetricListRequestSchema = z.object({
/** The token representing the current page number for pagination */

View File

@ -39,3 +39,21 @@ export type ShareMetricResponse = z.infer<typeof ShareMetricResponseSchema>;
export type ShareDeleteResponse = z.infer<typeof ShareDeleteResponseSchema>;
export type ShareUpdateResponse = z.infer<typeof ShareUpdateResponseSchema>;
export type MetricDataResponse = z.infer<typeof MetricDataResponseSchema>;
/**
* Path parameters for metric data endpoint
*/
export const MetricDataParamsSchema = z.object({
id: z.string().uuid('Metric ID must be a valid UUID'),
});
export type MetricDataParams = z.infer<typeof MetricDataParamsSchema>;
/**
* Query parameters for metric data endpoint
*/
export const MetricDataQuerySchema = z.object({
limit: z.coerce.number().min(1).max(5000).default(5000).optional(),
});
export type MetricDataQuery = z.infer<typeof MetricDataQuerySchema>;