mirror of https://github.com/buster-so/buster.git
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:
parent
cf8610eda4
commit
3c8f1f4615
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -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 { zValidator } from '@hono/zod-validator';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
import { HTTPException } from 'hono/http-exception';
|
||||||
import { requireAuth } from '../../../middleware/auth';
|
import { requireAuth } from '../../../middleware/auth';
|
||||||
import '../../../types/hono.types';
|
import '../../../types/hono.types';
|
||||||
import { downloadMetricFileHandler } from './download-metric-file';
|
import { downloadMetricFileHandler } from './download-metric-file';
|
||||||
|
import { getMetricDataHandler } from './get-metric-data';
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
// Apply authentication middleware to all routes
|
// Apply authentication middleware to all routes
|
||||||
.use('*', requireAuth)
|
.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 /metric_files/:id/download - Download metric file data as CSV
|
||||||
.get('/:id/download', zValidator('param', MetricDownloadParamsSchema), async (c) => {
|
.get('/:id/download', zValidator('param', MetricDownloadParamsSchema), async (c) => {
|
||||||
const { id } = c.req.valid('param');
|
const { id } = c.req.valid('param');
|
||||||
|
|
|
@ -8,7 +8,9 @@ export const GetMetricRequestSchema = z.object({
|
||||||
version_number: z.number().optional(), //api will default to latest if not provided
|
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({
|
export const GetMetricListRequestSchema = z.object({
|
||||||
/** The token representing the current page number for pagination */
|
/** The token representing the current page number for pagination */
|
||||||
|
|
|
@ -39,3 +39,21 @@ export type ShareMetricResponse = z.infer<typeof ShareMetricResponseSchema>;
|
||||||
export type ShareDeleteResponse = z.infer<typeof ShareDeleteResponseSchema>;
|
export type ShareDeleteResponse = z.infer<typeof ShareDeleteResponseSchema>;
|
||||||
export type ShareUpdateResponse = z.infer<typeof ShareUpdateResponseSchema>;
|
export type ShareUpdateResponse = z.infer<typeof ShareUpdateResponseSchema>;
|
||||||
export type MetricDataResponse = z.infer<typeof MetricDataResponseSchema>;
|
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>;
|
||||||
|
|
Loading…
Reference in New Issue