Add LearnersReport component with learner table and empty states#14497
Add LearnersReport component with learner table and empty states#14497AllanOXDi wants to merge 8 commits intolearningequality:release-v0.19.xfrom
Conversation
b0cd765 to
a9ff944
Compare
a9ff944 to
52429d6
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
New LearnersReport table, LearnerSidePanel, and supporting i18n strings for per-learner score detail with risk-level badges.
CI passing. No linked issues; PR description is empty (no summary, screenshots, or AI usage disclosure). PR targets release-v0.19.x — not the default branch (develop) — and adds ~20 new i18n strings; see blocking note below.
- blocking: Two i18n composition issues where numbers are rendered outside translated strings, breaking locale-aware formatting and translator word-order control (see inline comments)
- blocking: New translatable strings added on a non-default branch (
release-v0.19.xvsdevelop) - suggestion: PR description is entirely template placeholders — no summary, screenshots, or AI usage disclosure
- suggestion:
::v-deep theadoverride in LearningObjectivesReport couples to KTable internals - praise: Thorough test coverage with boundary-value tests for risk-level thresholds and LO struggling bands
- praise: Good a11y:
aria-labelon LO score spans,aria-hiddenon decorative elements, and keyboard-navigable learner links
Phase 3 (UI verification) skipped — no screenshots in PR body and no dev server available.
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Reviewed the pull request diff checking for:
- Correctness: bugs, edge cases, undocumented behavior, resource leaks, hardcoded values
- Design: unnecessary complexity, naming, readability, comment accuracy, redundant state
- Architecture: duplicated concerns, minimal interfaces, composition over inheritance
- Testing: behavior-based assertions, mocks only at hard boundaries, accurate coverage
- Completeness: missing dependencies, unupdated usages, i18n, accessibility, security
- Principles: DRY (same reason to change), SRP, Rule of Three (no premature abstraction)
- Checked CI status and linked issue acceptance criteria
- For UI changes: inspected screenshots for layout, visual completeness, and consistency
| class="stats-value" | ||
| :style="{ color: $themeTokens.annotation }" | ||
| > | ||
| <strong>{{ loCompletedCount }} {{ losCompletedOfLabel$({ total: loTotalCount }) }}</strong> |
There was a problem hiding this comment.
blocking: loCompletedCount is rendered outside the translated string, which prevents locale-aware number formatting (digit grouping, decimals) and blocks translators from controlling word order.
The existing project pattern puts numbers inside i18n strings — e.g., nOfMLearners$({ n, m }) uses '{n, number} of {m, number} learners'. This should follow the same approach.
Consider combining the count into a single string:
losCompletedLabel: {
message: '{completed, number} of {total, number} LOs completed',
context: '...'
}Then use <strong>{{ losCompletedLabel$({ completed: loCompletedCount, total: loTotalCount }) }}</strong> — the bold wraps the whole phrase rather than just the number, but i18n correctness takes priority over partial styling.
| class="warning-icon" | ||
| /> | ||
| {{ strugglingWithObjectivesPrefixLabel$() }} | ||
| <b>{{ strugglingCount }} {{ strugglingWithObjectivesSuffixLabel$({ count: strugglingCount }) }}</b> |
There was a problem hiding this comment.
blocking: The "struggling" warning composes a sentence from three separate elements: a prefix string, a raw number, and a suffix string. This prevents translators from reordering the sentence (some languages place the number after the noun, or restructure the phrase entirely).
The strugglingCount between the prefix and suffix isn't passed through ICU {count, number} formatting, so it also won't get locale-aware digit grouping.
Consider merging the prefix, count, and suffix into a single ICU string:
strugglingWithObjectivesLabel: {
message: 'Learner is struggling with {count, number} {count, plural, one {learning objective} other {learning objectives}}',
context: '...'
}This loses the <b> on the count, but you could wrap the whole phrase in <b> or use a <span> with font-weight: bold on the container if emphasis is needed.
| supportNeededLabel: { | ||
| message: 'Support needed', | ||
| context: | ||
| 'Risk level badge shown in the Learners report for learners whose aggregate score is ≤45%', |
There was a problem hiding this comment.
blocking: This PR adds ~20 new translatable strings but targets release-v0.19.x, not the default branch (develop). New strings on non-default branches typically cause problems because the i18n extraction and translation pipeline runs against the default branch.
Is this intentional — is the courses feature being developed exclusively on the release branch? If so, how will these strings get picked up for translation?
|
|
||
| /* Visually hide KTable headers while keeping them accessible to screen readers */ | ||
| .lo-report-table thead { | ||
| .lo-report-table ::v-deep thead { |
There was a problem hiding this comment.
suggestion: ::v-deep thead reaches into KTable's internal DOM to visually hide headers. This is fragile — if KTable changes its markup (e.g., wraps <thead> or uses a different element), this breaks silently.
Does KTable offer a prop to hide column headers while keeping them accessible? If not, this approach works, but a comment noting the coupling would help future maintainers.
| <span class="lo-text">{{ lo.text }}</span> | ||
| <span | ||
| class="lo-score" | ||
| :aria-label="xOfYCorrectLabel$({ correct: lo.correct, total: lo.numQuestions })" |
There was a problem hiding this comment.
praise: Good accessibility — aria-label on score spans provides a complete "X of Y correct" reading for screen readers, while aria-hidden on the visual-only number and "of N" text prevents double-reading. Well done.
| expect(screen.getByText(onTrackLabel$())).toBeInTheDocument(); | ||
| }); | ||
|
|
||
| it('shows "Support needed" (red) for learner at exactly 45%', () => { |
There was a problem hiding this comment.
praise: Thorough boundary-value testing — the 45% exact boundary (lines 163–182) and 60%+ boundary (lines 184–189) tests catch off-by-one errors in the risk-level thresholds. The LearnerSidePanel tests similarly test the 80% struggling boundary. This is exactly the kind of edge-case coverage that prevents subtle regressions.
Summary
…
References
…
Reviewer guidance
…
AI usage