Repository navigation
[COMMS-1022] Journal work package labels - #25320
Conversation
c5c66f6 to
4b766c9
Compare
4b766c9 to
d2db840
Compare
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
|
Note for the admin side of labels (COMMS-1025 / COMMS-1026, PR 2 of the labels stack): deleting a label from Administration → Labels removes its Consequences for this PR's model:
That is acceptable for the first iteration; the epic's journaling wording ("Label {name} removed by {author}") assumes a per-work-package change, and an admin delete is a bulk operation. If a dedicated entry at deletion time turns out to be wanted, the way to do it is a background job iterating the affected work package ids and calling Flagging it here so the two PRs stay consistent; nothing to change in this one. |
|
Follow-up to the note above, after checking the epic text: admin deletion of a label (COMMS-1026, #25394) is allowed while work packages still carry it and cascades to the labelings. The epic only defines work-package-level entries ("Label {name} added / removed by {author}") and is silent on admin deletion, so #25394 ships without journaling it. Decided to handle the journaling side here rather than in the admin PR. Two things this model needs to be able to show a deletion in history: a journal on each affected work package at deletion time (a job with the admin as author and a dedicated cause), and the label name surviving the delete, either as a snapshot column on |
Snapshots the label set per journal so the activity tab renders label changes and baseline comparison loads historic labels. Snapshot rows keep no foreign key to labels so history survives a hard delete. https://community.openproject.org/wp/79963
Keeps the migration ordered after the ones merged since the branch was opened.
A label can be hard-deleted while journals still reference it. Dropping it made the entry read as if the labels had been cleared.
d2db840 to
963aae4
Compare
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
There was a problem hiding this comment.
🟡 Changes recommended
Upgrade baselines, activity filtering, timestamped API integration, and snapshot preloading remain incomplete.
5 open issues
Include labels in timestamp API supported link properties · New Copy historic labels to root work packages · New Create baseline label snapshots during migration · New Label-only changes missing from Only changes activity filter · New Preload label journals to prevent per-entry activity queries · New
What changed in this PR
Adds label snapshots to work package journals, supporting activity history and historic label comparisons.
Changes:
- Adds label snapshot storage and journal change detection.
- Adds label formatting with deleted-label placeholders.
- Loads historic label sets and tests journaling, aggregation, and comparisons.
| File | Description |
|---|---|
| spec/services/journals/create_service/association_spec.rb | Tests label association registration. |
| spec/models/work_package/work_package_acts_as_journalized_spec.rb | Tests label journaling and aggregation. |
| spec/models/journal/label_journal_spec.rb | Tests snapshot persistence after deletion. |
| spec/models/journable/with_historic_attributes_spec.rb | Tests historic labels and comparisons. |
| spec/lib/open_project/journal_formatter/labels_spec.rb | Tests label history formatting. |
| spec/factories/journal/label_journal_factory.rb | Adds a label snapshot factory. |
| lib/open_project/journal_formatter/labels.rb | Formats label changes and deleted labels. |
| lib/open_project/journal_formatter/joined_versions.rb | Reuses shared association formatting. |
| lib/open_project/journal_formatter/joined_association.rb | Extracts joined-association formatting. |
| lib_static/plugins/acts_as_journalized/lib/journal_changes.rb | Computes label-set changes. |
| db/migrate/20261021090000_create_label_journals.rb | Creates label snapshot storage. |
| config/locales/en.yml | Adds label translations. |
| app/services/journals/create_service/labelable.rb | Snapshots labels and detects changes. |
| app/services/journals/create_service/association.rb | Registers label snapshot handling. |
| app/models/work_package/journalized.rb | Registers label change formatting. |
| app/models/journal/label_journal.rb | Defines the label snapshot model. |
| app/models/journal.rb | Registers label formatting and snapshots. |
| app/models/journable/with_historic_attributes/loader.rb | Loads historic label associations. |
| app/models/journable/with_historic_attributes.rb | Compares historic and current labels. |
Review effort: Balanced
Existing journals predate the snapshot table. Without a baseline, the next unchanged save of a labelled work package journals a spurious change and a first-edit removal of all labels journals nothing.
Label-only journals were hidden because the filter compared every other snapshot table with the predecessor but not the label snapshots.
Rendering label changes otherwise queries label_journals once per entry and once per predecessor.
The baseline comparison computed the label change but the timestamp representer dropped it, since labels were not among its supported links.
When the last requested timestamp is historic, the work package took its labels from the current record while every other attribute came from the journal.
The historic last-timestamp case was covered; the baseline comparison entry was not.
dev gained a migration with the version 20261021090000 after the rebase, and CI refuses duplicate versions.
Labels are unreleased and behind a feature flag, so no database has label history from before the snapshot table.
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
brunopagno
left a comment
There was a problem hiding this comment.
I think this is good to go. I don't see big risks as most of it is pretty similar to what we've done with target/observed_in versions. But journals is always somewhat energy consuming 😅
Comments are just observations, probably nothing that needs to be handled in this PR
| # See COPYRIGHT and LICENSE files for more details. | ||
| #++ | ||
|
|
||
| class OpenProject::JournalFormatter::JoinedAssociation < JournalFormatter::NamedAssociation |
| override_association(work_package, source, :target_versions) | ||
| override_association(work_package, source, :labels) |
There was a problem hiding this comment.
Did we forget to include observed_in_versions here in the past? 👀
There was a problem hiding this comment.
Yes, it seems so. Tracked separately as COMMS-1068 - raising a PR shortly
| .includes(:data, :customizable_journals, :attachable_journals, :target_version_journals, :label_journals, | ||
| :observed_in_version_journals, :bcf_comment) |
There was a problem hiding this comment.
This is one of the unnecessariest things ever, but it feels incorrect to have :label between :target_version and :observed_in_version 😅
There was a problem hiding this comment.
Some say some distance is healthy in relationships 😄
There was a problem hiding this comment.
Also contemplating moving these values into a constant as this list is fairly large
Covers the callback order that lets the snapshot see labels written in the same save, and drops two examples that only restated Rails conventions.
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |

Ticket
https://community.openproject.org/wp/79963
What are you trying to accomplish?
Label changes on a work package were invisible in its history. Each journal now snapshots the work package's label set, so the activity tab renders "Labels: set to A, B" and "Labels: changed from A, B to A", and baseline comparison loads the historic set for a timestamp.
A label that no longer exists renders as "(deleted label)" instead of silently dropping out of the entry.
Screenshots
What approach did you choose and why?
The snapshot lives in its own table,
label_journals, one row per journal and label, the same shape the versions snapshot uses. It keeps no foreign key tolabels, so history survives a hard delete; the formatter looks the label up live and falls back to a placeholder, which is how deleted versions and custom options already behave.Label names in history resolve live, so a renamed label shows its current name in old entries, the same trade-off versions make. Existing journals get no label snapshot: labels are unreleased and behind a feature flag, so no database carries label history from before this table.
AI involvement
Collaborative – AI generated a substantial part of the code; I reviewed and understand every line.
Merge checklist