Skip to content

Accept semantic work-package identifiers in text macros#23224

Closed
akabiru wants to merge 1 commit into
devfrom
feature/text-macro-semantic-id-clean
Closed

Accept semantic work-package identifiers in text macros#23224
akabiru wants to merge 1 commit into
devfrom
feature/text-macro-semantic-id-clean

Conversation

@akabiru
Copy link
Copy Markdown
Member

@akabiru akabiru commented May 15, 2026

Ticket

https://community.openproject.org/wp/74948

What are you trying to accomplish?

Backend support for project-based semantic identifiers in the work-package text-macro pipeline. Authors typing #PROJ-1, ##PROJ-1, or ###PROJ-1 in a comment, work-package description, or meeting body get a plain link, an inline quickinfo card, or a detailed quickinfo card — the same shapes that already work for numeric #1234, ##1234, and ###1234. Numeric references render unchanged.

Replaces #23203, which over-engineered the rendering path on the assumption that classic instances would 404 on /work_packages/PROJ-1 — they don't. The route constraint and WorkPackage.find_by_display_id accept both shapes regardless of the work_packages_identifier setting, so the text macro can interpolate the typed identifier verbatim into the href and let the route do the resolution.

Slice S4 from the parent #22976 slicing plan (replaces the originally-planned twelve-commit version with a single broadened regex plus two collateral guards).

Screenshots

Plain #PROJ-1 link in a work-package comment

##PROJ-1 inline quickinfo card

###PROJ-1 detailed quickinfo card

What approach did you choose and why?

The bare # branch of ResourceLinksMatcher.regexp widens to accept either \d+ or [A-Z][A-Z0-9_]*-\d+, reusing WorkPackage::SemanticIdentifier::ID_ROUTE_CONSTRAINT so routing, formatting, and the macro regex share one source of truth. The link handler reads matcher.identifier as a string and interpolates it into the href, label, and data-id directly — no to_i coercion, no DB lookup, no mode gate. The route layer resolves either shape transparently. #0123 still falls through to literal text via the canonical-form check on the numeric branch.

Quickinfo widgets (##PROJ-1 / ###PROJ-1) emit <opce-macro-wp-quickinfo data-id="PROJ-1"> with the user-facing identifier; the frontend macro component already handles both shapes when it calls back to APIv3. Hover-card URLs follow the same path so the URL the user sees matches the URL the link points at.

Two collateral guards ship alongside the matcher widening:

HashSeparator#applicable? rejects semantic-shape inputs digit-by-digit. Before this change, version#PROJ-1 would reach the version handler with "PROJ-1".to_i == 0 and issue a guaranteed-miss Version.find_by(id: 0). The same shape would have hit every other prefixed-resource table (documents, messages, meetings, projects, users, groups, queries). The new gate short-circuits all of them before any database round-trip.

The PDF export's WorkPackagesLinkHandler gates applicable? on digit-only identifiers. PDF rendering walks a separate Markly pipeline that does not yet resolve semantic identifiers. Without this guard, #PROJ-1 in an exported body would collapse to <mention data-id="0"> — broken-looking output that's hard to attribute. Semantic-id support on the PDF side lands separately via #23138 (community WP #74366 / #74766).

Out of scope: the CKEditor / autocomplete / mention-envelope surface (slice S5, paired with CKEditor build PR #113), and a per-render preload that batches lookups across many references in one body. The minimal path here renders without any work-package SELECTs — a regression guard in the spec asserts zero FROM "work_packages" queries for the rendering pipeline.

Merge checklist

  • RSpec — spec/lib/open_project/text_formatting/matchers/link_handlers/work_packages_spec.rb, spec/lib/open_project/text_formatting/markdown/in_tool_links_spec.rb, spec/models/exports/pdf/common/macro_spec.rb green
  • Rubocop clean
  • Manual round-trip on local dev: #PROJ-1, ##PROJ-1, ###PROJ-1 in semantic mode and in classic mode (route resolves both); PDF export of a body containing #PROJ-1 renders as literal text, not a broken mention
  • Tested major browsers (Chrome, Firefox, Edge, …)

Broaden the bare `#` branch of the text-macro regex to accept both the
numeric (`\d+`) and semantic (`[A-Z][A-Z0-9_]*-\d+`) identifier shapes,
reusing `WorkPackage::SemanticIdentifier::ID_ROUTE_CONSTRAINT` as the
single source of truth. The link handler interpolates the typed
identifier verbatim into the href, label, and `data-id`; the route layer
resolves either shape transparently via `find_by_display_id`, so no DB
lookup is needed at render time and the behaviour is mode-agnostic.

`#PROJ-1` renders as `<a href="/work_packages/PROJ-1">#PROJ-1</a>` and
`##PROJ-1` / `###PROJ-1` render as `<opce-macro-wp-quickinfo>` elements
with `data-id="PROJ-1"`. Numeric `#1234` rendering is unchanged. The
leading-zero canonical-form guard (`#123` stays literal) is preserved
on the numeric branch.

Two collateral safety guards:

- `HashSeparator#applicable?` rejects semantic-shape inputs digit-by-
  digit so `version#PROJ-1` (and every other prefixed-resource form)
  short-circuits before issuing a guaranteed-miss `find_by(id: 0)`
  against the versions / documents / messages / meetings / projects /
  users / groups / queries tables.

- The PDF export's `WorkPackagesLinkHandler` gates `applicable?` on
  digit-only identifiers. PDF rendering walks a separate Markly
  pipeline that does not yet resolve semantic ids; without this guard,
  `#PROJ-1` in an exported body would collapse to
  `<mention data-id="0">`. Semantic-id PDF support is a follow-up
  (community WP #74366 / #74766).

Refs https://community.openproject.org/wp/74948
@akabiru akabiru closed this May 15, 2026
@akabiru akabiru deleted the feature/text-macro-semantic-id-clean branch May 15, 2026 23:29
@github-actions github-actions Bot locked and limited conversation to collaborators May 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant