report final update fix

This commit is contained in:
dal 2025-09-22 12:59:59 -06:00
commit 0a430bef5b
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
12 changed files with 70 additions and 69 deletions

View File

@ -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

View File

@ -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

View File

@ -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
);
}

View File

@ -103,9 +103,9 @@ export type ModifyReportsOutput = z.infer<typeof ModifyReportsOutputSchema>;
export type ModifyReportsContext = z.infer<typeof ModifyReportsContextSchema>;
export type ModifyReportsEditState = z.infer<typeof ModifyReportsEditStateSchema>;
// 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<typeof ModifyReportsStateSchema> & {
lastDbWritePromise?: Promise<void>;
pendingDbWrites?: Promise<void>[]; // Track all pending writes
};
// Factory function that accepts agent context and maps to tool context

View File

@ -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": [
{

View File

@ -60,29 +60,12 @@ export function initializePool<T extends Record<string, postgres.PostgresType>>(
}
// 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,
});

View File

@ -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<string, unknown>;
return {
...actual,
sql: actual.sql,

View File

@ -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<string, unknown>;
return {
...actual,
sql: actual.sql,

View File

@ -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<typeof vi.fn>;
let mockFrom: ReturnType<typeof vi.fn>;
let mockWhere: ReturnType<typeof vi.fn>;
let mockOrderBy: ReturnType<typeof vi.fn>;
beforeEach(() => {
vi.clearAllMocks();

View File

@ -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<typeof vi.fn>;
let mockValues: ReturnType<typeof vi.fn>;
let mockOnConflictDoUpdate: ReturnType<typeof vi.fn>;
let mockReturning: ReturnType<typeof vi.fn>;
beforeEach(() => {
vi.clearAllMocks();

View File

@ -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;

View File

@ -16,7 +16,7 @@ export type UpdateShortcutInput = z.infer<typeof UpdateShortcutInputSchema>;
export async function updateShortcut(input: UpdateShortcutInput) {
const validated = UpdateShortcutInputSchema.parse(input);
const updateData: Record<string, any> = {
const updateData: Record<string, unknown> = {
updatedBy: validated.updatedBy,
updatedAt: new Date().toISOString(),
};