Skip to content

Conversation

@James-Voight
Copy link

@James-Voight James-Voight commented Nov 5, 2025

Rebuilds: Update WorkflowStateMachine
#1324

Summary by CodeRabbit

  • New Features
    • Added automatic product publishing on rebuild completion when a project enables the setting.
    • Project owners receive email notifications when an automatic publish completes.
    • Expanded email notification translations for Spanish (Latin America) and French locales.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Walkthrough

Adds automatic publish-on-rebuild support: propagates an isAutomatic flag into product actions and workflow instances, introduces autoPublishOnRebuild in workflow input/guards, wires guarded transitions and owner notification, and adds localization strings for auto-publish notifications.

Changes

Cohort / File(s) Summary
Product action API
src/lib/products/server.ts
doProductAction(productId, action, isAutomatic = false) — new optional isAutomatic parameter; passed into Workflow.create on Rebuild/Republish paths.
Workflow types & guards
src/lib/workflowTypes.ts
Added isAutomatic to WorkflowInstanceContext/WorkflowConfig, autoPublishOnRebuild to WorkflowInput, new autoPublishOnRebuild() guard and updated Guards union.
State machine
src/lib/server/workflow/state-machine.ts
New guarded transition from Build_SuccessfulProduct_Publish when guard autoPublishOnRebuild passes; calls notifyAutoPublishOwner() in relevant transitions; context initialized with new flags.
Workflow instance management
src/lib/server/workflow/index.ts
Selects AutoPublishOnRebuild from Project in Prisma queries; parses Context JSON into local context (ensures isAutomatic default); exposes autoPublishOnRebuild in input; filters autoPublishOnRebuild from persisted Context.
DB procedures / Notifications
src/lib/server/workflow/dbProcedures.ts
Added notifyAutoPublishOwner(productId: string) — loads product/project, returns if no owner, enqueues BullMQ email with auto-publish message key and project/product names.
Job executors
src/lib/server/job-executors/product.ts, src/lib/server/job-executors/system.ts
When creating workflow instances, include isAutomatic: false in Workflow.create()/context payloads.
Email localization
src/lib/server/email-service/locales/en-us.json, src/lib/server/email-service/locales/es-419.json, src/lib/server/email-service/locales/fr-FR.json
Added autoPublishOnRebuildCompleted keys under notifications.body and notifications.subject (and notification for es-419) with localized templates including projectName and productName.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API as Product API
    participant Workflow as State Machine
    participant Build as Build Service
    participant Publish as Publish Service
    participant Notify as Notification Queue

    User->>API: Trigger Rebuild (isAutomatic=true)
    API->>Workflow: create instance (isAutomatic=true, autoPublishOnRebuild)
    Workflow->>Build: run build
    Build-->>Workflow: Build Successful

    rect rgb(230, 245, 230)
        note right of Workflow: Evaluate guard\nautoPublishOnRebuild && isAutomatic && type=Rebuild
    end

    alt Guard passes
        Workflow->>Publish: transition to Publish
        Publish-->>Workflow: Publish Completed
        Workflow->>Notify: notifyAutoPublishOwner(productId)
        Notify-->>Owner: enqueue email (autoPublishOnRebuildCompleted)
    else Guard fails
        Workflow->>Workflow: await manual publish
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • src/lib/server/workflow/index.ts: snapshot parsing/restoration and filtering autoPublishOnRebuild from persisted Context.
    • src/lib/server/workflow/state-machine.ts: correctness of guard wiring and transition ordering.
    • src/lib/server/workflow/dbProcedures.ts: queue enqueuing details and early-exit behavior when no project owner.
    • Type updates in src/lib/workflowTypes.ts to ensure no downstream type mismatches.

Possibly related issues

Possibly related PRs

Suggested reviewers

  • chrisvire
  • FyreByrd

Poem

🐰 A rebuild hops, soft and spry,
An automatic publish nigh,
When build succeeds and bells are rung,
The owner’s mail sings out its song,
Workflows dance — the rabbit’s done a jig! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Rebuilds update workflow state machine' is partially related to the changeset, referring to workflow state machine updates but missing key details about the auto-publish feature that constitutes a significant portion of changes.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65510c3 and 4016687.

📒 Files selected for processing (2)
  • src/lib/server/email-service/locales/es-419.json (2 hunks)
  • src/lib/server/email-service/locales/fr-FR.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/server/email-service/locales/fr-FR.json
  • src/lib/server/email-service/locales/es-419.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: checks / build-and-test

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@James-Voight James-Voight force-pushed the Rebuilds-Update-WorkflowStateMachine branch from ae8eadb to 49a9ee3 Compare November 13, 2025 17:18
Copy link
Contributor

@FyreByrd FyreByrd left a comment

Choose a reason for hiding this comment

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

This is a good start. Hopefully we can get testing figured out soon.

options: new Set(product.ProductDefinition[flowType].WorkflowOptions),
workflowType: product.ProductDefinition[flowType].Type
workflowType: product.ProductDefinition[flowType].Type,
isAutomatic: false
Copy link
Contributor

Choose a reason for hiding this comment

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

Since doProductAction is likely going to be used for the software update feature, you will probably want to have isAutomatic as either an optional parameter, or a parameter with a default value. Either way you do it, you should be able to modify the function signature of doProductAction without needing to modify the callsites.

}
});
if (!product?.Project.OwnerId) return;
void getQueues().Emails.add(`Notify Owner of Auto Publish for Product #${productId}`, {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the purpose of the void keyword here? Why not await?

Copy link
Contributor

Choose a reason for hiding this comment

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

I have the same question in a few other locations.

@James-Voight James-Voight force-pushed the Rebuilds-Update-WorkflowStateMachine branch from 49a9ee3 to 65510c3 Compare November 17, 2025 14:04
@James-Voight James-Voight marked this pull request as ready for review November 17, 2025 14:11
Copy link
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

🧹 Nitpick comments (4)
src/lib/server/workflow/dbProcedures.ts (1)

55-82: Consider adding error handling for robustness.

The function silently returns on missing product or owner without logging. Consider adding logging or error handling for:

  • Database read failures
  • Missing product scenarios
  • Email queueing failures

This would aid debugging if auto-publish notifications fail to send.

Example:

 export async function notifyAutoPublishOwner(productId: string) {
   const product = await DatabaseReads.products.findUnique({
     where: { Id: productId },
     select: {
       ProductDefinition: {
         select: {
           Name: true
         }
       },
       Project: {
         select: {
           Name: true,
           OwnerId: true
         }
       }
     }
   });
-  if (!product?.Project.OwnerId) return;
+  if (!product) {
+    console.warn(`notifyAutoPublishOwner: Product ${productId} not found`);
+    return;
+  }
+  if (!product.Project.OwnerId) {
+    console.warn(`notifyAutoPublishOwner: No owner for product ${productId}`);
+    return;
+  }
   await getQueues().Emails.add(`Notify Owner of Auto Publish for Product #${productId}`, {
     type: BullMQ.JobType.Email_SendNotificationToUser,
     userId: product.Project.OwnerId,
     messageKey: 'autoPublishOnRebuildCompleted',
     messageProperties: {
       projectName: product.Project.Name ?? '',
       productName: product.ProductDefinition.Name ?? ''
     }
   });
 }
src/lib/server/workflow/state-machine.ts (1)

815-846: Align notification condition more tightly with the auto-publish guard

The owner notification on Publish_Completed is currently gated by:

if (context.autoPublishOnRebuild && context.isAutomatic) {
  void notifyAutoPublishOwner(context.productId);
}

whereas the transition into auto‑publish is guarded by autoPublishOnRebuild (which also checks workflowType === WorkflowType.Rebuild).

Two suggestions:

  1. Match the guard semantics
    If the email is intended only for “auto publish on rebuild” runs (not any future workflow that might set isAutomatic), consider also checking the workflow type, e.g.:

    if (
      context.autoPublishOnRebuild &&
      context.isAutomatic &&
      context.workflowType === WorkflowType.Rebuild
    ) {
      void notifyAutoPublishOwner(context.productId);
    }

    This keeps the notification tied to the same high‑level condition as the auto‑publish path.

  2. Optional: handle notification failures explicitly
    notifyAutoPublishOwner is async and is fire‑and‑forget here. That matches the existing pattern for queueing jobs, but if you ever see unhandled rejection noise from this path, consider adding a .catch inside notifyAutoPublishOwner or here to log and swallow failures.

src/lib/server/workflow/index.ts (2)

150-205: Snapshot restore defaults isAutomatic correctly and exposes new inputs

Parsing instance.Context into a WorkflowInstanceContext, then applying:

context.isAutomatic ??= false;

before building the returned context and input:

  • Gives older snapshots (which lack isAutomatic) a safe default of false.
  • Ensures input.isAutomatic and context.isAutomatic stay in sync.
  • Adds autoPublishOnRebuild to input based on the current Project flag, so snapshot restores always reflect the latest project setting rather than whatever might have been in the stored context.

One small nit: after the nullish‑assignment, context.isAutomatic ?? false in the input construction is redundant; you can just use context.isAutomatic if you want to simplify slightly.


366-405: Consider using the filtered context for both create and update

In createSnapshot, you build a filtered context that strips non‑instance fields:

const filtered = {
  ...context,
  productId: undefined,
  hasAuthors: undefined,
  hasReviewers: undefined,
  autoPublishOnRebuild: undefined,
  productType: undefined,
  options: undefined
} as WorkflowInstanceContext;

but only use it on update, while create still persists the full context:

create: {
  State: ...,
  Context: JSON.stringify(context),
  ...
},
update: {
  State: ...,
  Context: JSON.stringify(filtered)
}

That means the very first row for a workflow instance may still serialize autoPublishOnRebuild, productId, etc., even though subsequent updates do not.

Behavior-wise this is harmless (the restore path rebuilds input from DB fields and overlays it), but for consistency and to keep workflowInstances.Context strictly to WorkflowInstanceContext, you could also use filtered in the create branch:

-      create: {
-        State: Workflow.stateName(this.currentState ?? { id: 'Start' }),
-        Context: JSON.stringify(context),
+      create: {
+        State: Workflow.stateName(this.currentState ?? { id: 'Start' }),
+        Context: JSON.stringify(filtered),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a527d1d and 65510c3.

📒 Files selected for processing (10)
  • src/lib/products/server.ts (2 hunks)
  • src/lib/server/email-service/locales/en-us.json (2 hunks)
  • src/lib/server/email-service/locales/es-419.json (2 hunks)
  • src/lib/server/email-service/locales/fr-FR.json (2 hunks)
  • src/lib/server/job-executors/product.ts (1 hunks)
  • src/lib/server/job-executors/system.ts (1 hunks)
  • src/lib/server/workflow/dbProcedures.ts (2 hunks)
  • src/lib/server/workflow/index.ts (5 hunks)
  • src/lib/server/workflow/state-machine.ts (5 hunks)
  • src/lib/workflowTypes.ts (4 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-12T14:07:02.200Z
Learnt from: FyreByrd
Repo: sillsdev/appbuilder-portal PR: 1283
File: src/routes/(authenticated)/tasks/[product_id]/+page.server.ts:285-303
Timestamp: 2025-09-12T14:07:02.200Z
Learning: In src/routes/(authenticated)/tasks/[product_id]/+page.server.ts, FyreByrd prefers to optimize filterAvailableActions by creating Sets at the caller level rather than inside the function, so the function would take Set<number> arguments instead of arrays for better performance and separation of concerns.

Applied to files:

  • src/lib/products/server.ts
📚 Learning: 2025-09-04T14:26:59.326Z
Learnt from: FyreByrd
Repo: sillsdev/appbuilder-portal PR: 1227
File: src/lib/server/job-executors/product.ts:247-250
Timestamp: 2025-09-04T14:26:59.326Z
Learning: In src/lib/server/job-executors/product.ts, the createLocal function's catch block returns false instead of rethrowing errors. This was implemented intentionally to fix another issue, so any changes to this error handling should be carefully evaluated for downstream impacts.

Applied to files:

  • src/lib/products/server.ts
  • src/lib/server/job-executors/product.ts
📚 Learning: 2025-09-04T16:23:55.891Z
Learnt from: FyreByrd
Repo: sillsdev/appbuilder-portal PR: 1227
File: src/lib/server/job-executors/product.ts:247-250
Timestamp: 2025-09-04T16:23:55.891Z
Learning: In src/lib/server/job-executors/product.ts, createLocal’s catch should log the error via job.log with relevant context (projectId/productDefinitionId/storeId) and still return false to preserve the intentional “no-retry” behavior.

Applied to files:

  • src/lib/server/job-executors/product.ts
📚 Learning: 2025-09-12T14:02:04.558Z
Learnt from: FyreByrd
Repo: sillsdev/appbuilder-portal PR: 1283
File: src/lib/server/workflow/index.ts:52-74
Timestamp: 2025-09-12T14:02:04.558Z
Learning: In the appbuilder-portal codebase, when a Product exists, it always has an associated Project relationship. The Project._count fields (Authors, Reviewers) are safe to access directly when the Product query returns a result.

Applied to files:

  • src/lib/server/workflow/index.ts
🧬 Code graph analysis (3)
src/lib/server/workflow/dbProcedures.ts (3)
src/lib/server/job-executors/build.ts (1)
  • product (11-131)
src/lib/server/job-executors/publish.ts (1)
  • product (10-158)
src/lib/server/database/prisma.ts (1)
  • DatabaseReads (22-22)
src/lib/server/workflow/state-machine.ts (2)
src/lib/workflowTypes.ts (1)
  • autoPublishOnRebuild (265-271)
src/lib/server/workflow/dbProcedures.ts (1)
  • notifyAutoPublishOwner (55-82)
src/lib/server/workflow/index.ts (1)
src/lib/workflowTypes.ts (1)
  • WorkflowInstanceContext (104-137)
🔇 Additional comments (12)
src/lib/server/job-executors/product.ts (1)

247-248: LGTM: Correct initialization for manual workflow creation.

The isAutomatic: false field is appropriately set for manually created products via createLocal.

src/lib/server/job-executors/system.ts (1)

976-976: LGTM: Appropriate default for migrated workflows.

The isAutomatic: false initialization is correct for workflow instances created during migration, as these represent pre-existing workflows rather than automatically triggered ones.

src/lib/products/server.ts (2)

7-11: LGTM: Default parameter addresses previous feedback.

The addition of the isAutomatic = false default parameter properly addresses the past review comment about making this parameter optional for backward compatibility.

Based on learnings


56-57: LGTM: Correct propagation of isAutomatic flag.

The isAutomatic parameter is properly passed to Workflow.create for rebuild/republish workflows.

src/lib/server/email-service/locales/en-us.json (1)

76-76: LGTM: English translations added correctly.

The autoPublishOnRebuildCompleted translation keys are properly added with appropriate English messages and correct placeholder variables (projectName, productName).

Also applies to: 118-118

src/lib/workflowTypes.ts (4)

2-3: LGTM: Correct import optimization.

The split between value import (WorkflowType) and type-only import (RoleId) is appropriate, as WorkflowType is used in the runtime comparison at line 269.


136-136: LGTM: Consistent field additions.

The isAutomatic: boolean field is properly added to both WorkflowInstanceContext and WorkflowConfig, ensuring type consistency across the workflow system.

Also applies to: 186-186


193-193: LGTM: Appropriate field addition.

The autoPublishOnRebuild: boolean field on WorkflowInput correctly extends the workflow configuration for the auto-publish feature.


265-272: LGTM: Guard logic is correct.

The autoPublishOnRebuild guard properly checks all three required conditions:

  1. Feature is enabled (autoPublishOnRebuild)
  2. Workflow is automatic (isAutomatic)
  3. Workflow type is Rebuild

The function is correctly added to the Guards type union.

src/lib/server/workflow/state-machine.ts (2)

17-25: Context wiring for isAutomatic / autoPublishOnRebuild looks consistent

Importing autoPublishOnRebuild and notifyAutoPublishOwner, and carrying isAutomatic, hasReviewers, and autoPublishOnRebuild through the machine context from input matches how the guards and notifications are used later. This keeps WorkflowContext and WorkflowInput aligned so both guard-based filtering and runtime guards have the data they need. I don’t see issues with this wiring.

Also applies to: 60-68


528-563: Auto-publish transition on Build_Successful is correctly guarded

The new Build_Successful transition that jumps directly to Product_Publish under autoPublishOnRebuild:

  • Is placed before the normal “verify and publish” path, so it naturally short-circuits only when the guard passes.
  • Uses includeWhen.guards: [autoPublishOnRebuild] plus guard: autoPublishOnRebuild, keeping the visualization/happy‑path filtering and runtime behavior in sync.
  • Leaves the existing Google Play startup path and the default manual path untouched.

This matches the intent of an opt‑in automatic rebuild publish without breaking existing flows.

src/lib/server/workflow/index.ts (1)

51-76: Project-level AutoPublishOnRebuild is plumbed cleanly into workflow input

Extending the initial product query to select Project.AutoPublishOnRebuild and threading it into WorkflowInput.autoPublishOnRebuild (alongside hasAuthors / hasReviewers) is straightforward and keeps all the auto‑publish inputs together. The !!check?.Project.AutoPublishOnRebuild coercion means missing or null values safely default to false. This looks good.

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.

2 participants