mirror of https://github.com/buster-so/buster.git
report take screenshot
This commit is contained in:
parent
4fe31fab45
commit
ee623169c0
|
@ -47,7 +47,7 @@ const app = new Hono().get(
|
|||
type: search.type,
|
||||
supabaseCookieKey: c.get('supabaseCookieKey'),
|
||||
supabaseUser: c.get('supabaseUser'),
|
||||
accessToken: c.get('accessToken') || '',
|
||||
accessToken: c.get('accessToken'),
|
||||
organizationId: chat.organizationId,
|
||||
});
|
||||
|
||||
|
|
|
@ -141,9 +141,7 @@ export async function createChatHandler(
|
|||
// Just queue the background job - should be <100ms
|
||||
const taskHandle = await tasks.trigger(
|
||||
'analyst-agent-task',
|
||||
{
|
||||
message_id: actualMessageId,
|
||||
},
|
||||
{ message_id: actualMessageId },
|
||||
{
|
||||
concurrencyKey: chatId, // Ensure sequential processing per chat
|
||||
}
|
||||
|
|
|
@ -98,6 +98,13 @@ describe('getDashboardHandler', () => {
|
|||
workspaceSharing: 'none',
|
||||
};
|
||||
|
||||
// Mock Hono context
|
||||
const mockContext = {
|
||||
env: {},
|
||||
get: vi.fn(),
|
||||
set: vi.fn(),
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
|
@ -163,7 +170,11 @@ describe('getDashboardHandler', () => {
|
|||
|
||||
describe('successful requests', () => {
|
||||
it('should return dashboard data for valid dashboard ID', async () => {
|
||||
const result = await getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser);
|
||||
const result = await getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123' },
|
||||
mockUser,
|
||||
mockContext
|
||||
);
|
||||
|
||||
expect(result.dashboard.id).toBe('dashboard-123');
|
||||
expect(result.dashboard.name).toBe('Test Dashboard');
|
||||
|
@ -206,7 +217,8 @@ describe('getDashboardHandler', () => {
|
|||
|
||||
const result = await getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123', versionNumber: 1 },
|
||||
mockUser
|
||||
mockUser,
|
||||
mockContext
|
||||
);
|
||||
|
||||
expect((result.dashboard.config as any).name).toBe('Version 1');
|
||||
|
@ -227,7 +239,8 @@ describe('getDashboardHandler', () => {
|
|||
|
||||
const result = await getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123', password: 'secret123' },
|
||||
mockUser
|
||||
mockUser,
|
||||
mockContext
|
||||
);
|
||||
|
||||
expect(result.permission).toBe('can_view');
|
||||
|
@ -239,7 +252,7 @@ describe('getDashboardHandler', () => {
|
|||
mockGetDashboardById.mockResolvedValue(null);
|
||||
|
||||
await expect(
|
||||
getDashboardHandler({ dashboardId: 'nonexistent-dashboard' }, mockUser)
|
||||
getDashboardHandler({ dashboardId: 'nonexistent-dashboard' }, mockUser, mockContext)
|
||||
).rejects.toThrow(new HTTPException(404, { message: 'Dashboard not found' }));
|
||||
});
|
||||
|
||||
|
@ -254,7 +267,9 @@ describe('getDashboardHandler', () => {
|
|||
effectiveRole: undefined,
|
||||
});
|
||||
|
||||
await expect(getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser)).rejects.toThrow(
|
||||
await expect(
|
||||
getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser, mockContext)
|
||||
).rejects.toThrow(
|
||||
new HTTPException(403, { message: "You don't have permission to view this dashboard" })
|
||||
);
|
||||
});
|
||||
|
@ -274,7 +289,9 @@ describe('getDashboardHandler', () => {
|
|||
effectiveRole: undefined,
|
||||
});
|
||||
|
||||
await expect(getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser)).rejects.toThrow(
|
||||
await expect(
|
||||
getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser, mockContext)
|
||||
).rejects.toThrow(
|
||||
new HTTPException(403, { message: 'Public access to this dashboard has expired' })
|
||||
);
|
||||
});
|
||||
|
@ -291,9 +308,9 @@ describe('getDashboardHandler', () => {
|
|||
effectiveRole: undefined,
|
||||
});
|
||||
|
||||
await expect(getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser)).rejects.toThrow(
|
||||
new HTTPException(418, { message: 'Password required for public access' })
|
||||
);
|
||||
await expect(
|
||||
getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser, mockContext)
|
||||
).rejects.toThrow(new HTTPException(418, { message: 'Password required for public access' }));
|
||||
});
|
||||
|
||||
it('should throw 403 when incorrect password is provided', async () => {
|
||||
|
@ -309,7 +326,11 @@ describe('getDashboardHandler', () => {
|
|||
});
|
||||
|
||||
await expect(
|
||||
getDashboardHandler({ dashboardId: 'dashboard-123', password: 'wrong-password' }, mockUser)
|
||||
getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123', password: 'wrong-password' },
|
||||
mockUser,
|
||||
mockContext
|
||||
)
|
||||
).rejects.toThrow(
|
||||
new HTTPException(403, { message: 'Incorrect password for public access' })
|
||||
);
|
||||
|
@ -317,7 +338,11 @@ describe('getDashboardHandler', () => {
|
|||
|
||||
it('should throw 404 when requested version does not exist', async () => {
|
||||
await expect(
|
||||
getDashboardHandler({ dashboardId: 'dashboard-123', versionNumber: 99 }, mockUser)
|
||||
getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123', versionNumber: 99 },
|
||||
mockUser,
|
||||
mockContext
|
||||
)
|
||||
).rejects.toThrow(new HTTPException(404, { message: 'Version 99 not found' }));
|
||||
});
|
||||
|
||||
|
@ -335,7 +360,11 @@ describe('getDashboardHandler', () => {
|
|||
mockGetDashboardById.mockResolvedValue(versionedDashboard);
|
||||
|
||||
await expect(
|
||||
getDashboardHandler({ dashboardId: 'dashboard-123', versionNumber: 1 }, mockUser)
|
||||
getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123', versionNumber: 1 },
|
||||
mockUser,
|
||||
mockContext
|
||||
)
|
||||
).rejects.toThrow(new HTTPException(404, { message: 'Version 1 not found' }));
|
||||
});
|
||||
});
|
||||
|
@ -364,7 +393,11 @@ describe('getDashboardHandler', () => {
|
|||
};
|
||||
mockGetDashboardById.mockResolvedValue(versionedDashboard);
|
||||
|
||||
const result = await getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser);
|
||||
const result = await getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123' },
|
||||
mockUser,
|
||||
mockContext
|
||||
);
|
||||
|
||||
expect(result.versions).toEqual([
|
||||
{ version_number: 1, updated_at: '2023-01-01T00:00:00Z' },
|
||||
|
@ -391,7 +424,11 @@ describe('getDashboardHandler', () => {
|
|||
};
|
||||
mockGetDashboardById.mockResolvedValue(versionedDashboard);
|
||||
|
||||
const result = await getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser);
|
||||
const result = await getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123' },
|
||||
mockUser,
|
||||
mockContext
|
||||
);
|
||||
|
||||
expect(result.dashboard.version_number).toBe(2);
|
||||
expect(result.dashboard.config).toEqual(mockDashboardContent); // From current content
|
||||
|
@ -405,7 +442,11 @@ describe('getDashboardHandler', () => {
|
|||
effectiveRole: 'can_edit',
|
||||
});
|
||||
|
||||
const result = await getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser);
|
||||
const result = await getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123' },
|
||||
mockUser,
|
||||
mockContext
|
||||
);
|
||||
|
||||
expect(result.permission).toBe('can_edit');
|
||||
});
|
||||
|
@ -421,7 +462,11 @@ describe('getDashboardHandler', () => {
|
|||
effectiveRole: undefined,
|
||||
});
|
||||
|
||||
const result = await getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser);
|
||||
const result = await getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123' },
|
||||
mockUser,
|
||||
mockContext
|
||||
);
|
||||
|
||||
expect(result.permission).toBe('can_view');
|
||||
});
|
||||
|
@ -429,7 +474,11 @@ describe('getDashboardHandler', () => {
|
|||
|
||||
describe('response structure', () => {
|
||||
it('should return properly formatted dashboard data', async () => {
|
||||
const result = await getDashboardHandler({ dashboardId: 'dashboard-123' }, mockUser);
|
||||
const result = await getDashboardHandler(
|
||||
{ dashboardId: 'dashboard-123' },
|
||||
mockUser,
|
||||
mockContext
|
||||
);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
dashboard: {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { screenshots_task_keys } from '@buster-app/trigger/task-keys';
|
||||
import type { TakeDashboardScreenshotTrigger } from '@buster-app/trigger/task-schemas';
|
||||
import { checkPermission } from '@buster/access-controls';
|
||||
import {
|
||||
type User,
|
||||
|
@ -14,7 +16,8 @@ import {
|
|||
import type { DashboardYml } from '@buster/server-shared/dashboards';
|
||||
import type { VerificationStatus } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { tasks } from '@trigger.dev/sdk';
|
||||
import { type Context, Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import yaml from 'js-yaml';
|
||||
import { throwUnauthorizedError } from '../../../../shared-helpers/asset-public-access';
|
||||
|
@ -46,7 +49,8 @@ const app = new Hono().get(
|
|||
versionNumber: version_number,
|
||||
password,
|
||||
},
|
||||
user
|
||||
user,
|
||||
c
|
||||
);
|
||||
|
||||
return c.json(response);
|
||||
|
@ -61,7 +65,8 @@ export default app;
|
|||
*/
|
||||
export async function getDashboardHandler(
|
||||
params: GetDashboardHandlerParams,
|
||||
user: User
|
||||
user: User,
|
||||
c: Context
|
||||
): Promise<GetDashboardResponse> {
|
||||
const { dashboardId, versionNumber, password } = params;
|
||||
|
||||
|
@ -241,6 +246,18 @@ export async function getDashboardHandler(
|
|||
workspace_member_count: workspaceMemberCount,
|
||||
};
|
||||
|
||||
await tasks.trigger(
|
||||
screenshots_task_keys.take_dashboard_screenshot,
|
||||
{
|
||||
dashboardId,
|
||||
isOnSaveEvent: false,
|
||||
organizationId: dashboardFile.organizationId,
|
||||
supabaseCookieKey: c.get('supabaseCookieKey'),
|
||||
supabaseUser: c.get('supabaseUser'),
|
||||
accessToken: c.get('accessToken'),
|
||||
} satisfies TakeDashboardScreenshotTrigger,
|
||||
{ concurrencyKey: `take-dashboard-screenshot-${dashboardId}-${versionNumber}` }
|
||||
);
|
||||
return response;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { checkPermission } from '@buster/access-controls';
|
||||
import { getDashboardById, getUserOrganizationId } from '@buster/database/queries';
|
||||
import { getDashboardById } from '@buster/database/queries';
|
||||
import {
|
||||
GetDashboardScreenshotParamsSchema,
|
||||
GetDashboardScreenshotQuerySchema,
|
||||
|
@ -48,7 +48,7 @@ const app = new Hono()
|
|||
dashboardId,
|
||||
supabaseCookieKey: c.get('supabaseCookieKey'),
|
||||
supabaseUser: c.get('supabaseUser'),
|
||||
accessToken: c.get('accessToken') || '',
|
||||
accessToken: c.get('accessToken'),
|
||||
organizationId: dashboard.organizationId,
|
||||
});
|
||||
|
||||
|
|
|
@ -10,14 +10,15 @@ import type { User } from '@buster/database/queries';
|
|||
import type { GetDashboardResponse } from '@buster/server-shared/dashboards';
|
||||
import { type ShareUpdateRequest, ShareUpdateRequestSchema } from '@buster/server-shared/share';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { Hono } from 'hono';
|
||||
import { type Context, Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { getDashboardHandler } from '../GET';
|
||||
|
||||
export async function updateDashboardShareHandler(
|
||||
dashboardId: string,
|
||||
request: ShareUpdateRequest,
|
||||
user: User & { organizationId: string }
|
||||
user: User & { organizationId: string },
|
||||
c: Context
|
||||
) {
|
||||
// Check if dashboard exists
|
||||
const dashboard = await getDashboardById({ dashboardId });
|
||||
|
@ -104,7 +105,11 @@ export async function updateDashboardShareHandler(
|
|||
workspace_sharing,
|
||||
});
|
||||
|
||||
const updatedDashboard: GetDashboardResponse = await getDashboardHandler({ dashboardId }, user);
|
||||
const updatedDashboard: GetDashboardResponse = await getDashboardHandler(
|
||||
{ dashboardId },
|
||||
user,
|
||||
c
|
||||
);
|
||||
|
||||
return updatedDashboard;
|
||||
}
|
||||
|
@ -130,7 +135,8 @@ const app = new Hono().put('/', zValidator('json', ShareUpdateRequestSchema), as
|
|||
{
|
||||
...user,
|
||||
organizationId: userOrg.organizationId,
|
||||
}
|
||||
},
|
||||
c
|
||||
);
|
||||
|
||||
return c.json(updatedDashboard);
|
||||
|
|
|
@ -27,20 +27,20 @@ const app = new Hono()
|
|||
password
|
||||
);
|
||||
|
||||
const organizationId =
|
||||
(await getUserOrganizationId(user.id).then((res) => res?.organizationId)) || '';
|
||||
|
||||
await tasks.trigger(
|
||||
screenshots_task_keys.take_metric_screenshot,
|
||||
{
|
||||
metricId: id,
|
||||
isOnSaveEvent: true,
|
||||
isOnSaveEvent: false,
|
||||
supabaseCookieKey: c.get('supabaseCookieKey'),
|
||||
supabaseUser: c.get('supabaseUser'),
|
||||
accessToken: c.get('accessToken') || '',
|
||||
organizationId:
|
||||
(await getUserOrganizationId(user.id).then((res) => res?.organizationId)) || '',
|
||||
accessToken: c.get('accessToken'),
|
||||
organizationId,
|
||||
} satisfies TakeMetricScreenshotTrigger,
|
||||
{
|
||||
idempotencyKey: `take-metric-screenshot-${id}`,
|
||||
}
|
||||
{ concurrencyKey: `take-metric-screenshot-${id}-${version_number}` }
|
||||
);
|
||||
|
||||
return c.json(response);
|
||||
|
|
|
@ -50,7 +50,7 @@ const app = new Hono()
|
|||
type,
|
||||
supabaseCookieKey: c.get('supabaseCookieKey'),
|
||||
supabaseUser: c.get('supabaseUser'),
|
||||
accessToken: c.get('accessToken') || '',
|
||||
accessToken: c.get('accessToken'),
|
||||
organizationId: metric.organizationId,
|
||||
});
|
||||
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import { screenshots_task_keys } from '@buster-app/trigger/task-keys';
|
||||
import { TakeReportScreenshotTrigger } from '@buster-app/trigger/task-schemas';
|
||||
import { checkPermission } from '@buster/access-controls';
|
||||
import { type User, getMetricIdsInReport, getReportFileById } from '@buster/database/queries';
|
||||
import {
|
||||
type User,
|
||||
getMetricIdsInReport,
|
||||
getReportFileById,
|
||||
getUserOrganizationId,
|
||||
} from '@buster/database/queries';
|
||||
import {
|
||||
GetReportParamsSchema,
|
||||
GetReportQuerySchema,
|
||||
type GetReportResponse,
|
||||
} from '@buster/server-shared/reports';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { tasks } from '@trigger.dev/sdk';
|
||||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { throwUnauthorizedError } from '../../../../shared-helpers/asset-public-access';
|
||||
|
@ -81,6 +89,19 @@ const app = new Hono()
|
|||
versionNumber,
|
||||
password
|
||||
);
|
||||
|
||||
await tasks.trigger(
|
||||
screenshots_task_keys.take_report_screenshot,
|
||||
{
|
||||
reportId,
|
||||
organizationId: (await getUserOrganizationId(user.id))?.organizationId || '',
|
||||
supabaseCookieKey: c.get('supabaseCookieKey'),
|
||||
supabaseUser: c.get('supabaseUser'),
|
||||
accessToken: c.get('accessToken'),
|
||||
} satisfies TakeReportScreenshotTrigger,
|
||||
{ concurrencyKey: `take-report-screenshot-${reportId}-${versionNumber}` }
|
||||
);
|
||||
|
||||
return c.json(response);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -41,6 +41,6 @@ declare module 'hono' {
|
|||
/**
|
||||
* The access token for the user. Set by the requireAuth middleware.
|
||||
*/
|
||||
readonly accessToken?: string;
|
||||
readonly accessToken: string;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,3 +3,18 @@ export {
|
|||
TakeMetricScreenshotTriggerSchema,
|
||||
type TakeMetricScreenshotTrigger,
|
||||
} from './tasks/screenshots/schemas';
|
||||
|
||||
export {
|
||||
TakeDashboardScreenshotTriggerSchema,
|
||||
type TakeDashboardScreenshotTrigger,
|
||||
} from './tasks/screenshots/schemas';
|
||||
|
||||
export {
|
||||
TakeReportScreenshotTriggerSchema,
|
||||
type TakeReportScreenshotTrigger,
|
||||
} from './tasks/screenshots/schemas';
|
||||
|
||||
export {
|
||||
TakeChartScreenshotTriggerSchema,
|
||||
type TakeChartScreenshotTrigger,
|
||||
} from './tasks/screenshots/schemas';
|
||||
|
|
Loading…
Reference in New Issue