diff --git a/apps/web/package.json b/apps/web/package.json index f157917a9..536646270 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,6 +12,7 @@ "typecheck": "tsc --noEmit", "typecheck:watch": "tsc --noEmit --watch", "test": "vitest run", + "test:unit": "pnpm run test", "test:watch": "vitest --watch", "test:ui": "vitest --ui", "test:coverage": "vitest --coverage", diff --git a/apps/web/src/api/buster_rest/chats/queryRequests.test.tsx b/apps/web/src/api/buster_rest/chats/queryRequests.test.tsx index ed014e6dc..dee6bf8f7 100644 --- a/apps/web/src/api/buster_rest/chats/queryRequests.test.tsx +++ b/apps/web/src/api/buster_rest/chats/queryRequests.test.tsx @@ -91,7 +91,7 @@ describe('Chat Query Hooks', () => { expect(requests.getListChats).toHaveBeenCalledWith({ admin_view: false, page_token: 0, - page_size: 3500, + page_size: 5000, search: 'test' }); }); diff --git a/apps/web/src/context/Supabase/SupabaseContextProvider.tsx b/apps/web/src/context/Supabase/SupabaseContextProvider.tsx index 78f6336b0..106a7f62b 100644 --- a/apps/web/src/context/Supabase/SupabaseContextProvider.tsx +++ b/apps/web/src/context/Supabase/SupabaseContextProvider.tsx @@ -9,10 +9,10 @@ import type { UseSupabaseUserContextType } from '@/lib/supabase'; import { timeout } from '@/lib/timeout'; import { useBusterNotifications } from '../BusterNotifications'; import { flushSync } from 'react-dom'; -import { createClient } from '@/lib/supabase/client'; +import { createBrowserClient } from '@/lib/supabase/client'; const PREEMTIVE_REFRESH_MINUTES = 5; -const supabase = createClient(); +const supabase = createBrowserClient(); const useSupabaseContextInternal = ({ supabaseContext diff --git a/apps/web/src/lib/supabase/client.ts b/apps/web/src/lib/supabase/client.ts index c5a9f5abe..a738bb2cf 100644 --- a/apps/web/src/lib/supabase/client.ts +++ b/apps/web/src/lib/supabase/client.ts @@ -1,14 +1,14 @@ 'use client'; -import { createBrowserClient } from '@supabase/ssr'; +import { createBrowserClient as createBrowserClientSSR } from '@supabase/ssr'; -export function createClient() { +export function createBrowserClient() { const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; if (!supabaseUrl || !supabaseAnonKey) { - throw new Error('Missing Supabase environment variables'); + throw new Error('Missing Supabase environment variables for browser client'); } - return createBrowserClient(supabaseUrl, supabaseAnonKey); + return createBrowserClientSSR(supabaseUrl, supabaseAnonKey); } diff --git a/apps/web/src/lib/supabase/server.ts b/apps/web/src/lib/supabase/server.ts index b1b9a708b..225f6d8c1 100644 --- a/apps/web/src/lib/supabase/server.ts +++ b/apps/web/src/lib/supabase/server.ts @@ -15,7 +15,7 @@ export async function createSupabaseServerClient() { const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; if (!supabaseUrl || !supabaseAnonKey) { - throw new Error('Missing Supabase environment variables'); + throw new Error('Missing Supabase environment variables for server client'); } const cookieStore = await cookies(); diff --git a/apps/web/src/routes/busterRoutes/assetPageChecks.ts b/apps/web/src/routes/busterRoutes/assetPageChecks.ts index 017ffe2f5..db2b31cab 100644 --- a/apps/web/src/routes/busterRoutes/assetPageChecks.ts +++ b/apps/web/src/routes/busterRoutes/assetPageChecks.ts @@ -1,7 +1,7 @@ import type { NextRequest } from 'next/server'; import { BusterAuthRoutes } from './busterAuthRoutes'; import { BusterEmbedRoutes } from './busterEmbedRoutes'; -import { BusterRoutes } from './busterRoutes'; +import { BusterRoutes, type BusterRoutesWithArgsRoute } from './busterRoutes'; import { createBusterRoute, createPathnameToBusterRoute, @@ -100,6 +100,7 @@ export const getEmbedAssetToRegularAsset = (pathnameAndQueryParams: string) => { if (matched) { const params = extractPathParamsFromRoute(pathnameAndQueryParams); - return createBusterRoute({ route: matched, ...(params as any) }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- I am just using any here because it was a pain to type this out + return createBusterRoute({ route: matched as BusterRoutes, ...(params as any) }); } }; diff --git a/apps/web/vitest.setup.ts b/apps/web/vitest.setup.ts index a5860ed09..0176c8628 100644 --- a/apps/web/vitest.setup.ts +++ b/apps/web/vitest.setup.ts @@ -59,3 +59,24 @@ vi.mock('remark-gfm', () => ({ __esModule: true, default: vi.fn() })); + +// Mock Supabase client to prevent environment variable errors in tests +vi.mock('@/lib/supabase/client', () => ({ + createBrowserClient: vi.fn(() => ({ + auth: { + refreshSession: vi.fn().mockResolvedValue({ + data: { + session: { + access_token: 'mock-token', + expires_at: Date.now() / 1000 + 3600 // 1 hour from now + } + }, + error: null + }), + getUser: vi.fn().mockResolvedValue({ + data: { user: null }, + error: null + }) + } + })) +})); diff --git a/package.json b/package.json index f983d016d..b9287a634 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,9 @@ "format": "biome format ${1:-.}", "format:fix": "biome format --write ${1:-.}", "lint": "turbo lint", - "new:package": "bun run scripts/new-package.ts", + "new:package": "tsx scripts/new-package.ts", "setup": "bash scripts/setup.sh", + "test-before-pr": "turbo run test:unit build lint", "test": "dotenv -e .env -- turbo test", "test:unit": "dotenv -e .env -- turbo run test:unit", "test:integration": "dotenv -e .env -- turbo run test:integration", diff --git a/packages/ai/src/tools/visualization-tools/modify-dashboards-file-tool.ts b/packages/ai/src/tools/visualization-tools/modify-dashboards-file-tool.ts index 31fb92897..580970c6a 100644 --- a/packages/ai/src/tools/visualization-tools/modify-dashboards-file-tool.ts +++ b/packages/ai/src/tools/visualization-tools/modify-dashboards-file-tool.ts @@ -371,8 +371,10 @@ const modifyDashboardFiles = wrapTraced( for (const file of dashboardFilesToUpdate) { // Get current metric IDs from updated dashboard content - const newMetricIds = (file.content as DashboardYml).rows.flatMap(row => row.items).map(item => item.id); - + const newMetricIds = (file.content as DashboardYml).rows + .flatMap((row) => row.items) + .map((item) => item.id); + const existingAssociations = await tx .select({ metricFileId: metricFilesToDashboardFiles.metricFileId }) .from(metricFilesToDashboardFiles) @@ -383,10 +385,12 @@ const modifyDashboardFiles = wrapTraced( ) ) .execute(); - - const existingMetricIds = existingAssociations.map(a => a.metricFileId); - - const addedMetricIds = newMetricIds.filter((id: string) => !existingMetricIds.includes(id)); + + const existingMetricIds = existingAssociations.map((a) => a.metricFileId); + + const addedMetricIds = newMetricIds.filter( + (id: string) => !existingMetricIds.includes(id) + ); for (const metricId of addedMetricIds) { await tx .insert(metricFilesToDashboardFiles) @@ -396,25 +400,30 @@ const modifyDashboardFiles = wrapTraced( createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), deletedAt: null, - createdBy: userId + createdBy: userId, }) .onConflictDoUpdate({ - target: [metricFilesToDashboardFiles.metricFileId, metricFilesToDashboardFiles.dashboardFileId], + target: [ + metricFilesToDashboardFiles.metricFileId, + metricFilesToDashboardFiles.dashboardFileId, + ], set: { deletedAt: null, - updatedAt: new Date().toISOString() - } + updatedAt: new Date().toISOString(), + }, }) .execute(); } - - const removedMetricIds = existingMetricIds.filter((id: string) => !newMetricIds.includes(id)); + + const removedMetricIds = existingMetricIds.filter( + (id: string) => !newMetricIds.includes(id) + ); if (removedMetricIds.length > 0) { await tx .update(metricFilesToDashboardFiles) .set({ deletedAt: new Date().toISOString(), - updatedAt: new Date().toISOString() + updatedAt: new Date().toISOString(), }) .where( and( diff --git a/packages/ai/src/utils/retry/retry-mechanism.int.test.ts b/packages/ai/src/utils/retry/retry-mechanism.int.test.ts index fab4c3288..c49b4d06b 100644 --- a/packages/ai/src/utils/retry/retry-mechanism.int.test.ts +++ b/packages/ai/src/utils/retry/retry-mechanism.int.test.ts @@ -220,7 +220,9 @@ describe('Retry Mechanism Integration Tests', () => { // The conversation should start with the original messages const userMessages = result.conversationHistory.filter((msg) => msg.role === 'user'); - const assistantMessages = result.conversationHistory.filter((msg) => msg.role === 'assistant'); + const assistantMessages = result.conversationHistory.filter( + (msg) => msg.role === 'assistant' + ); expect(userMessages.length).toBeGreaterThan(0); expect(assistantMessages.length).toBeGreaterThan(0); diff --git a/scripts/test-before-pr.ts b/scripts/test-before-pr.ts new file mode 100755 index 000000000..561aed7fc --- /dev/null +++ b/scripts/test-before-pr.ts @@ -0,0 +1,120 @@ +#!/usr/bin/env tsx + +import { execSync } from 'node:child_process'; +import { performance } from 'node:perf_hooks'; + +interface Step { + name: string; + command: string; + emoji: string; + description: string; +} + +const steps: Step[] = [ + { + name: 'lint', + command: 'turbo run lint --ui=stream', + emoji: '๐Ÿ”', + description: 'Running linter checks' + }, + { + name: 'build', + command: 'turbo run build --ui=stream', + emoji: '๐Ÿ—๏ธ', + description: 'Building all packages' + }, + { + name: 'test', + command: 'turbo run test', + emoji: '๐Ÿงช', + description: 'Running all tests' + } +]; + +function formatTime(ms: number): string { + if (ms < 1000) { + return `${Math.round(ms)}ms`; + } + const seconds = ms / 1000; + if (seconds < 60) { + return `${seconds.toFixed(1)}s`; + } + const minutes = Math.floor(seconds / 60); + const remainingSeconds = Math.floor(seconds % 60); + return `${minutes}m ${remainingSeconds}s`; +} + +function runStep(step: Step, stepNumber: number, totalSteps: number): boolean { + console.log(`\n${step.emoji} [${stepNumber}/${totalSteps}] ${step.description}...`); + console.log(`๐Ÿ“ Command: ${step.command}`); + + const startTime = performance.now(); + + try { + execSync(step.command, { + stdio: 'inherit', + cwd: process.cwd() + }); + + const endTime = performance.now(); + const duration = formatTime(endTime - startTime); + + console.log(`โœ… ${step.name} completed successfully in ${duration}`); + return true; + } catch (error) { + const endTime = performance.now(); + const duration = formatTime(endTime - startTime); + + console.log(`โŒ ${step.name} failed after ${duration}`); + console.error(`๐Ÿ’ฅ Error in ${step.name}:`, error); + return false; + } +} + +function main() { + console.log('๐Ÿš€ Starting pre-PR checks...\n'); + console.log('๐ŸŽฏ This will run: lint โ†’ build โ†’ test'); + console.log('โฑ๏ธ Grab a coffee, this might take a while!\n'); + console.log('โ•'.repeat(50)); + + const overallStartTime = performance.now(); + let failedSteps: string[] = []; + + for (let i = 0; i < steps.length; i++) { + const step = steps[i]; + const success = runStep(step, i + 1, steps.length); + + if (!success) { + failedSteps.push(step.name); + break; // Stop on first failure + } + } + + const overallEndTime = performance.now(); + const totalDuration = formatTime(overallEndTime - overallStartTime); + + console.log('\n' + 'โ•'.repeat(50)); + + if (failedSteps.length === 0) { + console.log('๐ŸŽ‰ All pre-PR checks passed!'); + console.log('โœจ Your code is ready for review!'); + console.log(`โฐ Total time: ${totalDuration}`); + console.log('๐Ÿšข Safe to create that PR! ๐Ÿšข'); + process.exit(0); + } else { + console.log('๐Ÿ’ฅ Pre-PR checks failed!'); + console.log(`โŒ Failed steps: ${failedSteps.join(', ')}`); + console.log(`โฐ Total time: ${totalDuration}`); + console.log('๐Ÿ”ง Please fix the issues above before creating a PR.'); + process.exit(1); + } +} + +// Handle Ctrl+C gracefully +process.on('SIGINT', () => { + console.log('\n\n๐Ÿ›‘ Pre-PR checks interrupted by user'); + console.log('๐Ÿ‘‹ See you next time!'); + process.exit(130); +}); + +main(); \ No newline at end of file