diff --git a/packages/ai/src/tools/visualization-tools/metrics/helpers/metric-tool-description.txt b/packages/ai/src/tools/visualization-tools/metrics/helpers/metric-tool-description.txt index 23954e613..9460411f8 100644 --- a/packages/ai/src/tools/visualization-tools/metrics/helpers/metric-tool-description.txt +++ b/packages/ai/src/tools/visualization-tools/metrics/helpers/metric-tool-description.txt @@ -104,7 +104,7 @@ Only utilize the required/default fields unless the user specifically requests t # style: percent # replaceMissingDataWith: 0 # numberSeparatorStyle: ',' -# multiplier: 100 # if calculated and not already multiplied +# multiplier: 100 # Use 100 if SQL returns 0.75 for 75%; use 1 if SQL returns 75 for 75% # minimumFractionDigits: 1 # maximumFractionDigits: 2 # ------------------------------------- @@ -348,15 +348,21 @@ definitions: type: string enum: - currency # Note: The "$" sign is automatically prepended. - - percent # Note: "%" sign is appended. For percentage values: - # - If the value comes directly from a database column, use multiplier: 1 - # - If the value is calculated in your SQL query and not already multiplied by 100, use multiplier: 100 + - percent # Note: "%" sign is automatically appended as a suffix. + # IMPORTANT: You MUST analyze the SQL query to determine the correct multiplier: + # - If SQL returns decimal ratios (e.g., 0.75 for 75%), use multiplier: 100 + # - If SQL returns percentage values (e.g., 75 for 75%), use multiplier: 1 (or omit it) - number - date # Note: For date columns, consider setting xAxisTimeInterval in xAxisConfig to control date grouping (day, week, month, quarter, year) - string multiplier: type: number - description: Value to multiply the number by before display. Default value is 1. For percentages, the multiplier depends on how the data is sourced: if the value comes directly from a database column, use multiplier: 1; if the value is calculated in your SQL query and not already multiplied by 100, use multiplier: 100. + description: | + Value to multiply the number by before display. Default: 1. + For percentages with style: percent, you MUST examine the SQL query and data format: + - If SQL returns decimal ratios (e.g., 0.75), use multiplier: 100 to convert to percentage (75%) + - If SQL returns percentage values (e.g., 75), use multiplier: 1 or omit this field + The agent must analyze the actual SQL query and understand the data format, not make assumptions. displayName: type: string description: Custom display name for the column diff --git a/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-delta.ts b/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-delta.ts index b07135db8..ab5665440 100644 --- a/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-delta.ts +++ b/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-delta.ts @@ -312,35 +312,39 @@ export function createModifyReportsDelta(context: ModifyReportsContext, state: M }, }; - // Update the database with the result of all edits using Promise chain + // Update the database with the result of all edits (concurrent, not chained) try { - // Chain this write to ensure sequential execution - state.lastDbWritePromise = (async () => { - // Wait for any previous write to complete - if (state.lastDbWritePromise) { - try { - await state.lastDbWritePromise; - } catch (error) { - // Previous write failed, but we continue - console.warn('[modify-reports-delta] Previous write failed:', error); - } - } + // Initialize pending writes array if needed + if (!state.pendingDbWrites) { + state.pendingDbWrites = []; + } - // Now do our write - // We're already inside a check for state.reportId being defined - if (!state.reportId) { - throw new Error('Report ID is unexpectedly undefined'); - } - return batchUpdateReport({ - reportId: state.reportId, - content: currentContent, - name: state.reportName || undefined, - versionHistory, + // We're already inside a check for state.reportId being defined + if (!state.reportId) { + throw new Error('Report ID is unexpectedly undefined'); + } + + // Create the write promise and add to pending writes + const writePromise = batchUpdateReport({ + reportId: state.reportId, + content: currentContent, + name: state.reportName || undefined, + versionHistory, + }) + .then(() => { + // Convert to void to match the array type + return; + }) + .catch((error) => { + console.error('[modify-reports-delta] Database write failed:', error); + throw error; }); - })(); - // Await the promise to handle errors and ensure completion - await state.lastDbWritePromise; + // Add to pending writes array + state.pendingDbWrites.push(writePromise); + + // Await this specific write to handle errors + await writePromise; // No cache update during delta - execute will handle write-through diff --git a/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-execute.ts b/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-execute.ts index 2010f99b1..361291c0d 100644 --- a/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-execute.ts +++ b/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-execute.ts @@ -177,16 +177,22 @@ async function processEditOperations( // Write all changes to database in one operation try { - // Wait for any pending delta writes to complete before doing final update - if (state?.lastDbWritePromise) { - console.info('[modify-reports-execute] Waiting for pending delta writes to complete'); + // Wait for ALL pending delta writes to complete before doing final update + if (state?.pendingDbWrites && state.pendingDbWrites.length > 0) { + console.info( + `[modify-reports-execute] Waiting for ${state.pendingDbWrites.length} pending delta writes to complete` + ); try { - await state.lastDbWritePromise; + // Wait for all writes to complete (some may fail, that's OK) + await Promise.allSettled(state.pendingDbWrites); // Add small delay to ensure we're absolutely last await new Promise((resolve) => setTimeout(resolve, 50)); + console.info( + '[modify-reports-execute] All delta writes completed, proceeding with final update' + ); } catch (error) { console.warn( - '[modify-reports-execute] Delta write failed, proceeding with final update:', + '[modify-reports-execute] Error waiting for delta writes, proceeding with final update:', error ); } diff --git a/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-tool.ts b/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-tool.ts index 731287246..a4f78bb50 100644 --- a/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-tool.ts +++ b/packages/ai/src/tools/visualization-tools/reports/modify-reports-tool/modify-reports-tool.ts @@ -103,9 +103,9 @@ export type ModifyReportsOutput = z.infer; export type ModifyReportsContext = z.infer; export type ModifyReportsEditState = z.infer; -// Extend the inferred type to include Promise field (not supported by Zod directly) +// Extend the inferred type to include Promise fields (not supported by Zod directly) export type ModifyReportsState = z.infer & { - lastDbWritePromise?: Promise; + pendingDbWrites?: Promise[]; // Track all pending writes }; // Factory function that accepts agent context and maps to tool context diff --git a/packages/database/biome.json b/packages/database/biome.json index 726c0c97f..5c3aacb7b 100644 --- a/packages/database/biome.json +++ b/packages/database/biome.json @@ -2,7 +2,8 @@ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", "extends": ["../../biome.json"], "files": { - "include": ["src/**/*", "scripts/**/*"] + "include": ["src/**/*"], + "ignore": ["scripts/**/*"] }, "overrides": [ { diff --git a/packages/database/src/connection.ts b/packages/database/src/connection.ts index f816e63ac..096b35536 100644 --- a/packages/database/src/connection.ts +++ b/packages/database/src/connection.ts @@ -60,29 +60,12 @@ export function initializePool>( } // Create postgres client with pool configuration - // Disable SSL for local development - const isDevelopment = - process.env.ENVIRONMENT === 'development' || process.env.NODE_ENV === 'development'; - - console.log( - 'Database connection - ENVIRONMENT:', - process.env.ENVIRONMENT, - 'NODE_ENV:', - process.env.NODE_ENV, - 'isDevelopment:', - isDevelopment - ); - + // SSL is controlled via the connection string (e.g., ?sslmode=require) globalPool = postgres(connectionString, { max: poolSize, idle_timeout: 30, connect_timeout: 30, prepare: true, - ssl: isDevelopment - ? false - : { - rejectUnauthorized: false, // Allow self-signed certificates - }, ...config, }); diff --git a/packages/database/src/queries/cascading-permissions/check-dashboards-containing-metric.test.ts b/packages/database/src/queries/cascading-permissions/check-dashboards-containing-metric.test.ts index addc6ac45..a1c8c10dd 100644 --- a/packages/database/src/queries/cascading-permissions/check-dashboards-containing-metric.test.ts +++ b/packages/database/src/queries/cascading-permissions/check-dashboards-containing-metric.test.ts @@ -10,7 +10,7 @@ vi.mock('../../connection', () => ({ // Mock drizzle-orm vi.mock('drizzle-orm', async (importOriginal) => { - const actual = (await importOriginal()) as any; + const actual = (await importOriginal()) as Record; return { ...actual, sql: actual.sql, diff --git a/packages/database/src/queries/cascading-permissions/check-reports-containing-metric.test.ts b/packages/database/src/queries/cascading-permissions/check-reports-containing-metric.test.ts index 8c3ecbf19..1b83022e4 100644 --- a/packages/database/src/queries/cascading-permissions/check-reports-containing-metric.test.ts +++ b/packages/database/src/queries/cascading-permissions/check-reports-containing-metric.test.ts @@ -10,7 +10,7 @@ vi.mock('../../connection', () => ({ // Mock drizzle-orm vi.mock('drizzle-orm', async (importOriginal) => { - const actual = (await importOriginal()) as any; + const actual = (await importOriginal()) as Record; return { ...actual, sql: actual.sql, diff --git a/packages/database/src/queries/docs/get-organization-docs.test.ts b/packages/database/src/queries/docs/get-organization-docs.test.ts index 8041d9c53..68eda7d01 100644 --- a/packages/database/src/queries/docs/get-organization-docs.test.ts +++ b/packages/database/src/queries/docs/get-organization-docs.test.ts @@ -82,10 +82,10 @@ describe('getOrganizationDocs', () => { organizationId: '123e4567-e89b-12d3-a456-426614174000', }; - let mockSelect: any; - let mockFrom: any; - let mockWhere: any; - let mockOrderBy: any; + let mockSelect: ReturnType; + let mockFrom: ReturnType; + let mockWhere: ReturnType; + let mockOrderBy: ReturnType; beforeEach(() => { vi.clearAllMocks(); diff --git a/packages/database/src/queries/docs/upsert-doc.test.ts b/packages/database/src/queries/docs/upsert-doc.test.ts index e5aa68929..c48bcb326 100644 --- a/packages/database/src/queries/docs/upsert-doc.test.ts +++ b/packages/database/src/queries/docs/upsert-doc.test.ts @@ -124,10 +124,10 @@ describe('upsertDoc', () => { organizationId: '123e4567-e89b-12d3-a456-426614174000', }; - let mockInsert: any; - let mockValues: any; - let mockOnConflictDoUpdate: any; - let mockReturning: any; + let mockInsert: ReturnType; + let mockValues: ReturnType; + let mockOnConflictDoUpdate: ReturnType; + let mockReturning: ReturnType; beforeEach(() => { vi.clearAllMocks(); diff --git a/packages/database/src/queries/shortcuts/create-shortcut.ts b/packages/database/src/queries/shortcuts/create-shortcut.ts index 2d5a4feb6..0be571c4d 100644 --- a/packages/database/src/queries/shortcuts/create-shortcut.ts +++ b/packages/database/src/queries/shortcuts/create-shortcut.ts @@ -87,9 +87,10 @@ export async function createShortcut(input: CreateShortcutInput) { .returning(); return created; - } catch (error: any) { + } catch (error: unknown) { // Handle unique constraint violation - if (error?.code === '23505' && error?.constraint?.includes('unique')) { + const dbError = error as { code?: string; constraint?: string }; + if (dbError?.code === '23505' && dbError?.constraint?.includes('unique')) { throw new Error(`Shortcut with name '${validated.name}' already exists`); } throw error; diff --git a/packages/database/src/queries/shortcuts/update-shortcut.ts b/packages/database/src/queries/shortcuts/update-shortcut.ts index 7f3b209eb..7c54a694a 100644 --- a/packages/database/src/queries/shortcuts/update-shortcut.ts +++ b/packages/database/src/queries/shortcuts/update-shortcut.ts @@ -16,7 +16,7 @@ export type UpdateShortcutInput = z.infer; export async function updateShortcut(input: UpdateShortcutInput) { const validated = UpdateShortcutInputSchema.parse(input); - const updateData: Record = { + const updateData: Record = { updatedBy: validated.updatedBy, updatedAt: new Date().toISOString(), };