poc: Full Observed Versions support - #24606
Conversation
Mirrors #effective_target_versions so that pending, not yet persisted overrides of the observed version set are visible to readers such as the APIv3 representer. Unlike the target kind there is no deprecated single-value column to reconcile, so only the override is considered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2eeab41 to
821f5cd
Compare
Adds the link, embed and setter to the work package representer and the matching schema property, whose allowed values come from the contract's assignable_observed_in_versions (all shared versions, closed ones included). The attribute is always a collection, so options.multiple is unconditionally true - there is no single-valued field it stands in for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
observedInVersions is now part of the representation, so a change to an observed version has to invalidate the cached JSON. The correlated subquery drops its kind restriction and aggregates the kind alongside the version, so that moving a version between kinds is detected as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sortable and groupable through the same aggregated subqueries the target versions column uses, restricted to the observed_in kind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
821f5cd to
a33d392
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.
Pull request overview
This PR extends OpenProject’s multi-version infrastructure by adding end-to-end support for “Observed versions” (observedInVersions / observed_in_versions) across API v3 representations and schema, query filters/sorting, journaling, exports (PDF/XLS), and frontend editing.
Changes:
- Add
observedInVersionsas a first-class API v3 work package link collection (representer + schema + docs) and include it in cache checksum calculation. - Add query support for observed versions (filter, select/sort/group SQL) and wire it into the WorkPackage query filter set.
- Journal and export observed versions, including a new shared journal formatter for joined version lists and additional eager loading for journal activities.
Reviewed changes
Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb | Updates PDF export expectations to include “Observed versions” in details output. |
| spec/lib/api/v3/work_packages/eager_loading/cache_checksum_integration_spec.rb | Adjusts checksum expectation so observed-in version changes bust the cache. |
| modules/xls_export/app/models/xls_export/work_package/exporter/xls.rb | Eager-loads observed_in_versions for XLS exports. |
| lib/open_project/journal_formatter/target_versions.rb | Refactors TargetVersions formatter to inherit from new JoinedVersions. |
| lib/open_project/journal_formatter/observed_in_versions.rb | Adds journal formatter for observed-in versions. |
| lib/open_project/journal_formatter/joined_versions.rb | Introduces shared formatter for rendering comma-joined version name lists from stored ID strings. |
| lib/api/v3/work_packages/work_package_representer.rb | Exposes observed_in_versions as associated resources/links and adds it to representable attrs order. |
| lib/api/v3/work_packages/schema/work_package_schema_representer.rb | Adds schema entry for observed_in_versions as always-multi-valued collection. |
| lib/api/v3/work_packages/schema/specific_work_package_schema.rb | Delegates assignable_observed_in_versions to contract. |
| lib/api/v3/work_packages/eager_loading/checksum.rb | Extends versions checksum to include all work_package_versions kinds (incl. observed_in). |
| lib/api/v3/queries/schemas/observed_in_versions_filter_dependency_representer.rb | Adds filter dependency representer class for observed-in versions filter. |
| lib/api/v3/activities/activity_eager_loading_wrapper.rb | Switches eager loading from kind-scoped to unified work_package_version_journals. |
| lib_static/plugins/acts_as_journalized/lib/journal_changes.rb | Adds observed_in_versions changeset diffing and refactors change aggregation. |
| frontend/src/app/shared/components/fields/edit/field-types/versions-edit-field.component.ts | Updates documentation to cover both version collections in the versions edit field. |
| frontend/src/app/shared/components/fields/edit/edit-field.initializer.ts | Registers versions edit field for observedInVersions in addition to targetVersions. |
| frontend/src/app/features/work-packages/components/wp-edit/work-package-changeset.ts | Excludes empty targetVersions/observedInVersions link collections on create payloads. |
| docs/api/apiv3/tags/work_packages.yml | Documents observedInVersions in the work packages tag table. |
| docs/api/apiv3/components/schemas/work_package_write_model.yml | Adds observedInVersions to the write model schema docs. |
| docs/api/apiv3/components/schemas/work_package_schema_model.yml | Adds observedInVersions to schema model docs. |
| docs/api/apiv3/components/schemas/work_package_model.yml | Adds observedInVersions to work package model docs + example. |
| config/locales/en.yml | Adds English label for observed_in_versions. |
| config/initializers/export_formats.rb | Registers export formatter for observed_in_versions. |
| app/services/work_packages/activities_tab/paginator.rb | Eager-loads unified work_package_version_journals for activities tab pagination. |
| app/services/journals/create_service/work_package_version.rb | Expands journaling to include observed_in kind. |
| app/models/work_package/versions.rb | Adds observed-in associations and effective_observed_in_versions for pending overrides. |
| app/models/work_package/journalized.rb | Registers observed_in_versions as a formatted journal field. |
| app/models/work_package/exports/formatters/observed_in_versions.rb | Adds export formatter producing observed-in version names. |
| app/models/queries/work_packages/selects/property_select.rb | Adds observed-in versions select with SQL for sorting and grouping. |
| app/models/queries/work_packages/filter/version_filter.rb | Switches to generic FilterOnWorkPackageVersionsMixin and defines version_kind. |
| app/models/queries/work_packages/filter/target_versions_filter.rb | Switches to generic mixin and defines version_kind. |
| app/models/queries/work_packages/filter/observed_in_versions_filter.rb | Adds observed-in versions query filter using generic mixin. |
| app/models/queries/work_packages/filter/filter_on_work_package_versions_mixin.rb | Generalizes target-version-only mixin to kind-based version filtering. |
| app/models/queries/work_packages.rb | Registers the new ObservedInVersions filter in WorkPackage queries. |
| app/models/journal.rb | Registers ObservedInVersions formatter and derives kind-scoped version journal arrays in memory. |
| app/models/activities/fetcher.rb | Updates activity journal eager loading to use unified version journals association. |
| app/contracts/work_packages/base_contract.rb | Adds attribute contract entry for observed_in_versions with assignability validation. |
FilterOnTargetVersionsMixin becomes FilterOnWorkPackageVersionsMixin, parameterised over the work_package_versions kind its including filter declares, so the new filter shares the operator handling of the existing ones. The APIv3 filter dependency representer is a subclass of the version one, whose allowed values are identical; a dedicated class is needed only because the factory resolves representers by filter class name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds observed_in to the journaled kinds, an observed_in_version_journals association to read them back, and a journal change that diffs the whole set as one value - the same shape the target versions change uses. The rendering logic both formatters share moves into a JoinedVersions base class. Activity fetchers eager load the new association alongside the target one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the export formatter that renders the version names, so the column works in CSV, XLS and PDF exports as well as in workPackageValue macros, and eager loads the association in the XLS exporter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Registers the attribute with VersionsEditFieldComponent so it gets the project grouping and inline version creation the target versions field has. The empty collection stripping on create, which the API rejects as an empty array of links, is generalised over both version attributes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
a33d392 to
f938344
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 39 changed files in this pull request and generated no new comments.
Suppressed comments (1)
lib/api/v3/work_packages/eager_loading/checksum.rb:70
- The checksum aggregation concatenates
wpv.kind,v.id, andv.updated_atwithout any delimiter. This can theoretically create ambiguous strings (different tuples producing the same concatenated value), which could cause cache checksum collisions and stale API caching. Add explicit separators between the concatenated parts to make the checksum input unambiguous.
VERSIONS_CHECKSUM_SQL = <<~SQL.squish
(SELECT COALESCE(STRING_AGG(CONCAT(wpv.kind, v.id, v.updated_at), ',' ORDER BY wpv.kind, v.id), '')
FROM work_package_versions wpv
INNER JOIN versions v ON v.id = wpv.version_id
WHERE wpv.work_package_id = work_packages.id)
Ticket
What are you trying to accomplish?
Screenshots
What approach did you choose and why?
Merge checklist