Use displayId in work package URLs and stateful navigation#22733
Open
Conversation
6574c91 to
530eecc
Compare
2 tasks
ac24e63 to
42408c8
Compare
6aa7c34 to
1801afd
Compare
42408c8 to
bf9dcb1
Compare
1801afd to
e37e60f
Compare
These templates were still using hard-coded #{{ item.id }} which shows
the numeric PK instead of the semantic identifier in search dropdowns
and autocomplete results.
bf9dcb1 to
a556307
Compare
Routes already accept semantic identifiers (e.g. PROJ-7) via WP_ID_URL_PATTERN, but all frontend-generated links still used numeric PKs. This wires displayId into every navigation path so the browser URL bar shows the semantic form when available. Key design decision: data-work-package-id attributes on <a> elements stay numeric — the selection/hover system is keyed by PK. Only the href gets the semantic ID via a new optional routingId parameter on UiStateLinkBuilder. Changes span table links (ID column, linked WP fields, details action), navigation handlers (list view, embedded tables, boards, BCF, calendar), breadcrumbs, tabs, hierarchy, single view, context menu, quickinfo macro, and post-creation redirect. API-only paths (time entries, share, hover cards, progress modal) are deliberately left with numeric IDs — they never appear in the address bar.
Stateful navigation changes ($state.go callers) moved to a follow-up PR with P1 bug fixes for focus/selection, card highlight, bulk delete, and cache coherence. Retained changes only modify href attributes and URL strings — click handlers still read data-work-package-id (numeric PK), so no semantic IDs leak into Angular state params.
e37e60f to
6b119b4
Compare
Routes already accept semantic identifiers (e.g. PROJ-7) via WP_ID_URL_PATTERN, but all frontend-generated links still used numeric PKs. This wires displayId into every navigation path so the browser URL bar shows the semantic form when available. Key design decision: data-work-package-id attributes on <a> elements stay numeric — the selection/hover system is keyed by PK. Only the href gets the semantic ID via a new optional routingId parameter on UiStateLinkBuilder. Changes span table links (ID column, linked WP fields, details action), navigation handlers (list view, embedded tables, boards, BCF, calendar), breadcrumbs, tabs, hierarchy, single view, context menu, quickinfo macro, and post-creation redirect. API-only paths (time entries, share, hover cards, progress modal) are deliberately left with numeric IDs — they never appear in the address bar.
Replaces 5 identical private resolveRoutingId methods with a shared utility function that accepts States as a parameter.
Defer focus and selection to init() (called after WP loads) so we use this.workPackage.id (always numeric) instead of the route param which may be a semantic identifier like "PROJ-7".
…displayId
The route param may be a semantic identifier ("PROJ-7") which won't
match workPackage.id (numeric "42"). Comparing against displayId
as well handles both classic and semantic modes.
Resolve $state.params.workPackageId to numeric PK via the States cache before comparing against the deleted IDs array.
Normalize this.workPackageId from semantic (e.g. "PROJ-7") to numeric PK after first WP load, ensuring downstream cache lookups use the canonical key.
Document the dual-purpose contract in observeWorkPackage (cache normalization), deferred focus in split view init, card highlight comparison logic, and best-effort fallback in resolveRoutingId.
e1a7da3 to
e1cac12
Compare
3 tasks
The displayId getter on WorkPackageBaseResource already handles the fallback to numeric id, so callers don't need to repeat the `displayId ?? id!` pattern. This simplifies 7 call sites across breadcrumbs, tabs, relations, single-view, and display fields. The formatting regex that decides whether to prefix with # (classic mode) or not (semantic mode) was duplicated in WorkPackageBaseResource and WorkPackageDisplayField. Extract it into a shared formatWorkPackageId() function in work-package-id-pattern.ts alongside the existing WP_ID_URL_PATTERN constant.
Clicking a semantic ID link (e.g. PROJ-42) in the work package table navigated to the numeric ID URL (/work_packages/42) instead of the semantic one (/work_packages/PROJ-42). The href was correct but the click handler (WorkPackageStateLinksHandler) read data-work-package-id (the numeric PK) and used it for navigation. Add data-routing-id to link elements carrying the semantic identifier. The click handler now reads this attribute and flows it through the stateLinkClicked event so wp-list-view uses it for URL construction while keeping the numeric PK for selection and focus state.
Absorb PR #22739 (stateful navigation changes) into this branch so all displayId routing work lives in a single PR. Conflict resolution in wp-list-view: openStateLink now prefers event.routingId from the data attribute, falling back to resolveRoutingId (state store lookup) for callers that don't carry the routing ID on the event.
Make resolveRoutingId protected in the parent class so BcfListComponent inherits it instead of redeclaring its own private copy, which TypeScript rejects as conflicting declarations.
This reverts commit a556307.
akabiru
commented
Apr 16, 2026
Comment on lines
+10
to
+27
| /** | ||
| * Format a work package identifier for inline UI display. | ||
| * | ||
| * OpenProject supports two identifier modes: | ||
| * - **Semantic**: project-scoped identifiers like `PROJ-42` that contain letters. | ||
| * These are self-describing and returned as-is. | ||
| * - **Classic**: numeric-only identifiers like `42`. | ||
| * These are prefixed with `#` to visually distinguish them as WP references. | ||
| * | ||
| * @example | ||
| * formatWorkPackageId('PROJ-42') // => 'PROJ-42' | ||
| * formatWorkPackageId('42') // => '#42' | ||
| * formatWorkPackageId('') // => '' | ||
| */ | ||
| export function formatWorkPackageId(id:string):string { | ||
| if (!id) return ''; | ||
| return /[A-Za-z]/.test(id) ? id : `#${id}`; | ||
| } |
Member
Author
|
Hi @HDinger, could you please help me examine this more closely? I’m not very familiar with these parts 🙏🏾 |
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.
Ticket
https://community.openproject.org/wp/73797
https://community.openproject.org/wp/73834
Builds on #18997. Absorbs #22739 into a single PR.
What are you trying to accomplish?
When a project has semantic work package identifiers enabled, clicking any work package link still showed the numeric PK in the browser URL bar (e.g.
/work_packages/42/activity). This changes frontend-generated URLs and stateful navigations to use the semanticdisplayIdinstead (e.g./work_packages/PROJ-7/activity), giving users readable, shareable URLs that match the identifiers they see in the UI.This PR mainly focuses on the work package list and how views- it's possible other parts are missed and will be handled separately.
What approach did you choose and why?
Semantic IDs (e.g.
PROJ-7) should appear in URLs, but internal systems (selection, focus, caching) are keyed by numeric PK. This approach cleanly separates URL routing from internal identity.Link elements now carry both
data-work-package-id(numeric PK for selection) anddata-routing-id(semantic ID for navigation). Click and navigation handlers use a sharedresolveRoutingId()helper to look up the semantic ID from the states cache. Components that already have the WP resource in scope call.displayIddirectly.Where route params flow into internal systems (split view focus, card highlight, bulk delete, WP cache), they're resolved back to numeric PKs so selection and caching remain stable. A shared
formatWorkPackageId()utility replaces duplicated#-prefix formatting.API-only paths (time entries, hover cards, modals) stay numeric since they never appear in the address bar.
Merge checklist