Skip to content

feat: visualization titles and filter line - #390

Merged
HendrikThePendric merged 36 commits into
masterfrom
feat/visualization-title-and-filter-line
Oct 9, 2026
Merged

HendrikThePendric merged 36 commits into
masterfrom
feat/visualization-title-and-filter-line

Conversation

@HendrikThePendric

@HendrikThePendric HendrikThePendric commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Implements DHIS2-22147

Description

Implements the IDV title and filter line spec:

  • a three-way title control
  • generated default titles
  • a filter line on both line lists and pivot tables

Title

Replaces the plain title input with Auto generated / None / Custom.

These three modes are derived from the title and hideTitle fields on the visualization object:

  1. hideTitle: true is None
  2. A populated title is Custom
  3. hideTitle: false with an empty/missing title is Auto

The Auto title follows the spec's label precedence: a plural custom label as-is, else a singular one as "{label} count" (PT) or "{label} list" (LL), else "Events"/"Enrollments". Tracked entities use the type's plural label, else its name. On a PT a custom value replaces this with itemName · aggregationDisplayName. The plural labels weren't fetched before, so getProgramFields and getTrackedEntityTypeFields now request them. Changing the visualization type resets the title to Auto.

The new Title component in the options modal keeps the custom text in component state, even when the mode switches to auto/none. This lets a typed title survive toggling between modes while the Style tab is open and be discarded when it closes.

The spec asks for existing visualizations with no title to keep showing none rather than suddenly gaining a generated one. We cannot rely on hideTitle for this, since on these visualizations it is always false. The old apps did not use generated titles and completely ignored the hideTitle field both when reading and writing. In DHIS2 Core it is a primitive boolean, so a visualization that never set it comes back with false. These old visualizations are marked as legacy as they are received, so we already have a good way to identify them. For these we infer the correct value of hideTitle:

  • If title is missing/empty then hideTitle should be true
  • If title is populated then hideTitle should be false

Subtitle

Even though there was technically no need to make any changes to how we work with subtitle, I decided to align this with title:

  • For legacy visualizations we infer the hideSubtitle field value from subtitle, same as we do for title
  • When saving visualizations we always populate the hideSubtitle field. If subtitle is empty/missing it gets true, if populated, it gets false

The outcome is that we can now make a very clear and simple distinction:

  1. Legacy apps (incl. LL) do not use hideTitle and hideSubtitle
  2. IDV does use them

Filter line

First some context to explain how I landed on the current approach:

  • The PT already comes with a generated filter-row. This works for DV but it would need a significant amount of work to make it work for IDV PTs.
  • LL is an internal component, but PT lives in analytics
  • There is a huge overlap between what we show in the chips and their tooltips and what we want to show in these filter lines

Based on the observations above, I decided the optimal solution should:

  1. Be implemented in the app, so that code reuse/sharing can be maximised, not just between the two visualization types but also between the chips/tooltips and the filter text.
  2. Use the exact same logic for LL and PT, for code reuse and internal consistency
  3. Allow passing a filterText prop to the PivotTable that can be used instead of the internally generated one, which this replaces for IDV PTs (added in @dhis2/analytics 29.8.0).

To allow code sharing between the chips/tooltips and the filter-text, the existing code needed to be restructured significantly. The problem was that it lived inside React hooks, and those hooks read their input from the Redux store, which the dashboard plugin isn't allowed to use. So reusing it took two steps:

  1. Extract the logic from the hooks into pure functions and place them in the modules dir
  2. Give these pure functions the same input in both places: the layout panel and the filter line. The layout panel works from visUiConfig and getVisualizationUiConfig already derives that exact shape from a visualization. So the filter line calls it and from there works with the same data the layout panel has. In both, names come from the metadata store.
Details of this restructuring
  • getConditionsMetadataIds and getConditionsTexts in @modules/conditions, out of useConditionsTexts
  • @modules/dimension/item-names.ts, out of useTooltipContentData
  • @modules/dimension/layout-dimension.ts — the metadata item → LayoutDimension mapping, out of useDimensionsWithSuffixes
  • isItemBasedDimensionType in @modules/dimension/dimension-type.ts — the rule for whether a dimension is described by its items or its conditions, out of the tooltip component
  • @modules/dimension/value-texts.ts — applies that rule and calls the helpers above; this is what both the tooltip and the filter line call
  • getSuffixContext and getDimensionLabel in @modules/dimension/suffix.ts, now shared by the line list headers and the filter line

Quality checklist


Screenshots

The title control, with Custom selected
title-control

A generated title and a filter line on a line list
filter-line

🤖 Generated with Claude Code | Edited (heavily) by @HendrikThePendric

@dhis2-bot

Copy link
Copy Markdown
Contributor

🚀 Deployed on https://pr-390.event-visualizer.netlify.dhis2.org

@dhis2-bot
dhis2-bot temporarily deployed to netlify September 24, 2026 14:19 Inactive
@HendrikThePendric
HendrikThePendric force-pushed the feat/visualization-title-and-filter-line branch from f26be52 to 7097dc0 Compare October 1, 2026 13:48
@dhis2-bot
dhis2-bot temporarily deployed to netlify October 1, 2026 13:49 Inactive
@dhis2-bot
dhis2-bot temporarily deployed to netlify October 1, 2026 14:21 Inactive
HendrikThePendric and others added 25 commits October 6, 2026 14:46
…known

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hook spec now runs against the real condition logic instead of mocking
each helper, since calls inside one module can't be intercepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…missing name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… back

The spec defines no generic fallback for tracked entity titles, and the
metadata store guarantees a name for any type it holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also drops the name and dimensionId fallbacks in toLayoutDimension, which
the metadata store already guarantees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HendrikThePendric and others added 8 commits October 6, 2026 14:47
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a git dependency's install left test files in the store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves the items-or-conditions choice out of the chip tooltip into a pure
getDimensionValueTexts, and adds a pure getSuffixContext and
getDimensionLabel. The tooltip and the line list headers now use them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deriving layout, items and conditions with getVisualizationUiConfig applies
the enrollment org unit rename, so enrollment org unit filters are named.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Legacy visualizations infer it from the subtitle on load, alongside
hideTitle, and this app now saves it as hidden whenever the subtitle is
empty, since there is no generated subtitle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@HendrikThePendric
HendrikThePendric force-pushed the feat/visualization-title-and-filter-line branch from df23b32 to 333fa46 Compare October 6, 2026 12:50
@dhis2-bot
dhis2-bot temporarily deployed to netlify October 6, 2026 12:51 Inactive
@dhis2-bot
dhis2-bot temporarily deployed to netlify October 7, 2026 13:11 Inactive
edoardo
edoardo previously approved these changes Oct 8, 2026
…title-and-filter-line

# Conflicts:
#	src/hooks/__tests__/use-conditions-texts.spec.ts
#	src/hooks/use-conditions-texts.ts
@HendrikThePendric
HendrikThePendric enabled auto-merge (squash) October 8, 2026 15:16
@dhis2-bot
dhis2-bot temporarily deployed to netlify October 8, 2026 15:17 Inactive
@dhis2-bot
dhis2-bot temporarily deployed to netlify October 8, 2026 15:22 Inactive
@sonarqubecloud

sonarqubecloud Bot commented Oct 8, 2026

Copy link
Copy Markdown

@HendrikThePendric
HendrikThePendric merged commit 78383c9 into master Oct 9, 2026
17 checks passed
@HendrikThePendric
HendrikThePendric deleted the feat/visualization-title-and-filter-line branch October 9, 2026 09:43

This branch was previously deployed

1 inactive deployment
netlify — 5f65db3d Deployed Oct 8, 2026 by dhis2-bot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants