feat(scheduled-tasks): database query DSL trigger#9
Conversation
Run database.query on schedule and notify when rows or aggregate groups match. Settings UI adds Query trigger mode with DSL editor and preview. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Code Review
This pull request introduces support for database query triggers in scheduled tasks, allowing users to run tasks based on custom DSL queries (including aggregate queries). It adds the necessary backend execution logic, UI components for query configuration and previewing, and comprehensive unit tests. The review feedback highlights a few key improvements: ensuring the aggregate query deduplication hash includes actual row data rather than just the group count, safely handling potentially missing name attributes in matched rows, and falling back to an empty string when a database selection is cleared in the UI to prevent type mismatches.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| resultHash: JSON.stringify({ | ||
| type: check.type, | ||
| databaseId: check.databaseId, | ||
| dsl: check.dsl, | ||
| mode: 'aggregated', | ||
| groupCount, | ||
| }), |
There was a problem hiding this comment.
Using only groupCount in the resultHash for aggregate queries means that if the aggregate values or group keys change but the total number of groups remains the same, the hash will not change. As a result, deduplication will prevent notifications from being sent even when the underlying data has changed. Including the actual result.rows in the hash ensures that any changes to the aggregated data are correctly detected.
resultHash: JSON.stringify({
type: check.type,
databaseId: check.databaseId,
dsl: check.dsl,
mode: 'aggregated',
rows: result.rows,
}),| const attributes = JSON.parse(row.attributes) as | ||
| | PageAttributes | ||
| | RecordAttributes; | ||
| matchedPageIds.push(row.id); | ||
| matchedPageNames.push(attributes.name); |
There was a problem hiding this comment.
If a matched row does not have a name property (or if the attributes are empty/corrupted), attributes.name will be undefined. Pushing undefined into matchedPageNames (which is typed as string[]) violates the type contract and can result in user-facing issues like displaying "1 row matched: undefined". Using optional chaining and a fallback empty string prevents this.
const attributes = JSON.parse(row.attributes) as
| PageAttributes
| RecordAttributes;
matchedPageIds.push(row.id);
matchedPageNames.push(attributes?.name ?? '');| onChange={(databaseId) => | ||
| onChange({ | ||
| ...check, | ||
| databaseId, | ||
| }) | ||
| } |
There was a problem hiding this comment.
The databaseId parameter passed to onChange can be null (e.g., if the selection is cleared). Since ScheduledTaskDatabaseQueryCheck.databaseId is typed as a non-nullable string, assigning null directly violates the type definition and can lead to runtime errors. Fall back to an empty string to ensure type safety.
| onChange={(databaseId) => | |
| onChange({ | |
| ...check, | |
| databaseId, | |
| }) | |
| } | |
| onChange={(databaseId) => | |
| onChange({ | |
| ...check, | |
| databaseId: databaseId ?? '', | |
| }) | |
| } |
Summary
database_queryscheduled task check type that runsdatabase.queryDSL on each tickTest plan
packages/clienttests pass (scheduled-task-query-check.test.ts)@colanode/uibuild passesMade with Cursor