Skip to content

feat: trigger eval from referenced variable update#565

Merged
adityachoudhari26 merged 8 commits intomainfrom
trigger-eval-from-reference
May 13, 2025
Merged

feat: trigger eval from referenced variable update#565
adityachoudhari26 merged 8 commits intomainfrom
trigger-eval-from-reference

Conversation

@adityachoudhari26
Copy link
Copy Markdown
Member

@adityachoudhari26 adityachoudhari26 commented May 12, 2025

Summary by CodeRabbit

  • New Features

    • Added automatic detection and propagation of resource variable changes, triggering evaluations when variables are updated or related resources are deleted.
    • Enhanced resource update and creation endpoints to track and process affected variables, ensuring timely updates throughout the system.
    • Introduced new utility functions for identifying affected variables and retrieving resource relationships.
    • Extended event workers to dispatch evaluation jobs for release targets affected by resource hierarchy changes.
  • Bug Fixes

    • Improved consistency and reliability in variable update handling by removing redundant validation steps before queueing updates.
    • Allowed null values in variable snapshots to ensure accurate matching and querying.
  • Tests

    • Added comprehensive end-to-end tests to verify correct propagation and evaluation of variable changes and deletions.
  • Chores

    • Updated internal dependencies and export paths for better modularity and maintainability.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented May 12, 2025

"""

Walkthrough

This update implements comprehensive propagation and tracking of resource variable changes throughout the system. It introduces new helpers and workflow logic to detect, enqueue, and process variable update jobs after resource insertions, updates, and deletions. The changes span API handlers, workers, rule engine utilities, and end-to-end tests to ensure variable state changes trigger correct downstream evaluations.

Changes

File(s) Change Summary
apps/event-worker/src/workers/delete-resource.ts Added dispatchAffectedTargetJobs to enqueue evaluation jobs for release targets related to child resources after resource deletion.
apps/event-worker/src/workers/update-resource-variable.ts Implemented the worker to process variable update jobs, resolve dependent child resources, and dispatch evaluation jobs for affected release targets.
apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts
apps/webservice/src/app/api/v1/resources/route.ts
Modified resource PATCH/POST handlers to fetch existing variables, compute affected variables, and enqueue update jobs for them alongside resource update jobs.
e2e/tests/api/resource-variables.spec.ts Enhanced tests with dynamic references and added tests verifying release target evaluation triggered by referenced resource variable updates and deletions.
packages/api/src/router/resource-variables.ts Removed schema parsing before enqueuing variable update jobs in mutation handlers for create, update, and delete operations.
packages/db/src/queries/index.ts
packages/db/src/queries/relationships/index.ts
Changed export of relationship queries to use a new aggregator module that re-exports children and parents queries.
packages/db/src/queries/relationships/get-resource-children.ts Added new function to retrieve child resources of a given resource based on relationship rules and metadata matching, used for dependency analysis.
packages/db/src/queries/relationships/get-resource-parents.ts Updated import paths for types and schema to deeper relative paths; no logic changes.
packages/db/src/upsert-resources.ts Modified resource upsert query to eagerly load both metadata and variables relations.
packages/events/src/types.ts Updated type for Channel.UpdateResourceVariable to use inferred schema selection type instead of direct schema type.
packages/job-dispatch/package.json Added @ctrlplane/rule-engine as a workspace dependency.
packages/job-dispatch/src/resource/handle-provider-scan.ts Added helper getPreviousVariables and logic to enqueue variable update jobs for inserted and updated resources based on affected variables.
packages/rule-engine/src/index.ts
packages/rule-engine/src/manager/variables/index.ts
Changed export source to aggregate variable-related utilities including affected-variables and resolve-reference-variable.
packages/rule-engine/src/manager/variables/affected-variables.ts Introduced getAffectedVariables to detect created, updated, and deleted resource variables by comparing previous and new sets.
apps/event-worker/src/workers/updated-resources/index.ts Added getAffectedReleaseTargets helper and extended updatedResourceWorker to dispatch evaluation jobs for release targets related to resource children.
packages/rule-engine/src/manager/variable-manager.ts Modified variable snapshot query to correctly handle null values in variable values during release upsert.
packages/db/drizzle/0101_mysterious_redwing.sql Altered variable_value_snapshot.value column to allow NULL values by dropping NOT NULL constraint.
packages/db/drizzle/meta/_journal.json Added a new journal entry for the schema migration altering variable_value_snapshot.
packages/db/src/schema/release.ts Removed .notNull() constraint from variableValueSnapshot.value column in schema definition.

Sequence Diagram(s)

sequenceDiagram
    participant API as API Handler
    participant DB as Database
    participant JD as Job Dispatcher
    participant EW as Event Worker
    participant RE as Rule Engine

    API->>DB: Fetch resource (with metadata, variables)
    API->>DB: Upsert resource
    API->>JD: Enqueue UpdatedResource job
    API->>JD: Enqueue UpdateResourceVariable job(s) for affected variables

    JD->>EW: Dispatch UpdateResourceVariable job(s)
    EW->>DB: Query dependent child resources
    EW->>RE: Dispatch evaluation jobs for affected release targets
Loading

Possibly related PRs

  • ctrlplanedev/ctrlplane#453: Introduces the updateResourceVariableWorker and integrates variable update job dispatching, closely related to variable update propagation logic.
  • ctrlplanedev/ctrlplane#498: Extends deleteResourceWorker with logic to dispatch evaluation jobs for release targets related to child resources after deletion, directly connected to this PR's additions.
  • ctrlplanedev/ctrlplane#515: Updates dispatchEvaluateJobs parameter types and usage, which is directly related to this PR’s use of evaluation job dispatching.

Suggested reviewers

  • jsbroks
  • zacharyblasczyk

Poem

In the warren of code, variables hop,
When resources change, their updates won't stop.
With jobs dispatched and workers awake,
Dependencies ripple, for correctness' sake.
Tests now ensure the carrots are sweet—
Every update and delete, a system-wide treat!
🥕✨
"""

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92b0a93 and 2290383.

📒 Files selected for processing (1)
  • packages/job-dispatch/src/resource/handle-provider-scan.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/job-dispatch/src/resource/handle-provider-scan.ts
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (6)
apps/event-worker/src/workers/update-resource-variable.ts (2)

18-20: Remove noisy debug prints of relationship graphs

dependentResources can grow large and include nested objects – dumping it floods logs and again may surface sensitive metadata.
Either delete the statement or log only counts / identifiers at debug level.

-    console.log(dependentResources);
+    logger.debug({ resourceId, deps: dependentResources.length }, "resolved dependent resources");

21-41: Skip DB round-trip when no dependent resources & use addBulk for large result sets

  1. inArray(col, []) usually compiles to FALSE, but some ORMs emit invalid SQL (WHERE col IN ()). Early-return to avoid an unnecessary query.
  2. The loop inside dispatchEvaluateJobs calls q.add one by one. When affectedReleaseTargets is large we perform N network calls to Redis. Construct an array of { name, data } objects and call q.addBulk, mirroring the surrounding codebase patterns (e.g. delete-resource worker). This cuts latency dramatically.

Example patch:

+    if (dependentResources.length === 0) return;
...
-    dispatchEvaluateJobs(affectedReleaseTargets);
+    await dispatchEvaluateJobs(affectedReleaseTargets);

(You’ll also need to expose/implement an addBulk variant inside dispatchEvaluateJobs.)
[performance]

packages/db/src/queries/relationships/get-resource-children.ts (2)

12-18: Return type is any; expose an explicit interface

Public helpers in packages/db should communicate clear contracts. Consider
declaring a type such as:

export interface ResourceChild {
  ruleId: string;
  type: DependencyType; // import from schema
  source: typeof schema.resource.$inferSelect;
  reference: string;
}

and annotate the promise:

export const getResourceChildren = async (
  tx: Tx,
  resourceId: string,
): Promise<ResourceChild[]> => {  }

This improves IntelliSense, narrows accidental misuse, and surfaces breaking
changes at compile-time.


122-128: Prefer a structured logger over console.log

Raw console.log statements:

  1. Bypass your existing logging framework (if any) – you lose correlation IDs,
    log levels, redaction rules, etc.
  2. Risk leaking PII to stdout in cloud environments.

Use a structured logger (e.g. pino, winston) and respect log levels:

logger.debug({ targetMeta }, "getResourceChildren: target metadata");

…and remove them in the hot path when not required.

e2e/tests/api/resource-variables.spec.ts (2)

528-543: Avoid fixed sleeps – poll for the desired condition

page.waitForTimeout(5_000) introduces brittle timing-based flakiness; CI
environments with high load can still fail. Replace with polling helpers, e.g.

await expect.poll(
  async () => {
    const { data } = await api.GET(
      "/v1/resources/{resourceId}/release-targets",
      { params: { path: { resourceId: sourceResourceId! } } },
    );
    return data?.length ?? 0;
  },
  { timeout: 30_000, message: "release targets should appear" },
).toBeGreaterThan(0);

This keeps the suite reliable without inflating maximum runtime.


183-240: Minor: random reference may collide across parallel test runs

faker.string.alphanumeric(10).toLowerCase() gives only ≈ 60 B possible
combinations; in highly parallel CI jobs the chance of re-using the same
reference across test cases or other branches rises, which can create noisy
data races in shared staging back-ends.

Appending the Playwright worker index or a timestamp helps:

const reference = `${faker.string.alphanumeric(10).toLowerCase()}-${Date.now()}`;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdc70ca and 86a776b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • apps/event-worker/src/workers/delete-resource.ts (2 hunks)
  • apps/event-worker/src/workers/update-resource-variable.ts (1 hunks)
  • apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts (2 hunks)
  • apps/webservice/src/app/api/v1/resources/route.ts (3 hunks)
  • e2e/tests/api/resource-variables.spec.ts (8 hunks)
  • packages/api/src/router/resource-variables.ts (4 hunks)
  • packages/db/src/queries/index.ts (1 hunks)
  • packages/db/src/queries/relationships/get-resource-children.ts (1 hunks)
  • packages/db/src/queries/relationships/get-resource-parents.ts (1 hunks)
  • packages/db/src/queries/relationships/index.ts (1 hunks)
  • packages/db/src/upsert-resources.ts (2 hunks)
  • packages/events/src/types.ts (1 hunks)
  • packages/job-dispatch/package.json (1 hunks)
  • packages/job-dispatch/src/resource/handle-provider-scan.ts (4 hunks)
  • packages/rule-engine/src/index.ts (1 hunks)
  • packages/rule-engine/src/manager/variables/affected-variables.ts (1 hunks)
  • packages/rule-engine/src/manager/variables/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/rule-engine/src/manager/variables/index.ts
  • packages/db/src/queries/relationships/get-resource-parents.ts
  • packages/db/src/upsert-resources.ts
  • packages/db/src/queries/index.ts
  • apps/webservice/src/app/api/v1/resources/route.ts
  • packages/events/src/types.ts
  • packages/rule-engine/src/manager/variables/affected-variables.ts
  • packages/api/src/router/resource-variables.ts
  • packages/rule-engine/src/index.ts
  • packages/db/src/queries/relationships/index.ts
  • apps/event-worker/src/workers/delete-resource.ts
  • apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts
  • packages/job-dispatch/src/resource/handle-provider-scan.ts
  • apps/event-worker/src/workers/update-resource-variable.ts
  • packages/db/src/queries/relationships/get-resource-children.ts
  • e2e/tests/api/resource-variables.spec.ts
🧬 Code Graph Analysis (3)
apps/event-worker/src/workers/delete-resource.ts (3)
packages/db/src/common.ts (1)
  • Tx (22-22)
packages/db/src/schema/resource.ts (1)
  • resource (59-87)
packages/events/src/index.ts (1)
  • getQueue (28-34)
apps/event-worker/src/workers/update-resource-variable.ts (4)
packages/events/src/index.ts (1)
  • createWorker (10-25)
packages/db/src/queries/relationships/get-resource-children.ts (1)
  • getResourceChildren (12-191)
packages/db/src/client.ts (1)
  • db (15-15)
apps/event-worker/src/utils/dispatch-evaluate-jobs.ts (1)
  • dispatchEvaluateJobs (5-20)
packages/db/src/queries/relationships/get-resource-children.ts (1)
packages/db/src/common.ts (1)
  • Tx (22-22)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Lint
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (17)
packages/db/src/queries/relationships/get-resource-parents.ts (1)

5-6: Correct import path adjustments.

The import paths have been updated to reflect the new file location in the relationships directory structure.

packages/job-dispatch/package.json (1)

31-31: Appropriate dependency addition.

The addition of the @ctrlplane/rule-engine workspace dependency aligns well with the PR's objective to trigger evaluations from referenced variable updates.

packages/db/src/queries/index.ts (1)

1-1: Good reorganization of relationship queries.

Consolidating the relationship queries exports through a central index file improves code organization and maintainability.

packages/db/src/upsert-resources.ts (1)

175-175: Good enhancement to include variables in query results.

Including variables: true alongside metadata in the query results is essential for the PR's objective of tracking resource variable changes throughout the system.

packages/db/src/queries/relationships/index.ts (1)

1-2: Clean and organized export structure

This new index file provides a clean, centralized entry point for all relationship query functions, which improves modularity and makes imports more maintainable across the codebase. The approach follows established patterns for organizing related functionality.

packages/rule-engine/src/manager/variables/index.ts (1)

1-2: Well-structured variable utilities export

This index file effectively consolidates variable-related utilities, providing a single import point that simplifies access to both the new getAffectedVariables function and existing reference variable resolution logic. This organization supports the PR's goal of implementing comprehensive variable change tracking and propagation.

packages/rule-engine/src/index.ts (1)

8-8: Export updated to use new variable utilities index

This change correctly updates the export path to use the newly created variables index module, ensuring that both the getAffectedVariables function and the previously exported reference variable utilities are now available through the rule engine's public API.

apps/event-worker/src/workers/delete-resource.ts (1)

56-56: Consider transaction consistency for variable update jobs

The variable update jobs are dispatched outside the transaction that handles the resource deletion. While this works, it means that if dispatching the jobs fails, the resource would still be deleted without the necessary downstream variable updates being processed.

Consider whether this is the intended behavior or if the job dispatching should be included within the transaction for atomicity. If the current approach is intentional (to avoid rolling back deletion if post-processing fails), ensure this design decision is documented.

packages/api/src/router/resource-variables.ts (4)

39-40: Implemented variable change propagation for resource creation.

The added code now properly enqueues each newly created resource variable for asynchronous processing, which will trigger downstream evaluations based on this variable.


64-65: Implemented variable change propagation for reference variable creation.

Similar to direct variables, referenced variables now properly trigger the update workflow. This ensures consistency in how all variable types are handled.


99-100: Implemented variable change propagation for variable updates.

Updates to variables now correctly trigger downstream evaluations, which is essential for maintaining consistency when variable values change.


127-128: Implemented variable change propagation for variable deletion.

The system now properly handles variable deletions by enqueuing update jobs, ensuring that dependent systems are notified when variables are removed.

packages/events/src/types.ts (1)

55-55: Updated variable type to use inferred database schema type.

The type for UpdateResourceVariable channel now correctly uses $inferSelect from the schema definition, which aligns with the actual database-returned objects being enqueued in the system.

apps/webservice/src/app/api/v1/resources/route.ts (3)

10-10: Added import for variable change detection utility.

The import of getAffectedVariables brings in the necessary functionality to identify which variables have changed during resource updates.


79-79: Enhanced resource query to include variables.

The query now fetches the resource's variables alongside metadata, which is necessary for the subsequent variable change detection logic.


101-111: Implemented variable change detection and propagation for resources.

The added code correctly:

  1. Computes which variables are affected by comparing previous and new variables
  2. Enqueues update jobs for each affected variable

This ensures that changes to resources properly trigger evaluations for dependent systems that rely on these variables.

packages/rule-engine/src/manager/variables/affected-variables.ts (1)

1-28: Well-implemented utility for detecting variable changes.

The getAffectedVariables function efficiently identifies three types of variable changes:

  1. Deleted variables
  2. Created variables
  3. Updated variables (using deep equality comparison)

Good use of Map for efficient lookups and lodash's isEqual for deep comparison of objects.

Comment on lines +31 to +41
const dispatchVariableUpdateJobs = async (
tx: Tx,
resource: SCHEMA.Resource,
) => {
const variables = await tx.query.resourceVariable.findMany({
where: eq(SCHEMA.resourceVariable.resourceId, resource.id),
});

for (const variable of variables)
getQueue(Channel.UpdateResourceVariable).add(variable.id, variable);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling to the variable update job dispatching

The function correctly queries and dispatches update jobs for resource variables, but lacks error handling. If the database query fails or job enqueueing encounters an issue, this could lead to unhandled promise rejections.

Consider adding try/catch blocks to handle potential errors:

const dispatchVariableUpdateJobs = async (
  tx: Tx,
  resource: SCHEMA.Resource,
) => {
+  try {
    const variables = await tx.query.resourceVariable.findMany({
      where: eq(SCHEMA.resourceVariable.resourceId, resource.id),
    });

    for (const variable of variables)
      getQueue(Channel.UpdateResourceVariable).add(variable.id, variable);
+  } catch (error) {
+    console.error(`Failed to dispatch variable update jobs for resource ${resource.id}:`, error);
+    // Consider whether to rethrow or handle the error differently based on your requirements
+  }
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const dispatchVariableUpdateJobs = async (
tx: Tx,
resource: SCHEMA.Resource,
) => {
const variables = await tx.query.resourceVariable.findMany({
where: eq(SCHEMA.resourceVariable.resourceId, resource.id),
});
for (const variable of variables)
getQueue(Channel.UpdateResourceVariable).add(variable.id, variable);
};
const dispatchVariableUpdateJobs = async (
tx: Tx,
resource: SCHEMA.Resource,
) => {
try {
const variables = await tx.query.resourceVariable.findMany({
where: eq(SCHEMA.resourceVariable.resourceId, resource.id),
});
for (const variable of variables)
getQueue(Channel.UpdateResourceVariable).add(variable.id, variable);
} catch (error) {
console.error(
`Failed to dispatch variable update jobs for resource ${resource.id}:`,
error,
);
// Consider whether to rethrow or handle the error differently based on your requirements
}
};

Comment on lines +14 to +17
const { data: variable } = job;
console.log(variable);
const { resourceId, key } = variable;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Avoid leaking variable payloads – replace console.log with structured, redacted logging

variable might contain the raw value of a resource variable, including secrets (sensitive = true). Plain console.log outputs it straight to stdout where it is usually collected by centralised log pipelines. This is a substantial PII / secrets-leak risk.

-    console.log(variable);
+    logger.debug({ id: variable.id, key: variable.key, resourceId }, "processing UpdateResourceVariable job");

Use your existing @ctrlplane/logger (or similar) and NEVER include the decrypted value.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +166 to +175
const affectedVariables = getAffectedVariables(
resource.variables,
res.variables,
);

for (const variable of affectedVariables)
await getQueue(Channel.UpdateResourceVariable).add(
variable.id,
variable,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Batch-enqueue variable jobs instead of await-ing each call

Inside the for … of loop we perform sequential await getQueue().add, which is O(N) network round-trips. Gather the jobs and use addBulk — consistent with the surrounding insert / update / delete logic.

-      for (const variable of affectedVariables)
-        await getQueue(Channel.UpdateResourceVariable).add(
-          variable.id,
-          variable,
-        );
+      if (affectedVariables.length) {
+        await getQueue(Channel.UpdateResourceVariable).addBulk(
+          affectedVariables.map((v) => ({ name: v.id, data: v })),
+        );
+      }

Reduces latency and contention under large updates.
[performance]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const affectedVariables = getAffectedVariables(
resource.variables,
res.variables,
);
for (const variable of affectedVariables)
await getQueue(Channel.UpdateResourceVariable).add(
variable.id,
variable,
);
const affectedVariables = getAffectedVariables(
resource.variables,
res.variables,
);
if (affectedVariables.length) {
await getQueue(Channel.UpdateResourceVariable).addBulk(
affectedVariables.map((v) => ({ name: v.id, data: v })),
);
}

Comment on lines +144 to +158
console.log(body);

const all = await upsertResources(db, resource.workspaceId, [
_.merge(resource, body),
]);
const res = all.at(0);

if (res == null) throw new Error("Failed to update resource");

console.log(res.metadata);

console.log(
Object.fromEntries(res.metadata.map((m) => [m.key, m.value])),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Debug prints expose request bodies & metadata – switch to logger and redact

console.log(body) and subsequent metadata dumps can reveal secrets or internal structure in prod logs. Replace with structured logger at debug level and avoid printing values for keys flagged sensitive.

-      console.log(body);
...
-      console.log(res.metadata);
-      console.log(
-        Object.fromEntries(res.metadata.map((m) => [m.key, m.value])),
-      );
+      log.debug({ bodyKeys: Object.keys(body) }, "patch body received");
+      log.debug(
+        { metadataKeys: res.metadata.map((m) => m.key) },
+        "patched metadata keys",
+      );

[security]

Comment on lines +106 to +113
const { variables } = resource;
for (const variable of variables)
await getQueue(Channel.UpdateResourceVariable).add(
variable.id,
variable,
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use bulk enqueue for variables on newly inserted resources

The nested for loops await every add, causing N×M sequential Redis calls. Bundle them:

-    for (const resource of insertedResources) {
-      const { variables } = resource;
-      for (const variable of variables)
-        await getQueue(Channel.UpdateResourceVariable).add(
-          variable.id,
-          variable,
-        );
-    }
+    const insertVariableJobs = insertedResources.flatMap((r) =>
+      r.variables.map((v) => ({ name: v.id, data: v })),
+    );
+    await getQueue(Channel.UpdateResourceVariable).addBulk(insertVariableJobs);

[performance]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { variables } = resource;
for (const variable of variables)
await getQueue(Channel.UpdateResourceVariable).add(
variable.id,
variable,
);
}
const insertVariableJobs = insertedResources.flatMap((r) =>
r.variables.map((v) => ({ name: v.id, data: v })),
);
await getQueue(Channel.UpdateResourceVariable).addBulk(insertVariableJobs);

Comment on lines +114 to +124
for (const resource of updatedResources) {
const { variables } = resource;
const previousVars = previousVariables[resource.identifier] ?? [];

const affectedVariables = getAffectedVariables(previousVars, variables);
for (const variable of affectedVariables)
await getQueue(Channel.UpdateResourceVariable).add(
variable.id,
variable,
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Duplicate sequential enqueuing – replace with addBulk and reuse helper

Same performance problem for updated resources; recommend extracting a enqueueVariableJobs(variables[]) utility to keep code DRY.

-    for (const resource of updatedResources) {
-      ...
-      for (const variable of affectedVariables)
-        await getQueue(Channel.UpdateResourceVariable).add(
-          variable.id,
-          variable,
-        );
-    }
+    const updateVariableJobs = updatedResources.flatMap((r) => {
+      const prev = previousVariables[r.identifier] ?? [];
+      return getAffectedVariables(prev, r.variables).map((v) => ({
+        name: v.id,
+        data: v,
+      }));
+    });
+    await getQueue(Channel.UpdateResourceVariable).addBulk(updateVariableJobs);

[performance] [maintainability]

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const resource of updatedResources) {
const { variables } = resource;
const previousVars = previousVariables[resource.identifier] ?? [];
const affectedVariables = getAffectedVariables(previousVars, variables);
for (const variable of affectedVariables)
await getQueue(Channel.UpdateResourceVariable).add(
variable.id,
variable,
);
}
// replace per-item enqueuing with a single bulk enqueue
const updateVariableJobs = updatedResources.flatMap((r) => {
const prev = previousVariables[r.identifier] ?? [];
return getAffectedVariables(prev, r.variables).map((v) => ({
name: v.id,
data: v,
}));
});
await getQueue(Channel.UpdateResourceVariable).addBulk(updateVariableJobs);

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
apps/event-worker/src/workers/delete-resource.ts (1)

34-51: 🛠️ Refactor suggestion

Add error handling to the resource relationship processing.

The function correctly queries and dispatches evaluation jobs for affected release targets, but lacks error handling. If database queries fail, this could lead to unhandled promise rejections.

Consider adding try/catch blocks to handle potential errors:

const dispatchAffectedTargetJobs = async (
  tx: Tx,
  resource: SCHEMA.Resource,
) => {
+  try {
    const affectedResources = await getResourceChildren(tx, resource.id);

    const affectedReleaseTargets = await db
      .selectDistinctOn([SCHEMA.releaseTarget.id])
      .from(SCHEMA.releaseTarget)
      .where(
        inArray(
          SCHEMA.releaseTarget.resourceId,
          affectedResources.map((r) => r.source.id),
        ),
      );

    await dispatchEvaluateJobs(affectedReleaseTargets);
+  } catch (error) {
+    console.error(`Failed to dispatch jobs for affected targets of resource ${resource.id}:`, error);
+    // Consider whether to rethrow or handle the error differently based on your requirements
+  }
};
🧹 Nitpick comments (3)
apps/event-worker/src/workers/delete-resource.ts (1)

66-66: Pass transaction to maintain consistency.

The dispatchAffectedTargetJobs function is called with the global db instance after the transaction has completed. While this works, it might be preferable to use the transaction inside the block for consistency, especially if you later need to add more operations inside the transaction.

Consider moving this call inside the transaction block:

await db.transaction(async (tx) => {
  await softDeleteResource(tx, resource);
  await deleteComputedResources(tx, resource);
  const rts = await deleteReleaseTargets(tx, resource);
  for (const rt of rts)
    getQueue(Channel.DeletedReleaseTarget).add(rt.id, rt, {
      deduplication: { id: rt.id },
    });
+  await dispatchAffectedTargetJobs(tx, resource);
});

- await dispatchAffectedTargetJobs(db, resource);
packages/db/src/queries/relationships/get-resource-children.ts (2)

19-52: Consider extracting complex subquery conditions into helper functions.

The complex notExists subqueries for isMetadataMatchSatisfied and isMetadataEqualsSatisfied make the code harder to read and maintain. These could be extracted into separate helper functions to improve readability.

Example refactoring:

const createMetadataMatchCondition = (tx: Tx, sourceResource, targetResource) => {
  const sourceMetadata = alias(schema.resourceMetadata, "sourceMetadata");
  const targetMetadata = alias(schema.resourceMetadata, "targetMetadata");
  
  return notExists(
    // ... existing query logic
  );
};

// Then in getResourceChildren:
const isMetadataMatchSatisfied = createMetadataMatchCondition(tx, sourceResource, targetResource);

Also applies to: 54-85


104-150: Consider adding pagination for large resource graphs.

For workspaces with many resources and complex relationships, this query could return a large dataset. Consider implementing pagination or limiting the result set size to prevent potential performance issues.

const relationships = await tx
  .selectDistinctOn([targetResource.id, schema.resourceRelationshipRule.id], {
    ruleId: schema.resourceRelationshipRule.id,
    type: schema.resourceRelationshipRule.dependencyType,
    source: sourceResource,
    reference: schema.resourceRelationshipRule.reference,
  })
  .from(sourceResource)
  // ... existing joins and conditions
+  .limit(1000); // Add a reasonable limit to prevent excessive result sets
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86a776b and 6ef6347.

📒 Files selected for processing (8)
  • apps/event-worker/src/workers/delete-resource.ts (3 hunks)
  • apps/event-worker/src/workers/update-resource-variable.ts (1 hunks)
  • apps/event-worker/src/workers/updated-resources/index.ts (2 hunks)
  • apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts (3 hunks)
  • e2e/tests/api/resource-variables.spec.ts (9 hunks)
  • packages/db/src/queries/relationships/get-resource-children.ts (1 hunks)
  • packages/db/src/upsert-resources.ts (1 hunks)
  • packages/rule-engine/src/manager/variable-manager.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/db/src/upsert-resources.ts
  • apps/webservice/src/app/api/v1/resources/[resourceId]/route.ts
  • apps/event-worker/src/workers/update-resource-variable.ts
  • e2e/tests/api/resource-variables.spec.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/rule-engine/src/manager/variable-manager.ts
  • apps/event-worker/src/workers/delete-resource.ts
  • packages/db/src/queries/relationships/get-resource-children.ts
  • apps/event-worker/src/workers/updated-resources/index.ts
🧬 Code Graph Analysis (2)
packages/db/src/queries/relationships/get-resource-children.ts (1)
packages/db/src/common.ts (1)
  • Tx (22-22)
apps/event-worker/src/workers/updated-resources/index.ts (3)
packages/db/src/queries/relationships/get-resource-children.ts (1)
  • getResourceChildren (12-153)
packages/db/src/client.ts (1)
  • db (15-15)
apps/event-worker/src/utils/dispatch-evaluate-jobs.ts (1)
  • dispatchEvaluateJobs (5-20)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (5)
packages/rule-engine/src/manager/variable-manager.ts (2)

57-57: Consistent handling of null values improves data integrity.

Normalizing null values to empty strings during variable snapshot insertion ensures consistent data representation in the database.


73-76: Good consistency - matching null handling in both insertion and querying.

This change correctly applies the same null-to-empty-string conversion logic when querying for variable snapshots as is used during insertion, ensuring consistent behavior across database operations.

packages/db/src/queries/relationships/get-resource-children.ts (3)

7-11: Well-documented function with clear purpose.

The JSDoc comments clearly explain the purpose of the function and the parameters it takes, which is excellent for maintainability.


13-17: Good use of table aliases to prevent naming conflicts.

Creating aliases for tables that are joined multiple times helps prevent naming conflicts and makes the query logic clearer.


139-146: Good documentation of implementation details.

The comment clearly explains why deleted target resources aren't filtered out, which is important for understanding the function's behavior when called after resource deletion.

Comment on lines +59 to +60
const affectedReleaseTargets = await getAffectedReleaseTargets(resource.id);
await dispatchEvaluateJobs(affectedReleaseTargets);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for job dispatching operations.

If getAffectedReleaseTargets or dispatchEvaluateJobs fails, it could prevent the worker from completing successfully. Adding error handling here ensures the worker can continue even if these operations fail.

-    const affectedReleaseTargets = await getAffectedReleaseTargets(resource.id);
-    await dispatchEvaluateJobs(affectedReleaseTargets);
+    try {
+      const affectedReleaseTargets = await getAffectedReleaseTargets(resource.id);
+      await dispatchEvaluateJobs(affectedReleaseTargets);
+    } catch (error) {
+      span.recordException(error);
+      console.error(`Failed to process affected release targets for resource ${resource.id}:`, error);
+      // Worker continues execution despite this error
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const affectedReleaseTargets = await getAffectedReleaseTargets(resource.id);
await dispatchEvaluateJobs(affectedReleaseTargets);
try {
const affectedReleaseTargets = await getAffectedReleaseTargets(resource.id);
await dispatchEvaluateJobs(affectedReleaseTargets);
} catch (error) {
span.recordException(error);
console.error(`Failed to process affected release targets for resource ${resource.id}:`, error);
// Worker continues execution despite this error
}

Comment on lines +14 to +29
const getAffectedReleaseTargets = async (resourceId: string) => {
const resourceChildren = await getResourceChildren(db, resourceId);
const releaseTargets = await db
.selectDistinctOn([schema.releaseTarget.id])
.from(schema.releaseTarget)
.where(
and(
inArray(
schema.releaseTarget.resourceId,
resourceChildren.map((dr) => dr.source.id),
),
),
);

return releaseTargets;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling to prevent unhandled promise rejections.

The getAffectedReleaseTargets function handles database queries without error handling. This could lead to unhandled promise rejections if database operations fail.

Consider adding try/catch blocks:

const getAffectedReleaseTargets = async (resourceId: string) => {
+  try {
    const resourceChildren = await getResourceChildren(db, resourceId);
    const releaseTargets = await db
      .selectDistinctOn([schema.releaseTarget.id])
      .from(schema.releaseTarget)
      .where(
        and(
          inArray(
            schema.releaseTarget.resourceId,
            resourceChildren.map((dr) => dr.source.id),
          ),
        ),
      );

    return releaseTargets;
+  } catch (error) {
+    console.error(`Failed to get affected release targets for resource ${resourceId}:`, error);
+    return [];
+  }
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getAffectedReleaseTargets = async (resourceId: string) => {
const resourceChildren = await getResourceChildren(db, resourceId);
const releaseTargets = await db
.selectDistinctOn([schema.releaseTarget.id])
.from(schema.releaseTarget)
.where(
and(
inArray(
schema.releaseTarget.resourceId,
resourceChildren.map((dr) => dr.source.id),
),
),
);
return releaseTargets;
};
const getAffectedReleaseTargets = async (resourceId: string) => {
try {
const resourceChildren = await getResourceChildren(db, resourceId);
const releaseTargets = await db
.selectDistinctOn([schema.releaseTarget.id])
.from(schema.releaseTarget)
.where(
and(
inArray(
schema.releaseTarget.resourceId,
resourceChildren.map((dr) => dr.source.id),
),
),
);
return releaseTargets;
} catch (error) {
console.error(
`Failed to get affected release targets for resource ${resourceId}:`,
error,
);
return [];
}
};

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d109d7 and 92b0a93.

📒 Files selected for processing (5)
  • e2e/tests/api/resource-variables.spec.ts (9 hunks)
  • packages/db/drizzle/0101_mysterious_redwing.sql (1 hunks)
  • packages/db/drizzle/meta/_journal.json (1 hunks)
  • packages/db/src/schema/release.ts (1 hunks)
  • packages/rule-engine/src/manager/variable-manager.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • packages/db/drizzle/0101_mysterious_redwing.sql
  • packages/db/drizzle/meta/_journal.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/rule-engine/src/manager/variable-manager.ts
  • e2e/tests/api/resource-variables.spec.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • packages/db/src/schema/release.ts
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)

.references(() => workspace.id, { onDelete: "cascade" }),

value: jsonb("value").$type<any>().notNull(),
value: jsonb("value").$type<any>(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Removed the notNull constraint from the value field

This change allows the value field in the variableValueSnapshot table to accept null values, which aligns with the PR objective to trigger evaluations from referenced variable updates. Make sure to verify that:

  1. All code that accesses this field properly handles null values
  2. The uniqueness index on line 110 (which includes t.value) properly handles null values
  3. There's a database migration to accompany this schema change

🏁 Script executed:

#!/bin/bash
# Search for places that might need to handle null values in the variable value
echo "Checking usages of variableValueSnapshot.value that might need null handling:"
rg -A 3 "variableValueSnapshot.*value" --type ts

Length of output: 2229


🏁 Script executed:

#!/bin/bash
echo "----- Schema excerpt (packages/db/src/schema/release.ts lines 80-130) -----"
sed -n '80,130p' packages/db/src/schema/release.ts || echo "Schema file not found"

echo
echo "----- create-release-job.ts snippet around value usage -----"
grep -R --include '*.ts' -n "value: v\\.variableValueSnapshot\\.value" -A5 packages/db/src/queries/create-release-job.ts || echo "Snippet not found"

echo
echo "----- Listing potential migration files -----"
find . -type f | grep -i migration || echo "No migration files detected"

echo
echo "----- Searching for null constraint removal in migrations -----"
grep -R -H -n -E "DROP NOT NULL|SET NOT NULL" . || echo "No ALTER COLUMN null constraints found"

Length of output: 4671


Validate nullability changes for variableValueSnapshot.value

Please confirm the following now that the value column is nullable:

  • Code reads/writes handle null correctly:
    • packages/db/src/queries/create-release-job.ts (≈ line 67): value: v.variableValueSnapshot.value – update TypeScript types or add null checks if needed.
    • packages/rule-engine/src/manager/variable-manager.ts already covers isNull(...) vs eq(...).
    • apps/webservice/src/app/api/v1/.../releases/route.ts will emit JSON value: null – verify clients tolerate this.

  • Unique index on (workspace_id, key, value) in packages/db/src/schema/release.ts treats NULL as distinct in PostgreSQL. If you need to enforce uniqueness for NULL too, consider a functional or partial index (e.g. coalesce).

  • Migration file packages/db/drizzle/0101_mysterious_redwing.sql includes
    ALTER TABLE "variable_value_snapshot" ALTER COLUMN "value" DROP NOT NULL;
    Ensure it’s in your deployment pipeline.

@adityachoudhari26 adityachoudhari26 merged commit 6cdf28e into main May 13, 2025
7 of 10 checks passed
@adityachoudhari26 adityachoudhari26 deleted the trigger-eval-from-reference branch May 13, 2025 05:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant