Skip to content

fix: preserve single latest draft when saving a new identity - #7918

Open
Shivamkmr8 wants to merge 2 commits into
elsa-workflows:release/3.8.0from
Shivamkmr8:improve-save-draft-latest-state
Open

fix: preserve single latest draft when saving a new identity#7918
Shivamkmr8 wants to merge 2 commits into
elsa-workflows:release/3.8.0from
Shivamkmr8:improve-save-draft-latest-state

Conversation

@Shivamkmr8

Copy link
Copy Markdown

Purpose

Addresses #7915 by ensuring that SaveDraftAsync preserves a single latest workflow definition when saving a different draft identity for the same DefinitionId.

The change extends the existing latest-state handling to unpublished drafts while preserving the current behavior when re-saving the same draft.


Scope

Select one primary concern:

  • Bug fix (behavior change)
  • Refactor (no behavior change)
  • Documentation update
  • Formatting / code cleanup
  • Dependency / build update
  • New feature

If this PR includes multiple unrelated concerns, please split it before requesting review.


Description

Problem

SaveDraftAsync sets the incoming workflow definition to IsLatest = true.

When a previous version exists, its IsLatest flag is currently cleared only when that version is both published and latest:

if (lastVersion is { IsPublished: true, IsLatest: true })
{
    lastVersion.IsLatest = false;
    await workflowDefinitionStore.SaveAsync(lastVersion, cancellationToken);
}

This works for the standard published-to-draft flow.

However, when SaveDraftAsync receives a different draft identity for the same DefinitionId while the previous latest version is unpublished, the previous version is not demoted.

For example, starting with:

V1
IsPublished = false
IsLatest = true

and saving a different draft identity creates:

V1 -> IsPublished = false, IsLatest = true
V2 -> IsPublished = false, IsLatest = true

This leaves multiple workflow definition versions marked as latest.

Solution

Update the condition so that the previous last version is demoted whenever:

  • it is currently marked as latest, and
  • the incoming draft represents a different workflow definition identity.
if (lastVersion is { IsLatest: true } && lastVersion.Id != draft.Id)
{
    lastVersion.IsLatest = false;
    await workflowDefinitionStore.SaveAsync(lastVersion, cancellationToken);
}

The identity check is important because SaveDraftAsync also supports re-saving the same draft.

When the same draft is saved again:

lastVersion.Id == draft.Id

the existing draft remains latest and retains its existing version number.

This results in:

Different draft identity:
V1 -> IsLatest = false
V2 -> IsLatest = true

Same draft identity:
V1 -> IsLatest = true

No changes are made to version allocation, publication state, or persistence ordering.


Verification

Steps:

  1. Added SaveDraftAsync_ShouldClearLatestFlagFromPreviousUnpublishedDraft.
  2. Verified the test fails with the previous implementation because the existing unpublished draft remains IsLatest = true.
  3. Added SaveDraftAsync_ShouldKeepExistingDraftLatestWhenResaved to protect the existing same-draft behavior.
  4. Updated the SaveDraftAsync latest-state condition.
  5. Ran both focused tests.
  6. Ran the complete Elsa.Workflows.IntegrationTests suite.

Expected outcome:

  • Saving a different draft identity demotes the previous latest workflow definition.
  • The newly saved draft becomes the latest version.
  • Re-saving the same draft does not demote itself or create a new numeric version.
  • Existing workflow management behavior remains unaffected.

Test results:

Elsa.Workflows.IntegrationTests

Passed: 274
Failed: 0
Skipped: 0

Screenshots / Recordings (if applicable)

Not applicable.


Commit Convention

We recommend using conventional commit prefixes:

  • fix: – Bug fixes (behavior change)
  • feat: – New features
  • refactor: – Code changes without behavior change
  • docs: – Documentation updates
  • chore: – Maintenance, tooling, or dependency updates
  • test: – Test additions or modifications

Clear commit messages make reviews easier and history more meaningful.


Related Issue

Closes #7915


Checklist

  • The PR is focused on a single concern
  • Commit messages follow the recommended convention
  • Tests added or updated (if applicable)
  • Documentation updated (if applicable)
  • No unrelated cleanup included
  • All tests pass

@Shivamkmr8

Copy link
Copy Markdown
Author

@dotnet-policy-service agree company="Surya Fintech"

@sfmskywalker
sfmskywalker requested a balanced review from Copilot August 11, 2026 22:54
@sfmskywalker

Copy link
Copy Markdown
Member

@greptile

Copilot AI left a comment

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.

Pull request overview

Fixes #7915 by preserving a single latest workflow definition when saving a new draft identity.

Changes:

  • Demotes the previous latest draft when identities differ.
  • Preserves version and latest status when re-saving the same draft.
  • Adds integration coverage for both scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs Extends latest-version demotion to unpublished drafts.
test/integration/Elsa.Workflows.IntegrationTests/Scenarios/WorkflowDefinitionVersioning/Tests.cs Tests new-identity and same-identity draft saves.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The workflow draft persistence update keeps exactly one latest definition when a new draft identity replaces an unpublished draft, while preserving version and latest state when the same draft is re-saved. Focused integration coverage also confirms that persistence and notification failures do not leave committed workflow definitions in a conflicting latest state.

Confidence Score: 5/5

No blocking failure remains.

The focused integration checks exercised replacement, re-save, persistence-failure, and notification-failure behavior and confirmed the expected persisted state.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the focused workflow-draft integration tests against the parent revision and the updated revision to validate contract behavior.
  • The parent revision reproduced four failures across replacement, same-identity re-save, persistence-failure, and notification-failure paths.
  • The updated revision ran all six focused tests successfully, confirming the expected persisted latest-state behavior.
  • The before-commit and after-commit logs show the test-run transition from a setup with exact restore/test commands and four failures to a no-restore/no-build run with six passing tests.
  • The workflow-draft runtime test source file and its extraction/output were exercised and captured in both scopes.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (2): Last reviewed commit: "Merge branch 'release/3.8.0' into improv..." | Re-trigger Greptile

@Shivamkmr8
Shivamkmr8 force-pushed the improve-save-draft-latest-state branch from f21d162 to fbb2a8e Compare August 12, 2026 05:07
@Shivamkmr8

Copy link
Copy Markdown
Author

@sfmskywalker The previous Greptile issue has been addressed by persisting the previous-version demotion and the new latest version together using SaveManyAsync, with the post-save notification moved after persistence. The focused tests are passing. Could you please review the latest changes?

@sfmskywalker

Copy link
Copy Markdown
Member

@greptileai

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

src/modules/Elsa.Workflows.Management/Services/WorkflowDefinitionPublisher.cs:235

  • The PR description states that persistence ordering is unchanged, but this batch now persists the previous-version demotion before WorkflowDefinitionDraftSaved; previously the draft was saved and that notification was dispatched before the previous version was demoted. This materially changes notification and failure semantics (as the new notification-failure test confirms), so the description should document the ordering change rather than claim there is none.
            await workflowDefinitionStore.SaveManyAsync([lastVersion, draft], cancellationToken);

Comment on lines +232 to +235
if (lastVersion is { IsLatest: true } && lastVersion.Id != draft.Id)
{
lastVersion.IsLatest = false;
await workflowDefinitionStore.SaveAsync(lastVersion, cancellationToken);
await workflowDefinitionStore.SaveManyAsync([lastVersion, draft], cancellationToken);
@Shivamkmr8
Shivamkmr8 force-pushed the improve-save-draft-latest-state branch from 3c21b26 to 2ba7c5e Compare August 18, 2026 06:03
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

PR author is not in the allowed authors list.

@Shivamkmr8

Copy link
Copy Markdown
Author

@sfmskywalker I have addressed the latest Copilot comment.

SaveDraftAsync now uses the highest version only to calculate the version number, and separately finds the workflow marked as IsLatest to clear its latest flag.

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.

3 participants