feat: add raw data table under each dashboard chart - #1502
Merged
Conversation
One row per Answer beneath each chart, with every Answer field and the AnswerValues pivoted into one column per question (multi-select exploding into one column per option). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnP42zmHAgo2NZQsa6zdhG
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnP42zmHAgo2NZQsa6zdhG
CLAUDE.md forbids committing plugin code under eform-angular-frontend; the plan wrongly instructed a commit per backend task there. Plugin commits happen only in this repo after devgetchanges.sh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnP42zmHAgo2NZQsa6zdhG
Each chart already renders an aggregated table of percentages and counts.
This adds a second, collapsible table showing one row per Answer, with
every field the Answer holds in the database plus its AnswerValues
pivoted into one column per question. Multi-select questions expand to
one column per option, each cell carrying the chosen option's own name.
Backend:
- RawDataController exposes a paged endpoint and an xlsx export.
- AnswerFilterHelper reproduces the answer-selection rules of
ChartDataHelpers.CalculateDashboardItem predicate for predicate, with
each one annotated with its source line, so the table's row count
reconciles with the chart above it. Two documented deviations: it also
filters Answer.WorkflowState, and it replaces the materialised
filter-question id list with an equivalent correlated subquery.
- RawDataColumnBuilder derives the column set from the survey's questions
and options; RawDataTranslations resolves question and option text by
the logged-in user's language, since Answer.LanguageId is hardcoded to
Danish on import and is not trustworthy.
- Cell values are resolved by question kind rather than by option name:
the SDK auto-generates options whose translation is the type keyword
("smiley1", "number", "text", "next"), so smileys read from the weight
ladder and free-text/number read from AnswerValue.Value.
- Paging always breaks ties on Id; without it, answers sharing a
FinishedAt could be duplicated and skipped across pages.
- Text-type items are rejected: ChartDataHelpers filters them down a
different branch that this helper deliberately does not mirror.
Frontend:
- dashboard-raw-data-view renders an mtx-grid whose columns are built
from the server's column descriptors, with server-side paging and
sorting, a column picker for the audit fields, and an Excel export.
Data loads on first expand so the dashboard response does not grow.
Excel export is capped at 25000 rows and fails with a localized message
rather than exhausting memory; batching the id lookup would lift it.
Verified: dotnet build and ng build clean, 6 jest tests passing (checked
non-vacuous by mutation). The Playwright specs added here have not yet
been executed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnP42zmHAgo2NZQsa6zdhG
The PR previously relied on Playwright specs that had never been run, and
on nothing at all for the backend. This adds NUnit coverage to
InsightDashboard.Pn.Test, which the insight-dashboard-test-dotnet check
already runs against a 420_SDK database seeded from 420_SDK.sql.
RawData_RowSet_MatchesChartAnswers asserts the table's row set equals the
answers the chart plotted, for every dashboard fixture already used by
ChartDataUTests. The expected set is restated independently from the spec
rather than copied from AnswerFilterHelper, so the two cannot drift
together, and every returned answer is checked to actually carry a value
for the measured question.
RawData_Columns_CoverEveryQuestion asserts one column per question in
QuestionIndex order, multi questions expanded to one column per option
with the question label as prefix, and the audit columns present but
hidden by default.
RawData_Cells_ShowAnswersNotOptionKeywords guards the defect found in
review: the SDK names auto-generated options after the question type
("smiley1", "number", "text", "next"), so a naive implementation renders
the keyword instead of the answer.
To make cell resolution testable, the private ResolveSingleValue moves
out of RawDataService into a public RawDataValueResolver, which also owns
the not-answered sentinel and the skipped-question check. Behaviour is
unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnP42zmHAgo2NZQsa6zdhG
CI ran these for the first time and three of four failed. All three were defects in the test code, not the feature - the run showed the table rendering 137 answers correctly. - Every test shares one page from beforeAll, so each unconditional toggle click alternated the disclosure open and shut. The third test therefore collapsed what the second had opened and could not find the grid. Expanding now goes through ensureExpanded(), which only clicks when the grid is absent. - Row count was asserted the instant the grid became visible, before the response had rendered any rows. Now waits on the first row with an auto-retrying assertion. - chartAmountTotal() read the last cell of the wrong row, yielding 5 against 137 actual answers. Rather than repair a fragile scrape, the comparison is dropped: RawDataUTests.RawData_RowSet_MatchesChartAnswers already reconciles answer sets against the database, which is both stricter and stable. The Playwright spec now checks only that the UI is self-consistent, and the unused helper is removed. The multi-select column check is skipped when the seed survey has no multi question, since that is a property of the fixture rather than of the feature, and the expansion itself is asserted in RawDataUTests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnP42zmHAgo2NZQsa6zdhG
This was referenced Jul 29, 2026
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.
Each chart already renders an aggregated table of percentages and counts. This adds a second, collapsible table showing one row per
Answer— every field the Answer holds in the database, plus itsAnswerValues pivoted into one column per question.Design and plan are committed alongside the code under
docs/superpowers/.Shape
QuestionIndex, labelled"N – text".multiquestions expand to one column per option, each cell carrying the chosen option's own name.Notable implementation points
AnswerFilterHelperreproduces the answer-selection half ofChartDataHelpers.CalculateDashboardItempredicate for predicate, each annotated with its source line, so the table's row count reconciles with the chart above it. Two documented deviations: it also filtersAnswer.WorkflowState, and it replaces the materialised filter-question id list with an equivalent correlated subquery.smiley1,number,text,next), so preferring the option name would rendertextinstead of the answer andsmiley1instead ofMeget glad. Smileys read from the weight ladder; free-text and number read fromAnswerValue.Value.Answer.LanguageIdis hardcoded to Danish on import and is not trustworthy.Id; without it, answers sharing aFinishedAtcould be duplicated and skipped across pages.ChartDataHelpersfilters those down a different branch this helper deliberately does not mirror.Scope
Plugin only. No
-basechange, no EF migration, no SDK change — read-only over existing SDK tables.Verification
dotnet buildandng buildcleanKnown gaps, called out rather than buried:
ChartDataHelpersis not refactored onto the shared helper — it is 4 274 lines driving every chart in the product, so the two can drift. Tracked as a follow-up in the spec.🤖 Generated with Claude Code
https://claude.ai/code/session_01EnP42zmHAgo2NZQsa6zdhG