https://community.openproject.org/wp/STC-462
Two readability passes over the work package activity tab, no behaviour change. The paginator's private methods are reordered to follow their call order so the file reads top-down from `#call`, and the three activity filter modes (`:all`, `:only_comments`, `:only_changes`) — until now bare symbols duplicated across the controller, paginator, journal components and the hidden form — move into a single `WorkPackages::ActivitiesTab::Filters` module so the modes have one source of truth and can't drift apart. The diff reaches beyond the paginator into the controller, several components and a form, since that's where the symbols were scattered.
The work package activity tab computed a per-journal sequence_version on
every render — a ROW_NUMBER() window function over a LATERAL join — only to
stamp the legacy data-anchor-activity-id that #activity-N deep links rely on.
Nothing mints those links anymore; copy and share links use
#comment-<journal id>, which needs no extra query.
The activity number is now resolved on demand. Only a request carrying
?anchor=activity-N runs the window function, mapping the number to a journal
id the paginator exposes as resolved_anchor. The view hands that to the
client, which rewrites #activity-N to the canonical #comment-<id> and scrolls
using the comment anchor already present in the DOM. Default renders no longer
touch the window function.
References WP #68063.
The Convert identifiers button sat flush against the preview table.
The table now carries its own bottom margin (mb: 3, via Primer::Box),
matching the spacing the previous hand-rolled box had.
Long identifiers overflowed into the neighbouring column. Replace the
hand-rolled flex table with the design-system BorderBoxTableComponent,
which handles column spacing, wrapping, and responsive stacking; the
project and previous-identifier columns wrap as main columns.
The full-screen icon link in the shared work-package-details tab component
constructed its href from work_package.id (numeric PK). All hosts of the
primerized split view — Boards, Notifications, etc. — inherited the numeric
URL. Switch to work_package.display_id so the full-view URL carries the
semantic identifier when one is configured.
The shared info line rendered hard-coded `#<numeric-id>` instead of
respecting the semantic identifier setting. Use WorkPackage#formatted_id
(added alongside the autocompleter/global-search work) so Relations,
hover cards, version widgets, backlogs, meetings, and time tracking
all pick up semantic IDs like "PROJ-42" in semantic mode, "#42" in
classic mode.
find_by(id:) and find_by!(id:) now raise ArgumentError when passed
a semantic identifier string, directing developers to use find() or
the new find_by_display_id() method instead. This avoids silently
altering ActiveRecord's find_by semantics and ensures misuse is
caught in development even when semantic mode is not enabled locally.
Renames find_by_id_or_identifier to find_by_display_id (public API)
and migrates all app callers that receive user-facing strings to use
the new method.
Add find_by and find_by! overrides to FinderMethods so that
find_by(id: "PROJ-42") transparently resolves semantic identifiers,
matching the existing behavior of find and exists?.
Only intercepts calls where id: is the sole keyword — all other
find_by usage (e.g. find_by(subject: ...)) passes through unchanged.
Revert ShowComponent, SplitViewComponent, and WorkPackagesController
from the explicit find_by_id_or_identifier back to standard
find_by(id:) now that the override handles semantic resolution.
Make find_by_id_or_identifier and find_by_id_or_identifier! private
since they are now internal implementation details with no external
callers. Move their test coverage into the find_by/find_by! specs.
Extract FinderMethods module that transparently resolves both numeric and
semantic identifiers (e.g. "PROJ-42") using FriendlyId's Object#friendly_id?
for dispatch. The module is included in both the WorkPackage class and
extended onto every relation, so scoped queries like
WorkPackage.visible(user).find("PROJ-42") work seamlessly.
- Override find to resolve semantic IDs via identifier column + alias table
- Override exists? with the same resolution chain
- Refactor find_by_id_or_identifier to use friendly_id? instead of semantic_id?
- Update API route to accept string IDs (type: Integer → type: String)
- Update controller and ViewComponent finders to use find_by_id_or_identifier
- Pass display_id from Rails views to Angular custom elements
The setting previously used "numeric" and "alphanumeric" as its allowed
values. Rename them to "classic" and "semantic" to better align with the
product terminology for the work package identifier modes.
Includes a migration to update any stored setting values in the database,
updated constants and helper methods on Setting::WorkPackageIdentifier,
and all corresponding references across models, components, forms,
frontend controllers, locales, and specs.
This error is intended for cases when a method is
intentionally not implemented, because the module/class defining
it expects a subclass (or class including the module) to implement
the method.
This is intended to distinguish it from other cases, such as:
* feature not implemented yet
* edge case of a method call not yet supported
Notably it avoids the misuse of the Ruby-defined NotImplementedError,
which is only intended for much more specific scenarios:
> Raised when a feature is not implemented on the current platform. For example, methods depending on the fsync or fork system calls may raise this exception [...]
Also see https://docs.ruby-lang.org/en/master/NotImplementedError.html
Aligns the class name and all internal terminology with the domain
language: "handle" → "identifier" throughout. Renames the file, class,
constants (HANDLE_MAX_LENGTH → IDENTIFIER_MAX_LENGTH, FALLBACK_HANDLE →
FALLBACK_IDENTIFIER), public API (suggest_handle → suggest_identifier,
suggested_handle hash key → suggested_identifier), keyword arguments
(in_use_handles → in_use_identifiers, reserved_handles →
reserved_identifiers), and private helper methods accordingly. All
call-sites and specs updated to match.
Replace the imperative Stimulus openConfirmDialog action (which pre-rendered
the dialog in the page and called showModal() via JS) with the idiomatic
Hotwire approach: a GET endpoint that streams the dialog on demand.
- Add confirm_dialog route and controller action using respond_with_dialog
- Include OpTurbo::Streamable in ChangeIdentifiersDialogComponent
- Convert autofix button to a link with data-turbo-stream pointing to the
new route; remove static dialog render from the form template
- Remove openConfirmDialog method from the Stimulus controller
- Update specs to use click_on and raw English strings instead of I18n.t()
Relates to #72461
- Move DISPLAY_COUNT constant from IdentifierAutofixSectionComponent to
PreviewQuery, eliminating a service-layer dependency on a view component.
The component now forwards to PreviewQuery::DISPLAY_COUNT.
- Guard PreviewQuery.new.call to only run in the :edit state.
Previously it executed on every render, hitting the DB twice per
Hotwire status-poll during the :change_in_progress phase.
- Replace nil guard in error_label with I18n.t default: "" to cover
any unrecognised error reason, not just nil.
- Add component spec for IdentifierSettingsFormComponent covering all
three states (:change_in_progress, :completed, :edit) including the
autofix-section visibility branch.
- Update preview_query_spec to reference PreviewQuery::DISPLAY_COUNT
directly instead of the UI component constant.
- Swap wrapper_data_attrs condition: poll-for-changes must only be
active in :change_in_progress state, not :edit/:completed
- Replace update_to_alphanumeric? with autofix_requested? keyed on
confirm_dangerous_action param (DangerDialog checkbox signal)
- Use ActiveRecord::Type::Boolean cast for truthy check
- Fix spec: radio group label renders as <legend>, not <h2>; add
visible: :all for hidden element assertion
Replace the two-component approach (IdentifierSettingsFormComponent +
IdentifierChangeInProgressComponent) with a single component cycling
through three lifecycle states: :edit, :change_in_progress, :completed.