Feature/COMMS-1001: Labels data model - #25319
Conversation
Labels are a global, case-insensitively unique resource attached to work packages through a polymorphic join so other entities can be labeled later. Data model only; API, journaling and admin follow. https://community.openproject.org/wp/79593
There was a problem hiding this comment.
🟢 Approval recommended
The remaining findings are minor test-coverage nits with no blocking issues identified.
Pull request overview
Adds the foundational global label data model, including case-insensitive unique labels and polymorphic associations for work packages and future entities.
Changes:
- Creates
labelsandlabelingstables with indexes and cascade constraints. - Adds
Label,Labeling, andLabelablemodels and associations. - Adds factories and model coverage for validations, ordering, and deletion behavior.
File summaries
| File | Reviewed changes |
|---|---|
spec/models/work_package/work_package_labels_spec.rb |
Tests work package label behavior. |
spec/models/labeling_spec.rb |
Tests labeling validation behavior. |
spec/models/label_spec.rb |
Tests label validation and behavior. |
spec/factories/labeling_factory.rb |
Adds labeling factory. |
spec/factories/label_factory.rb |
Adds label factory. |
db/migrate/20260914120000_create_labels.rb |
Creates tables, indexes, and foreign keys. Nit (1 vote): add direct foreign-key cascade coverage. Nit (1 vote): add database-level duplicate-labeling coverage. |
app/models/work_package.rb |
Enables labels for work packages. |
app/models/labeling.rb |
Defines the polymorphic join model. |
app/models/label.rb |
Defines labels, validations, and associations. |
app/models/concerns/labelable.rb |
Adds labelable associations. |
Review details
Suppressed comments (2)
db/migrate/20260914120000_create_labels.rb:40
- This test calls
label.destroy!, so Rails'dependent: :delete_allremoves the labelings before the database foreign-key cascade is exercised. The migration explicitly promisesON DELETE CASCADE, which is also needed for direct SQL/Label.deletepaths; add a regression example that deletes the label without the association callback (or asserts the foreign key'son_deletevalue), similar tospec/models/work_package_semantic_alias_spec.rb:121-126.
t.references :label, null: false, foreign_key: { on_delete: :cascade }, index: false
db/migrate/20260914120000_create_labels.rb:46
- The duplicate-labeling example only builds the second record, so it verifies the ActiveRecord validation but never verifies this database unique index. Since the index is the protection against concurrent inserts, add a database-level duplicate insert/save-with-validation-disabled example expecting
ActiveRecord::RecordNotUnique, as the label-name index is tested inspec/models/label_spec.rb:53-58.
add_index :labelings, %i[labelable_type labelable_id label_id],
unique: true,
name: "index_labelings_on_labelable_and_label"
- Files reviewed: 10/10 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The BCF topic services forwarded every param to the work package service and relied on it ignoring keys without a setter. Work packages now have a labels association, so the BCF labels array of strings reached the association writer instead of failing the issue contract.
|
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. |
Collapses the hand-rolled presence, length and case-insensitive uniqueness examples into one-liners, matching sibling model specs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
brunopagno
left a comment
There was a problem hiding this comment.
This is pretty much it. I left some questions because I don't fully understand some details, but the PR seems in a good shape.
One extra thing though. We will need some way of identifying the author/creator of a label as per specs on the EPIC
An admin-created label has the particularity that, unlike user-created ones, it is visible on the list even if no work packages currently use it. (Else it would disappear on creation)
So we either define it in this step or we will need another migration as a follow up. I am good with either case.
|
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. |
Thanks for the reminder, last I checked we didn't have this spec! 😄 |
Label stays ignorant of which models can be labeled. Labelable types get a labeled_with scope and Label.with_usage_count aggregates usage in SQL, so the admin list never loads labeled records to count them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Admin-created labels stay listed while unused, user-created ones do not, so a label needs to know who created it. Deleting a user hands their labels to the deleted-user placeholder like other authored records. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An archived label stays on its work packages and in the admin list but is no longer offered when labeling. The timestamp doubles as the archive date. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Generated by 🚫 Danger |
|
@brunopagno addressed the following:
|
brunopagno
left a comment
There was a problem hiding this comment.
As discussed in sync, we will remove the archive behaviour, but other than that we're good to go, so approving it!
Briliant work! 🚢 🚢 🚢
This reverts commit afbdbc7. Archiving is not part of the first labels iteration.
|
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. |
https://community.openproject.org/wp/79593
First slice of labels for work packages: a global
Labelwhose names are case-insensitively unique across the instance, attached through a polymorphiclabelingsjoin so other entities can be labeled later without a schema change.Labels are hard deletable and deleting one cascades to its labelings. This is the data model only; the API, journaling and admin UI land in their own PRs.