Skip to content

[74625] Support async loading in FilterableTreeView - #451

Merged
HDinger merged 10 commits into
mainfrom
feature/74625-make-project-hierarchy-collapsable-in-the-global-project-selector-3
May 19, 2026
Merged

[74625] Support async loading in FilterableTreeView#451
HDinger merged 10 commits into
mainfrom
feature/74625-make-project-hierarchy-collapsable-in-the-global-project-selector-3

Conversation

@HDinger

@HDinger HDinger commented May 7, 2026

Copy link
Copy Markdown
Collaborator

What are you trying to accomplish?

Extends FilterableTreeView with an optional src: attribute that activates server-side filtering mode. When set, filter interactions no longer happen client-side but the component sends debounced requests to the server and replaces the current tree with the filtered response.
Further, the component now also supports select_variant: :none to allow rendering links for navigational contexts

List the issues that this change affects.

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

Risk Assessment

  • Medium risk changes that are isolated, reduced in scope or could impact few users. The change will not impact library availability.

What approach did you choose and why?

Behavior

Filtering

  • On mount, the component fetches the full tree from src
  • Typing in the filter field triggers a debounced (300 ms) request; the server receives query, filter_mode, and include_sub_items as query parameters
  • All matching results and their ancestor nodes are expanded automatically
  • Matching text is highlighted in the returned tree
  • A no-results message is shown when the server returns an empty tree
  • A loading skeleton is visible while a request is in-flight

Expansion state

  • The expansion state is snapshotted before the first filter is applied and restored when the filter is cleared

Selection persistence

  • Checked nodes are preserved across tree replacements using data-node-id
  • Nodes that are checked but filtered out of the DOM are still included in form submissions via injected hidden inputs (data-filterable-tree-view-retained), ensuring a complete payload regardless of what is currently visible

Include sub-items

  • Toggling "Include sub-items" does not reload the tree; visual state is updated client-side only
  • In async mode, include_sub_items is included in form submissions so the server can resolve all descendants

Usage

  <%= render(Primer::OpenProject::FilterableTreeView.new(src:
  my_path)) %>                                                 

The server endpoint must return a HTML fragment. Each node should carry a stable data-node-id on its [role=treeitem] element.

Merge checklist

  • Added/updated tests
  • Added/updated documentation
  • Added/updated previews (Lookbook)
  • Tested in Chrome
  • Tested in Firefox
  • Tested in Safari
  • Tested in Edge

@changeset-bot

changeset-bot Bot commented May 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bf466f0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@openproject/primer-view-components Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown

⚠️ Visual or ARIA snapshot differences found

Our visual and ARIA snapshot tests found UI differences. Please review the differences by viewing the files changed tab to ensure that the changes were intentional.

Review differences

@HDinger
HDinger force-pushed the feature/74625-make-project-hierarchy-collapsable-in-the-global-project-selector-3 branch from 474604c to c8b856f Compare May 11, 2026 08:30
@HDinger
HDinger force-pushed the feature/74625-make-project-hierarchy-collapsable-in-the-global-project-selector-3 branch from b2dfdfb to 3a1d770 Compare May 11, 2026 09:13
@HDinger
HDinger marked this pull request as ready for review May 11, 2026 13:11
Comment thread app/components/primer/open_project/filterable_tree_view/sub_tree.rb Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Primer::OpenProject::FilterableTreeView with an optional src: attribute to enable async (server-side) filtering, adds support for select_variant: :none (for link/navigation trees), and introduces a new trailing action slot for Primer::Alpha::TreeView nodes. It also adds previews, a demo controller/endpoint, and test coverage for the new async behaviors.

Changes:

  • Add async filtering mode to FilterableTreeView via src: with debounced fetching, tree replacement, selection/expansion persistence, retained hidden inputs, and a loading skeleton.
  • Add select_variant: :none support to allow rendering navigational/link nodes.
  • Add TreeView trailing action support (TrailingAction, slot plumbing, CSS, and previews).

Reviewed changes

Copilot reviewed 41 out of 80 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
test/system/open_project/filterable_tree_view_test.rb System tests covering async loading, filtering, expansion/selection persistence, and form submission behaviors.
test/components/primer/open_project/filterable_tree_view_test.rb Component tests for src:, select variants (including :none), and form inclusion/exclusion behavior.
app/components/primer/open_project/filterable_tree_view.rb Adds src: API, supported select variants update, and guards against tree_view_arguments in async mode.
app/components/primer/open_project/filterable_tree_view.ts Implements async fetch/replace flow, debouncing, selection/expansion snapshots, and retained hidden inputs.
app/components/primer/open_project/filterable_tree_view.html.erb Updates layout, adds scroll container + loading skeleton, and adjusts form-exclusion behavior for inputs.
app/components/primer/open_project/filterable_tree_view.pcss Adds component styling (layout, loading skeleton visibility, highlight styles).
app/components/primer/primer.pcss Imports the new filterable_tree_view.pcss stylesheet.
app/components/primer/open_project/filterable_tree_view/sub_tree.rb Expands supported select variants and updates async-subtree error messaging.
app/components/primer/alpha/tree_view/trailing_action.rb New component to render a trailing action wrapper for TreeView nodes.
app/components/primer/alpha/tree_view/trailing_action.html.erb Template for trailing action container.
app/components/primer/alpha/tree_view/node.rb Adds a generic trailing_action slot and data attribute for CSS layout.
app/components/primer/alpha/tree_view/node.html.erb Renders the trailing action when present.
app/components/primer/alpha/tree_view/leaf_node.rb Adds with_trailing_action_button helper for leaf nodes.
app/components/primer/alpha/tree_view/leaf_node.html.erb Wires trailing action into the node composition.
app/components/primer/alpha/tree_view/sub_tree_node.rb Adds with_trailing_action_button helper for sub-tree nodes.
app/components/primer/alpha/tree_view/sub_tree_node.html.erb Wires trailing action into the node composition.
app/components/primer/alpha/tree_view.pcss Updates TreeView grid layout to accommodate trailing actions and adjusts alignment.
previews/primer/open_project/filterable_tree_view_preview.rb Adds Lookbook previews for async mode, async form input, and link nodes; updates select_variant param options.
previews/primer/open_project/filterable_tree_view_preview/async.html.erb New async preview template using src:.
previews/primer/open_project/filterable_tree_view_preview/async_form_input.html.erb New async + form preview template for retained selections and include_sub_items submission.
previews/primer/open_project/filterable_tree_view_preview/link_nodes.html.erb New preview demonstrating select_variant: :none link-style nodes.
previews/primer/alpha/tree_view_preview.rb Adds trailing_action_icon param to the TreeView leaf node playground.
previews/primer/alpha/tree_view_preview/leaf_node_playground.html.erb Renders a trailing action button when configured in the playground.
config/routes.rb Adds routes/endpoints for the async filterable tree demo responses.
app/controllers/primer/view_components/filterable_tree_view_items_controller.rb Demo controller providing server-rendered <tree-view> fragments for async mode previews/tests.
app/views/primer/view_components/filterable_tree_view_items/index.html.erb Server-rendered <tree-view> fragment for async filtering results.
app/views/primer/view_components/filterable_tree_view_items/async_form_tree.html.erb Server-rendered <tree-view> fragment variant configured for form submissions.
app/views/primer/view_components/filterable_tree_view_items/_node.html.erb Partial to render demo nodes with stable data-node-id and expanded hierarchy during filtering.
.playwright/screenshots/snapshots.test.ts-snapshots/primer/open_project/filterable_tree_view/default/aria-snapshot.yml Updated ARIA snapshot due to toolbar layout changes.
.playwright/screenshots/snapshots.test.ts-snapshots/primer/open_project/filterable_tree_view/default/aria-snapshot--after-interaction.yml Updated ARIA snapshot after interaction.
.playwright/screenshots/snapshots.test.ts-snapshots/primer/open_project/filterable_tree_view/custom_segmented_control/aria-snapshot.yml Updated ARIA snapshot for custom segmented control preview.
.playwright/screenshots/snapshots.test.ts-snapshots/primer/open_project/filterable_tree_view/custom_segmented_control/aria-snapshot--after-interaction.yml Updated ARIA snapshot after interaction.
static/previews.json Regenerated static preview metadata for new previews.
static/info_arch.json Regenerated info-architecture metadata for new slots/components and updated docs.
static/arguments.json Regenerated argument metadata including src: and trailing action component.
static/constants.json Regenerated constants mapping including new TrailingAction and supported select variants.
static/classes.json Regenerated CSS class mapping for new FilterableTreeView classes.
static/statuses.json Regenerated status map including new alpha component.
static/audited_at.json Regenerated audited-at map including new alpha component.
.changeset/loud-paths-taste.md Changeset for trailing action addition.
.changeset/afraid-poets-sing.md Changeset for async loading strategy.

Comment thread app/components/primer/open_project/filterable_tree_view/sub_tree.rb
Comment thread .changeset/afraid-poets-sing.md Outdated
Comment thread .changeset/loud-paths-taste.md Outdated
Comment thread previews/primer/open_project/filterable_tree_view_preview/link_nodes.html.erb Outdated
Comment thread previews/primer/open_project/filterable_tree_view_preview/link_nodes.html.erb Outdated
Comment thread previews/primer/open_project/filterable_tree_view_preview/link_nodes.html.erb Outdated
Comment thread previews/primer/open_project/filterable_tree_view_preview/link_nodes.html.erb Outdated
Comment thread app/components/primer/open_project/filterable_tree_view/sub_tree.rb Outdated
Comment thread app/components/primer/open_project/filterable_tree_view.html.erb Outdated
Comment thread app/components/primer/open_project/filterable_tree_view.rb Outdated
Comment thread app/components/primer/open_project/filterable_tree_view.rb Outdated
Comment thread app/components/primer/open_project/filterable_tree_view.rb Outdated
Comment thread app/components/primer/open_project/filterable_tree_view.ts Outdated
Comment thread app/controllers/primer/view_components/filterable_tree_view_items_controller.rb Outdated
Comment thread app/components/primer/alpha/tree_view/trailing_action.html.erb
Comment thread app/controllers/primer/view_components/filterable_tree_view_items_controller.rb Outdated
Comment thread app/controllers/primer/view_components/filterable_tree_view_items_controller.rb Outdated
Comment thread app/controllers/primer/view_components/filterable_tree_view_items_controller.rb Outdated
Comment thread app/controllers/primer/view_components/filterable_tree_view_items_controller.rb Outdated
Comment thread app/components/primer/open_project/filterable_tree_view.ts

@myabc myabc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the exception of the Preview controllers, the Ruby code looks good. 👍🏻

Functionally, this seems acceptable as an interim solution, though I'm still not fully comfortable with the amount of manual, imperative state synchronisation between the frontend and backend. From a UX perspective, the flickering during filtering makes the experience feel a little less polished than we'd ideally want ^*.

Screen.Recording.2026-05-12.at.21.10.51.mov

We already have at least two mechanisms for loading HTML from the backend: Primer's include-fragment and Turbo frames. I would prefer not to introduce another. If we make raw fetch requests, we need to a) allow custom headers to be passed in (CSRF-Token, credentials, etc.) and b) make sure callers don't forget.

I still need to review the downstream PR.

^* I created a small experiment (#452) to see if Idiomorph might help but didn't get very far. The flicker is still present - it's not simply a result of recreating DOM nodes.

@HDinger
HDinger force-pushed the feature/74625-make-project-hierarchy-collapsable-in-the-global-project-selector-3 branch from 7bb0691 to 8fc6557 Compare May 13, 2026 11:42
@HDinger

HDinger commented May 13, 2026

Copy link
Copy Markdown
Collaborator Author

Functionally, this seems acceptable as an interim solution, though I'm still not fully comfortable with the amount of manual, imperative state synchronisation between the frontend and backend.

Yeah, I am also not 100% happy about this but I did not find good alternative given that the downstream endpoints should not care about all of this..

From a UX perspective, the flickering during filtering makes the experience feel a little less polished than we'd ideally want ^*.

We could increase the debounce a bit, but I guess there will always be some kind of visual flickering. This is the case in the current (angular) project selector as well.

We already have at least two mechanisms for loading HTML from the backend: Primer's include-fragment and Turbo frames. I would prefer not to introduce another. If we make raw fetch requests, we need to a) allow custom headers to be passed in (CSRF-Token, credentials, etc.) and b) make sure callers don't forget.

I tried with include-fragment at first (see #449). However, I did not really manage with that approach which is why I switched.

* Add tests for TrailingActionButton
* Fix eslint issues
* Include review feedback
@HDinger
HDinger force-pushed the feature/74625-make-project-hierarchy-collapsable-in-the-global-project-selector-3 branch from 608b7af to cf4723e Compare May 13, 2026 12:10
@HDinger
HDinger requested a review from myabc May 13, 2026 12:10
Comment thread app/components/primer/open_project/filterable_tree_view.ts
Comment thread app/components/primer/open_project/filterable_tree_view.ts

try {
const response = await fetch(url.toString(), {signal, headers: {Accept: 'text/html'}})
if (!response.ok) return

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need more error handling than this?

@myabc myabc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. However it would be good to check with @oliverguenther about fetch headers (and also potentially CSP - which is something include-fragment-element attempts to handle)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 83 changed files in this pull request and generated no new comments.

@HDinger
HDinger merged commit fe16998 into main May 19, 2026
28 of 32 checks passed
@HDinger
HDinger deleted the feature/74625-make-project-hierarchy-collapsable-in-the-global-project-selector-3 branch May 19, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants