diff --git a/src/components/PRAnalyticsDashboard/ReviewsInsight/PRQualityGraph.jsx b/src/components/PRAnalyticsDashboard/ReviewsInsight/PRQualityGraph.jsx index a2b9810496..f58355e04e 100644 --- a/src/components/PRAnalyticsDashboard/ReviewsInsight/PRQualityGraph.jsx +++ b/src/components/PRAnalyticsDashboard/ReviewsInsight/PRQualityGraph.jsx @@ -7,7 +7,13 @@ import ChartDataLabels from 'chartjs-plugin-datalabels'; Chart.register(ChartDataLabels); -function PRQualityGraph({ selectedTeams, qualityData, isDataViewActive, orderedTeamIds }) { +function PRQualityGraph({ + selectedTeams, + qualityData, + isDataViewActive, + orderedTeamIds, + teamData, +}) { const darkMode = useSelector(state => state.theme.darkMode); if (!selectedTeams || selectedTeams.length === 0) { @@ -67,9 +73,8 @@ function PRQualityGraph({ selectedTeams, qualityData, isDataViewActive, orderedT displayColors: false, enabled: true, callbacks: { - title: items => - items && items[0] ? `${items[0].label}: ${items[0].formattedValue}` : '', - label: ctx => (isDataViewActive ? `${ctx.raw.toFixed(1)}%` : ctx.raw), + title: () => '', + label: ctx => `${ctx.label}: ${isDataViewActive ? `${ctx.raw.toFixed(1)}%` : ctx.raw}`, }, }, datalabels: { @@ -101,6 +106,14 @@ function PRQualityGraph({ selectedTeams, qualityData, isDataViewActive, orderedT }`} > {team} + + {' '} + ({teamData[team]?.memberCount || 0} members) + @@ -127,6 +140,11 @@ PRQualityGraph.propTypes = { ), isDataViewActive: PropTypes.bool, orderedTeamIds: PropTypes.arrayOf(PropTypes.string), + teamData: PropTypes.objectOf( + PropTypes.shape({ + memberCount: PropTypes.number, + }), + ), }; PRQualityGraph.defaultProps = { @@ -134,6 +152,7 @@ PRQualityGraph.defaultProps = { qualityData: {}, isDataViewActive: false, orderedTeamIds: [], + teamData: {}, }; export default PRQualityGraph; diff --git a/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.jsx b/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.jsx index 3086749795..107dc1de9a 100644 --- a/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.jsx +++ b/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.jsx @@ -247,6 +247,7 @@ function ReviewsInsight() { qualityData={qualityData} isDataViewActive={dataViewActive} orderedTeamIds={orderedTeamIds} + teamData={teamData} /> )} diff --git a/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.module.css b/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.module.css index fafa0b329c..f8ca15659f 100644 --- a/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.module.css +++ b/src/components/PRAnalyticsDashboard/ReviewsInsight/ReviewsInsight.module.css @@ -237,6 +237,11 @@ border: none; } +.riTeamMemberCount { + font-size: 0.8em; + font-weight: normal; +} + /* Shared */ .riGraph { border: 1px solid #ccc; diff --git a/src/components/PRAnalyticsDashboard/ReviewsInsight/__tests__/PRQualityGraph.test.jsx b/src/components/PRAnalyticsDashboard/ReviewsInsight/__tests__/PRQualityGraph.test.jsx new file mode 100644 index 0000000000..e4edde762b --- /dev/null +++ b/src/components/PRAnalyticsDashboard/ReviewsInsight/__tests__/PRQualityGraph.test.jsx @@ -0,0 +1,102 @@ +import { vi } from 'vitest'; + +vi.mock('react-redux', async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + useSelector: vi.fn(() => false), + }; +}); + +let lastPieOptions; +vi.mock('react-chartjs-2', () => ({ + Pie: ({ options }) => { + lastPieOptions = options; + return
; + }, +})); + +import { render, screen } from '@testing-library/react'; +import PRQualityGraph from '../PRQualityGraph'; + +const renderWithStore = ui => render(ui); + +const selectedTeams = [{ value: 'Team A', label: 'Team A' }]; +const qualityData = { + 'Team A': { + NotApproved: 0, + LowQuality: 1, + Sufficient: 1, + Exceptional: 0, + }, +}; +const teamData = { + 'Team A': { + memberCount: 5, + }, +}; + +describe('PRQualityGraph', () => { + it('labels every category in the tooltip in Number mode', () => { + renderWithStore( + , + ); + + const { tooltip } = lastPieOptions.plugins; + expect(tooltip.callbacks.title()).toBe(''); + expect(tooltip.callbacks.label({ label: 'Not Approved', raw: 0 })).toBe('Not Approved: 0'); + expect(tooltip.callbacks.label({ label: 'Low Quality', raw: 1 })).toBe('Low Quality: 1'); + expect(tooltip.callbacks.label({ label: 'Sufficient', raw: 1 })).toBe('Sufficient: 1'); + expect(tooltip.callbacks.label({ label: 'Exceptional', raw: 0 })).toBe('Exceptional: 0'); + }); + + it('labels every category in the tooltip in Data View (percentage) mode', () => { + renderWithStore( + , + ); + + const { tooltip } = lastPieOptions.plugins; + expect(tooltip.callbacks.label({ label: 'Not Approved', raw: 0 })).toBe('Not Approved: 0.0%'); + expect(tooltip.callbacks.label({ label: 'Low Quality', raw: 50 })).toBe('Low Quality: 50.0%'); + }); + + it('renders the team member count from teamData', () => { + renderWithStore( + , + ); + + expect(screen.getByText(/5 members/)).toBeInTheDocument(); + }); + + it('defaults to 0 members when teamData is missing for a team', () => { + renderWithStore( + , + ); + + expect(screen.getByText(/0 members/)).toBeInTheDocument(); + }); +});