poc: Multiple Categories - #24620
Draft
thykel wants to merge 14 commits into
Draft
Conversation
Introduces the experimental work_package_multiple_categories feature flag together with the user-facing setting of the same name, and Setting::WorkPackageMultipleCategories as the single predicate call sites should ask. Mirrors Setting::WorkPackageMultipleVersions so the phased rollout (adding the admin switch, then dropping the flag) only ever touches that method. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds work_package_categories as the future home of a work package's categories, backfilled from the deprecated work_packages.category_id column. Unlike work_package_versions the table needs no kind discriminator: a work package holds exactly one set of categories. Both foreign keys cascade, because categories are removed with their project through a dependent: :delete_all that skips callbacks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WorkPackage::Categories mirrors WorkPackage::Versions: a name-ordered categories association, a category_ids_replacements accessor the contracts/services flow can validate before anything is written, an effective_categories reader that accounts for a pending override, and an after_save hook that keeps the deprecated category_id column in sync with the first category. Ordering by name means #category and #categories.first can never disagree. Destroying a category now moves or prunes its join rows and recomputes the deprecated column for work packages that keep another category, which neither dependent: :nullify nor the bulk reassignment could get right on their own. #default_assign and .by_category read the set rather than the single column, so a work package with several categories takes its default assignee from the primary one and is counted under each of its categories. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The set-attributes service routes a category_ids attribute through the replacements accessor rather than the association writer generated by has_many, which would write to the database before the contract can validate. A project change reassigns the whole set to the equally named categories of the new project instead of just the single one. The contract validates assignability and enforces the two constraints of the transition: categories holds at most one entry unless the multiple categories feature is enabled, and a category_id the user writes has to be part of the set they write. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a categories link collection and schema attribute alongside the now deprecated single-valued category, following targetVersions: the attribute always reads and writes a collection, and while the multiple categories feature is inactive it keeps the "Category" label and announces the single-value restriction via options.multiple. The categories enter the work package cache checksum as an aggregated correlated subquery, since a has_many cannot be expressed by the left_joins/pluck design the checksum is built on. The neighbouring versions subquery gains the same CONCAT_WS separator, so that no two distinct tuples can concatenate to the same string and collide. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only one of the two category attributes is offered at a time, matching the query columns and the versions handling: categories with the multiple categories feature enabled, the deprecated single category without it. A saved configuration holding either name renders as whichever one the current feature state exposes, and the new gate joins the form attributes cache key. The stored-to-offered substitution now covers both attribute pairs through one map instead of a version-specific pair of locals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Snapshots a work package's categories into work_package_category_journals, diffs the whole set as a single value and renders it as one line in the activity. Existing journals are backfilled from work_package_journals' category_id, so no work package journals a spurious "categories set" change on its next save. While the deprecated category_id column mirrors the set, a category change diffs under both keys; only the set representation is exposed, and the journal entry keeps the "Category" label until the multiple categories feature is enabled. JoinedVersions is renamed to JoinedAssociation: nothing in it was version-specific, and the categories formatter needs exactly the same rendering. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Introduces the categories column, offered instead of the deprecated single category once the multiple categories feature is enabled. Both sort and group through the join table, so the category set is the group key and a work package holding several categories forms a group of its own. The category filter matches against the join table as well, so it finds work packages by any of their categories. Unlike versions there is no second filter to add: categories form a single set, and the API name derived from the existing key is the one the new attribute would want anyway. Only the label follows the feature. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Formats the categories column as the joined category names in CSV, XLS and PDF exports, and the deprecated category column from the same data. The workPackageValue macro maps the deprecated attribute to the set it is replaced by, alongside the equivalent version mapping. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bulk edit form writes through category_ids, with the feature only toggling whether the select accepts a single value or several. Like target_version_ids, the array parameter is normalized separately from the generic scalar "none"/blank transforms. TargetVersionNormalization becomes MultiValueAttributeNormalization, since nothing in it was version-specific and both array parameters need the same magic-value handling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a Categories keyword that assigns the whole comma-separated set. Both it and the deprecated Category keyword are always read so neither leaks into the description, and the plural one wins when both are given. The single-value limit stays with the contract, so the full list is parsed regardless of the feature state. Unlike versions the category keyword is not overridable by default, so the categories keyword is overridable exactly when the deprecated one is. The default overridable attributes move into a constant, keeping assign_options below the complexity threshold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Duplicating a work package carries its whole category set over. A project copy points the copied work packages at the categories copied by Projects::Copy::CategoriesDependentService rather than relying on the reassign-by-name fallback, which cannot tell equally named categories apart. The deprecated category_id is written alongside so it does not contradict the set, as for the versions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Notification mails, the work package summary report and the automatic subject tokens all read the category set instead of the deprecated column, listing every category once the multiple categories feature is enabled and keeping the single "Category" label and value until then. The summary heading stays singular either way, since it names a single category per section. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The categories attribute always reads and writes a collection, but the schema restricts it to a single value while the multiple categories feature is inactive. In that mode the field mimics the single select it stands in for: an explicit "-" option, no save/cancel controls, saving right on selection. That behaviour is extracted from the versions field into a shared SingleOrMultiSelectEditFieldComponent, since it belongs to any attribute replacing a deprecated single-valued one. Categories add nothing on top: unlike versions they are never shared across projects and cannot be created from within the field. Displaying the attribute follows []Version throughout: the resources field in tables, one line per category in the single view, and a comma-separated list under the singleline layout the macro component maps the deprecated attribute to. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AI-generated prototype.
Previous PR:
Ticket
What are you trying to accomplish?
Screenshots
What approach did you choose and why?
Merge checklist