From 4a14cb58cd744a1fef43daff1bb1eec52c91383c Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 4 Jun 2025 13:33:43 +0200 Subject: [PATCH 01/20] Correccio test.yml --- .github/{ => workflows}/test.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{ => workflows}/test.yml (100%) diff --git a/.github/test.yml b/.github/workflows/test.yml similarity index 100% rename from .github/test.yml rename to .github/workflows/test.yml From 36898f46bb6d409a52d40cc4331e44759521fde7 Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 4 Jun 2025 14:00:51 +0200 Subject: [PATCH 02/20] Refactor completat per a commit.jsx --- src/domain/commits.jsx | 82 ++++++++++++++-------- src/pages/commitsPage.jsx | 57 +++++++-------- src/tests/commits.test.jsx | 140 +++++++++++++++++++++++++------------ 3 files changed, 174 insertions(+), 105 deletions(-) diff --git a/src/domain/commits.jsx b/src/domain/commits.jsx index 49bb90e..09fb044 100644 --- a/src/domain/commits.jsx +++ b/src/domain/commits.jsx @@ -1,3 +1,4 @@ + export const filterHistoricData = (data, days) => { if (days === "lifetime") return data; @@ -17,12 +18,12 @@ export const filterHistoricData = (data, days) => { }; export const transformCommitsDataForLineChart = (data) => { - const xDataCommits = []; + const xData = []; const userSeries = {}; for (const date in data) { - xDataCommits.push(date); - const commits = data[date].commits; + xData.push(date); + const commits = data[date].commits || {}; for (const user in commits) { if (user === 'total' || user === 'anonymous') continue; @@ -31,68 +32,89 @@ export const transformCommitsDataForLineChart = (data) => { } } - const seriesDataCommits = Object.keys(userSeries).map(user => ({ + const seriesData = Object.keys(userSeries).map(user => ({ name: user, data: userSeries[user] })); - return { xDataCommits, seriesDataCommits }; + return { xData, seriesData }; }; export const transformModifiedLinesDataForLineChart = (data) => { - const xDataModified = []; + const xData = []; const userSeries = {}; for (const date in data) { - xDataModified.push(date); - const modifiedLines = data[date].modified_lines; + xData.push(date); + const modifiedLines = data[date].modified_lines || {}; for (const user in modifiedLines) { if (user === 'total') continue; if (!userSeries[user]) userSeries[user] = []; - userSeries[user].push(modifiedLines[user].modified); + userSeries[user].push(modifiedLines[user]?.modified || 0); } } - const seriesModified = Object.keys(userSeries).map(user => ({ + const seriesData = Object.keys(userSeries).map(user => ({ name: user, data: userSeries[user] })); - return { xDataModified, seriesModified }; + return { xData, seriesData }; +}; + +export const GetRadarDataCommits = (data) => { + const radarData = {}; + for (const [user, val] of Object.entries(data.commits || {})) { + if (user !== 'total' && user !== 'anonymous') { + radarData[user] = val; + } + } + return radarData; }; -export const prepareRadarData = (modifiedLinesData) => { - const radarChartModifiedLines = {}; - for (const [user, { modified }] of Object.entries(modifiedLinesData)) { - radarChartModifiedLines[user] = modified; +export const GetRadarDataModifiedLines = (data) => { + const radarData = {}; + for (const [user, val] of Object.entries(data.modified_lines || {})) { + if (user !== 'total' && val.modified !== undefined) { + radarData[user] = val.modified; + } } - return radarChartModifiedLines; + return radarData; }; -export const getPieChartData = (dataObj, key = null) => { - return Object.entries(dataObj) +export const getPieChartDataCommits = (data) => { + return Object.entries(data.commits || {}) .filter(([user]) => user !== 'total' && user !== 'anonymous') - .map(([user, val]) => [user, key ? val[key] : val]); + .map(([user, val]) => [user, val]); }; -export const getGaugeChartPercentages = (dataObj, totalKey, anonymousKey = null) => { - const total = dataObj[totalKey]; - const anonymous = anonymousKey ? dataObj[anonymousKey] : 0; - return Object.entries(dataObj) - .filter(([user]) => user !== totalKey && user !== anonymousKey) +export const getPieChartDataModifiedLines = (data) => { + return Object.entries(data.modified_lines || {}) + .filter(([user]) => user !== 'total') + .map(([user, val]) => [user, val.modified]); +}; + +export const getGaugeChartDataCommits = (data) => { + const total = data.commits?.total || 0; + const anonymous = data.commits?.anonymous || 0; + const denominator = total - anonymous; + + return Object.entries(data.commits || {}) + .filter(([user]) => user !== 'total' && user !== 'anonymous') .map(([user, val]) => ({ user, - percentage: (total - anonymous) > 0 ? val / (total - anonymous) : 0 + percentage: denominator > 0 ? val / denominator : 0, })); }; -export const getGaugeChartModifiedLines = (dataObj, totalKey) => { - const total = dataObj[totalKey].modified; - return Object.entries(dataObj) - .filter(([user]) => user !== totalKey) +export const getGaugeChartDataModifiedLines = (data) => { + const totalModified = data.modified_lines?.total?.modified || 0; + + return Object.entries(data.modified_lines || {}) + .filter(([user]) => user !== 'total') .map(([user, val]) => ({ user, - percentage: total > 0 ? val.modified / total : 0 + percentage: totalModified > 0 ? val.modified / totalModified : 0, })); }; diff --git a/src/pages/commitsPage.jsx b/src/pages/commitsPage.jsx index 2411c74..507cf81 100644 --- a/src/pages/commitsPage.jsx +++ b/src/pages/commitsPage.jsx @@ -9,44 +9,40 @@ import { filterHistoricData, transformCommitsDataForLineChart, transformModifiedLinesDataForLineChart, - prepareRadarData, - getPieChartData, - getGaugeChartPercentages, - getGaugeChartModifiedLines + getPieChartDataCommits, + getPieChartDataModifiedLines, + getGaugeChartDataCommits, + getGaugeChartDataModifiedLines, + GetRadarDataCommits, + GetRadarDataModifiedLines, } from '../domain/commits.jsx'; function CommitsPage({ data, historicData, features }) { - const commitsData = data.commits; - const modifiedLinesData = data.modified_lines; - const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalCommits', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeCommits', "7"); - const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; - const { xDataCommits, seriesDataCommits } = transformCommitsDataForLineChart(filteredhistoricaData); - const { xDataModified, seriesModified } = transformModifiedLinesDataForLineChart(filteredhistoricaData); - const radarChartModifiedLines = prepareRadarData(modifiedLinesData); + const filteredHistoricData = historicData ? filterHistoricData(historicData, dateRange) : null; + + const { xData: xDataCommits, seriesData: seriesDataCommits } = transformCommitsDataForLineChart(filteredHistoricData || {}); + const { xData: xDataModified, seriesData: seriesDataModified } = transformModifiedLinesDataForLineChart(filteredHistoricData || {}); - const totalCommits = commitsData.total; - const totalModifiedLines = modifiedLinesData.total.modified; - const totalPeople = Object.keys(data.avatars).length; + const dataPieChartCommits = getPieChartDataCommits(data); + const dataPieChartModifiedLines = getPieChartDataModifiedLines(data); + const commitsGaugeData = getGaugeChartDataCommits(data); + const modifiedLinesGaugeData = getGaugeChartDataModifiedLines(data); + const radarChartCommits = GetRadarDataCommits(data); + const radarChartModifiedLines = GetRadarDataModifiedLines(data); - const dataPieChartCommits = getPieChartData(commitsData); - const dataPieChartModifiedLines = getPieChartData(modifiedLinesData, 'modified'); - const commitsGaugeData = getGaugeChartPercentages(commitsData, 'total', 'anonymous'); - const modifiedLinesGaugeData = getGaugeChartModifiedLines(modifiedLinesData, 'total'); + const totalCommits = data.commits?.total || 0; + const totalPeople = Object.keys(data.avatars || {}).length || 1; return (

Commits

- - + +
@@ -73,10 +69,10 @@ function CommitsPage({ data, historicData, features }) {

Summary

- +
- +

@@ -89,7 +85,7 @@ function CommitsPage({ data, historicData, features }) { 0 ? (totalCommits - commitsData.anonymous) / totalCommits : 0} + percentage={totalCommits > 0 ? (totalCommits - (data.commits?.anonymous || 0)) / totalCommits : 0} totalPeople={1} />

@@ -98,6 +94,7 @@ function CommitsPage({ data, historicData, features }) {

Metrics by User

+

Commits per user @@ -133,17 +130,17 @@ function CommitsPage({ data, historicData, features }) {
- +
- +
) : (
No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop s'hagi fet la primera execució del workflow Daily Metrics. + Si és el primer dia, torna demà un cop s'hagi fet la primera execució del bot.
)} diff --git a/src/tests/commits.test.jsx b/src/tests/commits.test.jsx index 1755791..f980204 100644 --- a/src/tests/commits.test.jsx +++ b/src/tests/commits.test.jsx @@ -2,106 +2,156 @@ import { filterHistoricData, transformCommitsDataForLineChart, transformModifiedLinesDataForLineChart, - prepareRadarData, - getPieChartData, - getGaugeChartPercentages, - getGaugeChartModifiedLines, + GetRadarDataCommits, + GetRadarDataModifiedLines, + getPieChartDataCommits, + getPieChartDataModifiedLines, + getGaugeChartDataCommits, + getGaugeChartDataModifiedLines, } from '../domain/commits'; -describe('commitsCalculations utils', () => { +describe('commits utils', () => { const mockHistoricData = { '2025-06-01': { commits: { pau: 3, lluis: 5, total: 8, - anonymous: 1 + anonymous: 1, }, modified_lines: { pau: { modified: 100 }, lluis: { modified: 200 }, - total: { modified: 300 } - } + total: { modified: 300 }, + }, }, '2025-06-02': { commits: { pau: 4, lluis: 6, total: 10, - anonymous: 2 + anonymous: 2, }, modified_lines: { pau: { modified: 150 }, lluis: { modified: 250 }, - total: { modified: 400 } - } - } + total: { modified: 400 }, + }, + }, }; test('filterHistoricData returns full data for "lifetime"', () => { expect(filterHistoricData(mockHistoricData, 'lifetime')).toEqual(mockHistoricData); }); + test('filterHistoricData filters correctly by days', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2025-06-03')); + + const filtered = filterHistoricData(mockHistoricData, '1'); + expect(Object.keys(filtered)).toEqual(['2025-06-02']); + + vi.useRealTimers(); + }); + test('transformCommitsDataForLineChart works as expected', () => { - const { xDataCommits, seriesDataCommits } = transformCommitsDataForLineChart(mockHistoricData); - expect(xDataCommits).toEqual(['2025-06-01', '2025-06-02']); - expect(seriesDataCommits).toEqual([ + const { xData, seriesData } = transformCommitsDataForLineChart(mockHistoricData); + expect(xData).toEqual(['2025-06-01', '2025-06-02']); + expect(seriesData).toEqual([ { name: 'pau', data: [3, 4] }, - { name: 'lluis', data: [5, 6] } + { name: 'lluis', data: [5, 6] }, ]); }); test('transformModifiedLinesDataForLineChart works as expected', () => { - const { xDataModified, seriesModified } = transformModifiedLinesDataForLineChart(mockHistoricData); - expect(xDataModified).toEqual(['2025-06-01', '2025-06-02']); - expect(seriesModified).toEqual([ + const { xData, seriesData } = transformModifiedLinesDataForLineChart(mockHistoricData); + expect(xData).toEqual(['2025-06-01', '2025-06-02']); + expect(seriesData).toEqual([ { name: 'pau', data: [100, 150] }, - { name: 'lluis', data: [200, 250] } + { name: 'lluis', data: [200, 250] }, ]); }); - test('prepareRadarData returns correct structure', () => { + test('GetRadarDataCommits returns correct radar data excluding total and anonymous', () => { + const input = { + commits: { + pau: 7, + lluis: 8, + total: 15, + anonymous: 3, + }, + }; + expect(GetRadarDataCommits(input)).toEqual({ pau: 7, lluis: 8 }); + }); + + test('GetRadarDataModifiedLines returns correct radar data excluding total', () => { + const input = { + modified_lines: { + pau: { modified: 100 }, + lluis: { modified: 200 }, + total: { modified: 300 }, + }, + }; + expect(GetRadarDataModifiedLines(input)).toEqual({ pau: 100, lluis: 200 }); + }); + + test('getPieChartDataCommits returns filtered array excluding total and anonymous', () => { const input = { - pau: { modified: 100 }, - lluis: { modified: 200 } + commits: { + pau: 5, + lluis: 10, + total: 15, + anonymous: 2, + }, }; - expect(prepareRadarData(input)).toEqual({ pau: 100, lluis: 200 }); + expect(getPieChartDataCommits(input)).toEqual([ + ['pau', 5], + ['lluis', 10], + ]); }); - test('getPieChartData with key works', () => { + test('getPieChartDataModifiedLines returns filtered array excluding total', () => { const input = { - pau: { modified: 100 }, - lluis: { modified: 200 }, - total: { modified: 300 } + modified_lines: { + pau: { modified: 100 }, + lluis: { modified: 200 }, + total: { modified: 300 }, + }, }; - expect(getPieChartData(input, 'modified')).toEqual([ + expect(getPieChartDataModifiedLines(input)).toEqual([ ['pau', 100], - ['lluis', 200] + ['lluis', 200], ]); }); - test('getGaugeChartPercentages works', () => { + test('getGaugeChartDataCommits returns correct percentages excluding total and anonymous', () => { const input = { - pau: 3, - lluis: 5, - total: 8, - anonymous: 2 + commits: { + pau: 4, + lluis: 6, + total: 10, + anonymous: 1, + }, }; - expect(getGaugeChartPercentages(input, 'total', 'anonymous')).toEqual([ - { user: 'pau', percentage: 3 / 6 }, - { user: 'lluis', percentage: 5 / 6 } + const result = getGaugeChartDataCommits(input); + expect(result).toEqual([ + { user: 'pau', percentage: 4 / 9 }, + { user: 'lluis', percentage: 6 / 9 }, ]); }); - test('getGaugeChartModifiedLines works', () => { + test('getGaugeChartDataModifiedLines returns correct percentages excluding total', () => { const input = { - pau: { modified: 100 }, - lluis: { modified: 200 }, - total: { modified: 300 } + modified_lines: { + pau: { modified: 100 }, + lluis: { modified: 300 }, + total: { modified: 400 }, + }, }; - expect(getGaugeChartModifiedLines(input, 'total')).toEqual([ - { user: 'pau', percentage: 100 / 300 }, - { user: 'lluis', percentage: 200 / 300 } + const result = getGaugeChartDataModifiedLines(input); + expect(result).toEqual([ + { user: 'pau', percentage: 100 / 400 }, + { user: 'lluis', percentage: 300 / 400 }, ]); }); }); From 0ae30277e5107fba8a2dc8d9313d480b619fbdba Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 4 Jun 2025 16:32:50 +0200 Subject: [PATCH 03/20] Refactor Issues page --- src/domain/issues.jsx | 91 +++++++++++++++++++++++++++++++ src/pages/issues.jsx | 111 +++++++++++--------------------------- src/tests/issues.test.jsx | 94 ++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 80 deletions(-) create mode 100644 src/domain/issues.jsx create mode 100644 src/tests/issues.test.jsx diff --git a/src/domain/issues.jsx b/src/domain/issues.jsx new file mode 100644 index 0000000..f0f5380 --- /dev/null +++ b/src/domain/issues.jsx @@ -0,0 +1,91 @@ + +export const filterHistoricData = (data, days) => { + if (days === "lifetime") return data; + + const today = new Date(); + const cutoff = new Date(today); + cutoff.setDate(today.getDate() - parseInt(days)); + const cutoffDateString = cutoff.toISOString().split("T")[0]; + + const filtered = {}; + for (const date in data) { + if (date >= cutoffDateString) { + filtered[date] = data[date]; + } + } + + return filtered; +}; + +export const transformAssignedIssuesDataForLineChart = (data) => { + const xDataAssigned = []; + const userSeries = {}; + + for (const date in data) { + xDataAssigned.push(date); + const issues = data[date].issues.assigned; + + for (const user in issues) { + if (user === 'non_assigned') continue; + if (!userSeries[user]) { + userSeries[user] = []; + } + userSeries[user].push(issues[user]); + } + } + + const seriesDataAssigned = Object.keys(userSeries).map(user => ({ + name: user, + data: userSeries[user] + })); + + return { xDataAssigned, seriesDataAssigned }; +}; + +export const getRadarAndPieDataAssigned = (data) => { + const radarData = {}; + const assigned = data.issues.assigned || {}; + for (const [user, val] of Object.entries(assigned)) { + if (user !== 'non_assigned' && user !== 'total') { + radarData[user] = val; + } + } + const pieData = Object.entries(radarData) + return { radarData, pieData }; +}; + +export const getGaugeDataAssigned = (data) => { + const total = data.issues.total || 0; + const nonAssigned = data.issues.assigned['non_assigned'] || 0; + const assigned = total - nonAssigned; + return total > 0 ? assigned / total : 0; +}; + +export const getGaugeDataAssignedPerUser = (data) => { + const total = data.issues.total || 0; + const nonAssigned = data.issues.assigned['non_assigned'] || 0; + const totalAssigned = total - nonAssigned; + + return Object.entries(data.issues.assigned) + .filter(([user]) => user !== 'non_assigned') + .map(([user, assigned]) => ({ + user, + percentage: totalAssigned > 0 ? assigned / totalAssigned : 0, + })); +}; + +export const getGaugeDataClosedPerUser = (data) => { + return Object.entries(data.issues.closed).map(([user, closed]) => { + const assigned = data.issues.assigned[user] || 0; + return { + user, + percentage: assigned > 0 ? closed / assigned : 0, + }; + }); +}; + +export const getGaugeDataHavePR = (data) => { + const totalClosed = data.issues.total_closed || 0; + const havePullRequest = data.issues.have_pull_request || 0; + return totalClosed > 0 ? havePullRequest / totalClosed : 0; +}; diff --git a/src/pages/issues.jsx b/src/pages/issues.jsx index 10cd447..a229aa5 100644 --- a/src/pages/issues.jsx +++ b/src/pages/issues.jsx @@ -3,67 +3,30 @@ import GaugeChart from '../components/gaugeChart'; import RadarPieToggle from '../components/radarPieToggle'; import LineChartMultiple from '../components/lineChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; +import { + filterHistoricData, + transformAssignedIssuesDataForLineChart, + getRadarAndPieDataAssigned, + getGaugeDataAssigned, + getGaugeDataAssignedPerUser, + getGaugeDataClosedPerUser, + getGaugeDataHavePR +} from '../domain/issues'; import '../styles/commits.css'; function Issues({ data,historicData,features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalIssues', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeIssues', "7"); - const filterHistoricData = (data, days) => { - if (days === "lifetime") return data; - const today = new Date(); - const cutoff = new Date(today); - cutoff.setDate(today.getDate() - parseInt(days)); - const cutoffDateString = cutoff.toISOString().split("T")[0]; - const filtered = {}; - for (const date in data) { - if (date >= cutoffDateString) { - filtered[date] = data[date]; - } - } - - return filtered; - }; - const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; - - const issuesData = data.issues; - const totalIssues = issuesData.total; - const totalClosed = issuesData.total_closed - const havePullRequest = issuesData.have_pull_request - const { non_assigned, ...filteredData } = issuesData.assigned; + const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; const totalPeople = Object.keys(data.avatars).length; - const totalAssigned = totalIssues - issuesData.assigned['non_assigned'] - const closedBy = issuesData.closed - - - const transformAssignedPRsDataForLineChart = (data) => { - const xDataAssigned = []; - const userSeries = {}; - - for (const date in data) { - xDataAssigned.push(date); - const issues = data[date].issues.assigned; - - for (const user in issues) { - if(user === 'non_assigned') continue; - if (!userSeries[user]) { - userSeries[user] = []; - } - userSeries[user].push(issues[user]); - } - } - const seriesDataAssigned = Object.keys(userSeries).map(user => ({ - name: user, - data: userSeries[user] - })); - - return { xDataAssigned, seriesDataAssigned}; - }; - - const { xDataAssigned, seriesDataAssigned } = transformAssignedPRsDataForLineChart(filteredhistoricaData); - - + const { xDataAssigned, seriesDataAssigned } = transformAssignedIssuesDataForLineChart(filteredhistoricaData); + const percentageAssigned = getGaugeDataAssigned(data); + const gaugeDataHavePR = getGaugeDataHavePR(data); + const gaugeDataAssigned = getGaugeDataAssignedPerUser(data); + const gaugeDataClosed = getGaugeDataClosedPerUser(data); + const {radarData,pieData} = getRadarAndPieDataAssigned(data) return (

Issues

@@ -107,8 +70,8 @@ function Issues({ data,historicData,features }) {
@@ -123,7 +86,7 @@ function Issues({ data,historicData,features }) { 0 ? totalAssigned / totalIssues : 0} + percentage={percentageAssigned} totalPeople= {1} />
@@ -141,7 +104,7 @@ function Issues({ data,historicData,features }) { 0 ? havePullRequest / totalClosed : 0} + percentage={gaugeDataHavePR} totalPeople= {1} />
)} @@ -156,23 +119,16 @@ function Issues({ data,historicData,features }) { Percentage of issues assigned per user relative to the number of assigned issues

-
- {Object.keys(issuesData.assigned).map((user) => { - if (user !== 'non_assigned') { - const userIssues = issuesData.assigned[user]; - const percentage = totalAssigned > 0 ? userIssues / totalAssigned : 0; - return ( +
+ {gaugeDataAssigned.map(({ user, percentage }) => ( - ); - } - return null; - })} -
+ ))} +

Issues closed per user @@ -181,22 +137,17 @@ function Issues({ data,historicData,features }) { Percentage of issues closed per user relative to the issues assigned to that user -

-
- {Object.keys(closedBy).map((user) => { - const closedCount = closedBy[user]; - const assigned = issuesData.assigned[user]; - const percentage = assigned > 0 ? closedCount / assigned : 0; - return ( + +
+ {gaugeDataClosed.map(({ user, percentage }) => ( - ); - })} -
+ ))} +
)} diff --git a/src/tests/issues.test.jsx b/src/tests/issues.test.jsx new file mode 100644 index 0000000..60f64ad --- /dev/null +++ b/src/tests/issues.test.jsx @@ -0,0 +1,94 @@ +import { + filterHistoricData, + transformAssignedIssuesDataForLineChart, + getRadarAndPieDataAssigned, + getGaugeDataAssigned, + getGaugeDataAssignedPerUser, + getGaugeDataClosedPerUser, + getGaugeDataHavePR, +} from '../domain/issues'; + +describe('issues utils', () => { + const mockHistoricData = { + '2025-06-01': { + issues: { + assigned: { pau: 3, lluis: 5, non_assigned: 1 }, + }, + }, + '2025-06-02': { + issues: { + assigned: { pau: 4, lluis: 6, non_assigned: 2 }, + }, + }, + }; + + const mockData = { + issues: { + total: 20, + total_closed: 10, + have_pull_request: 7, + assigned: { + pau: 5, + lluis: 5, + non_assigned: 10, + }, + closed: { + pau: 3, + lluis: 2, + }, + }, + }; + + test('filterHistoricData returns full data for "lifetime"', () => { + expect(filterHistoricData(mockHistoricData, 'lifetime')).toEqual(mockHistoricData); + }); + + test('filterHistoricData filters correctly by days', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2025-06-03')); + + const filtered = filterHistoricData(mockHistoricData, '1'); + expect(Object.keys(filtered)).toEqual(['2025-06-02']); + + vi.useRealTimers(); + }); + + test('transformAssignedIssuesDataForLineChart returns expected structure', () => { + expect(transformAssignedIssuesDataForLineChart(mockHistoricData)).toEqual({ + xDataAssigned: ['2025-06-01', '2025-06-02'], + seriesDataAssigned: [ + { name: 'pau', data: [3, 4] }, + { name: 'lluis', data: [5, 6] }, + ], + }); + }); + + test('getRadarAndPieDataAssigned returns radarData and pieData excluding non_assigned and total', () => { + expect(getRadarAndPieDataAssigned(mockData)).toEqual({ + radarData: { pau: 5, lluis: 5 }, + pieData: [['pau', 5], ['lluis', 5]], + }); + }); + + test('getGaugeDataAssigned returns correct overall assigned percentage', () => { + expect(getGaugeDataAssigned(mockData)).toEqual((20 - 10) / 20); + }); + + test('getGaugeDataAssignedPerUser returns array with user and percentage', () => { + expect(getGaugeDataAssignedPerUser(mockData)).toEqual([ + { user: 'pau', percentage: 5 / 10 }, + { user: 'lluis', percentage: 5 / 10 }, + ]); + }); + + test('getGaugeDataClosedPerUser returns array with user and percentage', () => { + expect(getGaugeDataClosedPerUser(mockData)).toEqual([ + { user: 'pau', percentage: 3 / 5 }, + { user: 'lluis', percentage: 2 / 5 }, + ]); + }); + + test('getGaugeDataHavePR returns correct percentage of PRs', () => { + expect(getGaugeDataHavePR(mockData)).toEqual(7 / 10); + }); +}); From 960f830c4a7c497a84539950c6aeb1d577f40aaa Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Thu, 5 Jun 2025 08:28:36 +0200 Subject: [PATCH 04/20] Canviat de Issues a IssuesPage --- src/App.jsx | 4 ++-- src/pages/{issues.jsx => issuesPage.jsx} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/pages/{issues.jsx => issuesPage.jsx} (98%) diff --git a/src/App.jsx b/src/App.jsx index a9485d1..b6b33f4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,7 +3,7 @@ import Header from "./components/header"; import Index from "./pages/index"; import PullRequests from "./pages/pullRequests"; import CommitsPage from "./pages/commitsPage"; -import Issues from "./pages/issues"; +import IssuesPage from "./pages/issuesPage"; import Individual from "./pages/individual"; import Projects from "./pages/projects"; import { HashRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; @@ -85,7 +85,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/pages/issues.jsx b/src/pages/issuesPage.jsx similarity index 98% rename from src/pages/issues.jsx rename to src/pages/issuesPage.jsx index a229aa5..5ff8aa3 100644 --- a/src/pages/issues.jsx +++ b/src/pages/issuesPage.jsx @@ -15,7 +15,7 @@ import { import '../styles/commits.css'; -function Issues({ data,historicData,features }) { +function IssuesPage({ data,historicData,features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalIssues', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeIssues', "7"); @@ -189,4 +189,4 @@ function Issues({ data,historicData,features }) { ); } -export default Issues; +export default IssuesPage; From 85657b5e929da43117c23d986a45e3bc6904769b Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Thu, 5 Jun 2025 08:38:47 +0200 Subject: [PATCH 05/20] Creat utils.jsx per a funcions independents als conceptes de commits,issues... --- src/domain/commits.jsx | 19 ---------------- src/domain/issues.jsx | 18 --------------- src/domain/utils.jsx | 17 ++++++++++++++ src/pages/commitsPage.jsx | 3 ++- src/pages/issuesPage.jsx | 2 +- src/tests/commits.test.jsx | 18 +-------------- src/tests/issues.test.jsx | 17 +------------- src/tests/utils.test.jsx | 46 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 68 insertions(+), 72 deletions(-) create mode 100644 src/domain/utils.jsx create mode 100644 src/tests/utils.test.jsx diff --git a/src/domain/commits.jsx b/src/domain/commits.jsx index 09fb044..4f5afd5 100644 --- a/src/domain/commits.jsx +++ b/src/domain/commits.jsx @@ -1,22 +1,3 @@ - -export const filterHistoricData = (data, days) => { - if (days === "lifetime") return data; - - const today = new Date(); - const cutoff = new Date(today); - cutoff.setDate(today.getDate() - parseInt(days)); - const cutoffDateString = cutoff.toISOString().split("T")[0]; - - const filtered = {}; - for (const date in data) { - if (date >= cutoffDateString) { - filtered[date] = data[date]; - } - } - - return filtered; -}; - export const transformCommitsDataForLineChart = (data) => { const xData = []; const userSeries = {}; diff --git a/src/domain/issues.jsx b/src/domain/issues.jsx index f0f5380..f2f03be 100644 --- a/src/domain/issues.jsx +++ b/src/domain/issues.jsx @@ -1,22 +1,4 @@ -export const filterHistoricData = (data, days) => { - if (days === "lifetime") return data; - - const today = new Date(); - const cutoff = new Date(today); - cutoff.setDate(today.getDate() - parseInt(days)); - const cutoffDateString = cutoff.toISOString().split("T")[0]; - - const filtered = {}; - for (const date in data) { - if (date >= cutoffDateString) { - filtered[date] = data[date]; - } - } - - return filtered; -}; - export const transformAssignedIssuesDataForLineChart = (data) => { const xDataAssigned = []; const userSeries = {}; diff --git a/src/domain/utils.jsx b/src/domain/utils.jsx new file mode 100644 index 0000000..1eed2e3 --- /dev/null +++ b/src/domain/utils.jsx @@ -0,0 +1,17 @@ +export const filterHistoricData = (data, days) => { + if (days === "lifetime") return data; + + const today = new Date(); + const cutoff = new Date(today); + cutoff.setDate(today.getDate() - parseInt(days)); + const cutoffDateString = cutoff.toISOString().split("T")[0]; + + const filtered = {}; + for (const date in data) { + if (date >= cutoffDateString) { + filtered[date] = data[date]; + } + } + + return filtered; +}; diff --git a/src/pages/commitsPage.jsx b/src/pages/commitsPage.jsx index 507cf81..1430c32 100644 --- a/src/pages/commitsPage.jsx +++ b/src/pages/commitsPage.jsx @@ -6,7 +6,6 @@ import usePersistentStateSession from '../components/usePersistentStateSession.j import '../styles/commits.css'; import { - filterHistoricData, transformCommitsDataForLineChart, transformModifiedLinesDataForLineChart, getPieChartDataCommits, @@ -17,6 +16,8 @@ import { GetRadarDataModifiedLines, } from '../domain/commits.jsx'; +import { filterHistoricData } from '../domain/utils.jsx'; + function CommitsPage({ data, historicData, features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalCommits', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeCommits', "7"); diff --git a/src/pages/issuesPage.jsx b/src/pages/issuesPage.jsx index 5ff8aa3..b256f01 100644 --- a/src/pages/issuesPage.jsx +++ b/src/pages/issuesPage.jsx @@ -4,7 +4,6 @@ import RadarPieToggle from '../components/radarPieToggle'; import LineChartMultiple from '../components/lineChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; import { - filterHistoricData, transformAssignedIssuesDataForLineChart, getRadarAndPieDataAssigned, getGaugeDataAssigned, @@ -12,6 +11,7 @@ import { getGaugeDataClosedPerUser, getGaugeDataHavePR } from '../domain/issues'; +import { filterHistoricData } from '../domain/utils.jsx'; import '../styles/commits.css'; diff --git a/src/tests/commits.test.jsx b/src/tests/commits.test.jsx index f980204..924a678 100644 --- a/src/tests/commits.test.jsx +++ b/src/tests/commits.test.jsx @@ -1,5 +1,4 @@ import { - filterHistoricData, transformCommitsDataForLineChart, transformModifiedLinesDataForLineChart, GetRadarDataCommits, @@ -10,7 +9,7 @@ import { getGaugeChartDataModifiedLines, } from '../domain/commits'; -describe('commits utils', () => { +describe('commits ', () => { const mockHistoricData = { '2025-06-01': { commits: { @@ -39,21 +38,6 @@ describe('commits utils', () => { }, }, }; - - test('filterHistoricData returns full data for "lifetime"', () => { - expect(filterHistoricData(mockHistoricData, 'lifetime')).toEqual(mockHistoricData); - }); - - test('filterHistoricData filters correctly by days', () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date('2025-06-03')); - - const filtered = filterHistoricData(mockHistoricData, '1'); - expect(Object.keys(filtered)).toEqual(['2025-06-02']); - - vi.useRealTimers(); - }); - test('transformCommitsDataForLineChart works as expected', () => { const { xData, seriesData } = transformCommitsDataForLineChart(mockHistoricData); expect(xData).toEqual(['2025-06-01', '2025-06-02']); diff --git a/src/tests/issues.test.jsx b/src/tests/issues.test.jsx index 60f64ad..b7c9cb3 100644 --- a/src/tests/issues.test.jsx +++ b/src/tests/issues.test.jsx @@ -1,5 +1,4 @@ import { - filterHistoricData, transformAssignedIssuesDataForLineChart, getRadarAndPieDataAssigned, getGaugeDataAssigned, @@ -8,7 +7,7 @@ import { getGaugeDataHavePR, } from '../domain/issues'; -describe('issues utils', () => { +describe('issues', () => { const mockHistoricData = { '2025-06-01': { issues: { @@ -39,20 +38,6 @@ describe('issues utils', () => { }, }; - test('filterHistoricData returns full data for "lifetime"', () => { - expect(filterHistoricData(mockHistoricData, 'lifetime')).toEqual(mockHistoricData); - }); - - test('filterHistoricData filters correctly by days', () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date('2025-06-03')); - - const filtered = filterHistoricData(mockHistoricData, '1'); - expect(Object.keys(filtered)).toEqual(['2025-06-02']); - - vi.useRealTimers(); - }); - test('transformAssignedIssuesDataForLineChart returns expected structure', () => { expect(transformAssignedIssuesDataForLineChart(mockHistoricData)).toEqual({ xDataAssigned: ['2025-06-01', '2025-06-02'], diff --git a/src/tests/utils.test.jsx b/src/tests/utils.test.jsx new file mode 100644 index 0000000..ee93174 --- /dev/null +++ b/src/tests/utils.test.jsx @@ -0,0 +1,46 @@ +import { filterHistoricData } from '../domain/utils'; + +describe('utils', () => { + const mockHistoricData = { + '2025-06-01': { + commits: { + pau: 3, + lluis: 5, + total: 8, + anonymous: 1, + }, + modified_lines: { + pau: { modified: 100 }, + lluis: { modified: 200 }, + total: { modified: 300 }, + }, + }, + '2025-06-02': { + commits: { + pau: 4, + lluis: 6, + total: 10, + anonymous: 2, + }, + modified_lines: { + pau: { modified: 150 }, + lluis: { modified: 250 }, + total: { modified: 400 }, + }, + }, + }; + + test('filterHistoricData returns full data for "lifetime"', () => { + expect(filterHistoricData(mockHistoricData, 'lifetime')).toEqual(mockHistoricData); + }); + + test('filterHistoricData filters correctly by days', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2025-06-03')); + + const filtered = filterHistoricData(mockHistoricData, '1'); + expect(Object.keys(filtered)).toEqual(['2025-06-02']); + + vi.useRealTimers(); + }); +}); From 87b1781cb28c58506e35b6248db5677ffc65e0fc Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Thu, 5 Jun 2025 09:18:35 +0200 Subject: [PATCH 06/20] Refactor PullRequests --- src/domain/issues.jsx | 10 +-- src/domain/pullRequests.jsx | 107 ++++++++++++++++++++++++++++ src/pages/issuesPage.jsx | 20 +++--- src/pages/pullRequests.jsx | 135 +++++++++++------------------------- 4 files changed, 164 insertions(+), 108 deletions(-) create mode 100644 src/domain/pullRequests.jsx diff --git a/src/domain/issues.jsx b/src/domain/issues.jsx index f2f03be..c108188 100644 --- a/src/domain/issues.jsx +++ b/src/domain/issues.jsx @@ -24,7 +24,7 @@ export const transformAssignedIssuesDataForLineChart = (data) => { return { xDataAssigned, seriesDataAssigned }; }; -export const getRadarAndPieDataAssigned = (data) => { +export const getRadarAndPieDataIssuesAssigned = (data) => { const radarData = {}; const assigned = data.issues.assigned || {}; for (const [user, val] of Object.entries(assigned)) { @@ -36,14 +36,14 @@ export const getRadarAndPieDataAssigned = (data) => { return { radarData, pieData }; }; -export const getGaugeDataAssigned = (data) => { +export const getGaugeDataIssuesAssigned = (data) => { const total = data.issues.total || 0; const nonAssigned = data.issues.assigned['non_assigned'] || 0; const assigned = total - nonAssigned; return total > 0 ? assigned / total : 0; }; -export const getGaugeDataAssignedPerUser = (data) => { +export const getGaugeDataAssignedIssuesPerUser = (data) => { const total = data.issues.total || 0; const nonAssigned = data.issues.assigned['non_assigned'] || 0; const totalAssigned = total - nonAssigned; @@ -56,7 +56,7 @@ export const getGaugeDataAssignedPerUser = (data) => { })); }; -export const getGaugeDataClosedPerUser = (data) => { +export const getGaugeDataClosedIssuesPerUser = (data) => { return Object.entries(data.issues.closed).map(([user, closed]) => { const assigned = data.issues.assigned[user] || 0; return { @@ -66,7 +66,7 @@ export const getGaugeDataClosedPerUser = (data) => { }); }; -export const getGaugeDataHavePR = (data) => { +export const getGaugeDataIssuesHavePR = (data) => { const totalClosed = data.issues.total_closed || 0; const havePullRequest = data.issues.have_pull_request || 0; return totalClosed > 0 ? havePullRequest / totalClosed : 0; diff --git a/src/domain/pullRequests.jsx b/src/domain/pullRequests.jsx new file mode 100644 index 0000000..70b7e35 --- /dev/null +++ b/src/domain/pullRequests.jsx @@ -0,0 +1,107 @@ +export const transformCreatedPRsDataForLineChart = (data) => { + const xDataCreated = []; + const userSeries = {}; + + for (const date in data) { + xDataCreated.push(date); + const created = data[date].pull_requests.created; + + for (const user in created) { + if (!userSeries[user]) { + userSeries[user] = []; + } + userSeries[user].push(created[user]); + } + } + const seriesDataCreated = Object.keys(userSeries).map(user => ({ + name: user, + data: userSeries[user] + })); + + return { xDataCreated, seriesDataCreated }; +}; + +export const transformMergedPRsDataForLineChart = (data) => { + const xDataMerged = []; + const userSeries = {}; + + for (const date in data) { + xDataMerged.push(date); + const merged = data[date].pull_requests.merged_per_member; + + for (const user in merged) { + if (!userSeries[user]) { + userSeries[user] = []; + } + userSeries[user].push(merged[user]); + } + } + const seriesDataMerged = Object.keys(userSeries).map(user => ({ + name: user, + data: userSeries[user] + })); + + return { xDataMerged, seriesDataMerged }; +}; + +export const getRadarAndPieDataForCreatedPRs = (data) => { + const radarDataCreated = data.pull_requests.created + const pieDataCreated = Object.entries(radarDataCreated) + return {radarDataCreated,pieDataCreated} +} + +export const getRadarAndPieDataForMergedPRs = (data) => { + const radarDataMerged = data.pull_requests.merged_per_member + + const pieDataMerged = Object.entries(radarDataMerged) + return {radarDataMerged,pieDataMerged} +} + +export const getGaugeChartDataMergedPRs = (data) => { + const total = data.pull_requests.total + const totalMerged = data.pull_requests.merged + const totalClosed = data.pull_requests.closed + + const percentageMerged = (total - totalClosed) > 0 ? totalMerged / (total - totalClosed) : 0 + + return percentageMerged +} + +export const getGaugeChartDataReviewedPRs = (data) => { + const totalMerged = data.pull_requests.merged + const totalMergedNotByAuthor = data.pull_requests.not_merged_by_author + + const percentatgeReviewed = totalMerged > 0 ? totalMergedNotByAuthor / totalMerged : 0 + + return percentatgeReviewed +} + +export const getGaugeChartDataMergesPRs = (data) => { + const totalMerged = data.pull_requests.merged + const merges = data.commit_merges + + const percentageMergesPR = merges > 0 ? totalMerged / merges : 0 + + return percentageMergesPR +} + +export const getGaugeDataCreatedPRsPerUser = (data) => { + const total = data.pull_requests.total + return Object.entries(data.pull_requests.created).map(([user, created]) => { + return { + user, + percentage: total > 0 ? created / total : 0, + }; + }); +}; + + +export const getGaugeDataMergedPRsPerUser = (data) => { + const totalMerged = data.pull_requests.merged + return Object.entries(data.pull_requests.merged_per_member).map(([user, merged]) => { + return { + user, + percentage: totalMerged > 0 ? merged / totalMerged : 0, + }; + }); +}; \ No newline at end of file diff --git a/src/pages/issuesPage.jsx b/src/pages/issuesPage.jsx index b256f01..c47faf2 100644 --- a/src/pages/issuesPage.jsx +++ b/src/pages/issuesPage.jsx @@ -5,11 +5,11 @@ import LineChartMultiple from '../components/lineChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; import { transformAssignedIssuesDataForLineChart, - getRadarAndPieDataAssigned, - getGaugeDataAssigned, - getGaugeDataAssignedPerUser, - getGaugeDataClosedPerUser, - getGaugeDataHavePR + getRadarAndPieDataIssuesAssigned, + getGaugeDataIssuesAssigned, + getGaugeDataAssignedIssuesPerUser, + getGaugeDataClosedIssuesPerUser, + getGaugeDataIssuesHavePR } from '../domain/issues'; import { filterHistoricData } from '../domain/utils.jsx'; @@ -22,11 +22,11 @@ function IssuesPage({ data,historicData,features }) { const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; const totalPeople = Object.keys(data.avatars).length; const { xDataAssigned, seriesDataAssigned } = transformAssignedIssuesDataForLineChart(filteredhistoricaData); - const percentageAssigned = getGaugeDataAssigned(data); - const gaugeDataHavePR = getGaugeDataHavePR(data); - const gaugeDataAssigned = getGaugeDataAssignedPerUser(data); - const gaugeDataClosed = getGaugeDataClosedPerUser(data); - const {radarData,pieData} = getRadarAndPieDataAssigned(data) + const percentageAssigned = getGaugeDataIssuesAssigned(data); + const gaugeDataHavePR = getGaugeDataIssuesHavePR(data); + const gaugeDataAssigned = getGaugeDataAssignedIssuesPerUser(data); + const gaugeDataClosed = getGaugeDataClosedIssuesPerUser(data); + const {radarData,pieData} = getRadarAndPieDataIssuesAssigned(data) return (

Issues

diff --git a/src/pages/pullRequests.jsx b/src/pages/pullRequests.jsx index 8204766..82a0939 100644 --- a/src/pages/pullRequests.jsx +++ b/src/pages/pullRequests.jsx @@ -4,88 +4,45 @@ import '../styles/commits.css'; import RadarPieToggle from '../components/radarPieToggle'; import LineChartMultiple from '../components/lineChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; +import { + transformCreatedPRsDataForLineChart, + transformMergedPRsDataForLineChart, + getRadarAndPieDataForCreatedPRs, + getRadarAndPieDataForMergedPRs, + getGaugeChartDataMergedPRs, + getGaugeChartDataReviewedPRs, + getGaugeChartDataMergesPRs, + getGaugeDataCreatedPRsPerUser, + getGaugeDataMergedPRsPerUser +} from '../domain/pullRequests' +import { filterHistoricData } from '../domain/utils'; function PullRequests({ data,historicData,features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalPR', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangePR', "7"); - const filterHistoricData = (data, days) => { - if (days === "lifetime") return data; - - const today = new Date(); - const cutoff = new Date(today); - cutoff.setDate(today.getDate() - parseInt(days)); - const cutoffDateString = cutoff.toISOString().split("T")[0]; - const filtered = {}; - for (const date in data) { - if (date >= cutoffDateString) { - filtered[date] = data[date]; - } - } - - return filtered; - }; const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; const pullRequests = data.pull_requests const createdby = pullRequests.created const mergedby = pullRequests.merged_per_member - const totalMerged = pullRequests.merged - const totalMergedNotByAuthor = pullRequests.not_merged_by_author - const totalClosed = pullRequests.closed - const total = pullRequests.total const totalPeople = Object.keys(data.avatars).length; - const merges = data.commit_merges - - const transformCreatedPRsDataForLineChart = (data) => { - const xDataCreated = []; - const userSeries = {}; - - for (const date in data) { - xDataCreated.push(date); - const created = data[date].pull_requests.created; - - for (const user in created) { - if (!userSeries[user]) { - userSeries[user] = []; - } - userSeries[user].push(created[user]); - } - } - const seriesDataCreated = Object.keys(userSeries).map(user => ({ - name: user, - data: userSeries[user] - })); - - return { xDataCreated, seriesDataCreated }; - }; + const total = data.pull_requests.total + const totalMerged = data.pull_requests.merged const { xDataCreated, seriesDataCreated } = transformCreatedPRsDataForLineChart(filteredhistoricaData); - const transformMergedPRsDataForLineChart = (data) => { - const xDataMerged = []; - const userSeries = {}; - - for (const date in data) { - xDataMerged.push(date); - const merged = data[date].pull_requests.merged_per_member; - - for (const user in merged) { - if (!userSeries[user]) { - userSeries[user] = []; - } - userSeries[user].push(merged[user]); - } - } - const seriesDataMerged = Object.keys(userSeries).map(user => ({ - name: user, - data: userSeries[user] - })); - - return { xDataMerged, seriesDataMerged }; - }; - const { xDataMerged, seriesDataMerged } = transformMergedPRsDataForLineChart(filteredhistoricaData); + const {radarDataCreated,pieDataCreated} = getRadarAndPieDataForCreatedPRs(data); + + const {radarDataMerged,pieDataMerged} = getRadarAndPieDataForMergedPRs(data); + + const percentageMerged = getGaugeChartDataMergedPRs(data); + const percentatgeReviewed = getGaugeChartDataReviewedPRs(data); + const percentageMergesPR = getGaugeChartDataMergesPRs(data); + + const gaugeDataCreatedPRs = getGaugeDataCreatedPRsPerUser(data) + const gaugeDataMergedPRs = getGaugeDataMergedPRsPerUser(data) return (

Pull requests

@@ -129,15 +86,15 @@ function PullRequests({ data,historicData,features }) {
@@ -153,7 +110,7 @@ function PullRequests({ data,historicData,features }) { 0 ? totalMerged / (total - totalClosed) : 0} + percentage={percentageMerged} totalPeople= {1} />
@@ -169,7 +126,7 @@ function PullRequests({ data,historicData,features }) { 0 ? totalMergedNotByAuthor / totalMerged : 0} + percentage={percentatgeReviewed} totalPeople={1} />
@@ -185,7 +142,7 @@ function PullRequests({ data,historicData,features }) { 0 ? totalMerged / merges : 0} + percentage={percentageMergesPR} totalPeople={1} />
@@ -201,18 +158,14 @@ function PullRequests({ data,historicData,features }) {
- {Object.keys(createdby).map((user) => { - const userPRs = createdby[user]; - const percentage = total > 0 ? userPRs / total : 0; - return ( - - ); - })} + {gaugeDataCreatedPRs.map(({ user, percentage }) => ( + + ))}

Pull requests merged per user @@ -222,18 +175,14 @@ function PullRequests({ data,historicData,features }) {

- {Object.keys(mergedby).map((user) => { - const userMergedPRs = mergedby[user]; - const percentage = totalMerged > 0 ? userMergedPRs / totalMerged : 0; - return ( + {gaugeDataMergedPRs.map(({ user, percentage }) => ( - ); - })} + ))}
From 4d13de6e6a0973b9d3bc9876b2b833c2f03b4bca Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Thu, 5 Jun 2025 09:20:13 +0200 Subject: [PATCH 07/20] Corretgit test --- src/tests/issues.test.jsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/tests/issues.test.jsx b/src/tests/issues.test.jsx index b7c9cb3..c125068 100644 --- a/src/tests/issues.test.jsx +++ b/src/tests/issues.test.jsx @@ -1,10 +1,10 @@ import { transformAssignedIssuesDataForLineChart, - getRadarAndPieDataAssigned, - getGaugeDataAssigned, - getGaugeDataAssignedPerUser, - getGaugeDataClosedPerUser, - getGaugeDataHavePR, + getRadarAndPieDataIssuesAssigned, + getGaugeDataIssuesAssigned, + getGaugeDataAssignedIssuesPerUser, + getGaugeDataClosedIssuesPerUser, + getGaugeDataIssuesHavePR, } from '../domain/issues'; describe('issues', () => { @@ -49,31 +49,31 @@ describe('issues', () => { }); test('getRadarAndPieDataAssigned returns radarData and pieData excluding non_assigned and total', () => { - expect(getRadarAndPieDataAssigned(mockData)).toEqual({ + expect(getRadarAndPieDataIssuesAssigned(mockData)).toEqual({ radarData: { pau: 5, lluis: 5 }, pieData: [['pau', 5], ['lluis', 5]], }); }); test('getGaugeDataAssigned returns correct overall assigned percentage', () => { - expect(getGaugeDataAssigned(mockData)).toEqual((20 - 10) / 20); + expect(getGaugeDataIssuesAssigned(mockData)).toEqual((20 - 10) / 20); }); test('getGaugeDataAssignedPerUser returns array with user and percentage', () => { - expect(getGaugeDataAssignedPerUser(mockData)).toEqual([ + expect(getGaugeDataAssignedIssuesPerUser(mockData)).toEqual([ { user: 'pau', percentage: 5 / 10 }, { user: 'lluis', percentage: 5 / 10 }, ]); }); test('getGaugeDataClosedPerUser returns array with user and percentage', () => { - expect(getGaugeDataClosedPerUser(mockData)).toEqual([ + expect(getGaugeDataClosedIssuesPerUser(mockData)).toEqual([ { user: 'pau', percentage: 3 / 5 }, { user: 'lluis', percentage: 2 / 5 }, ]); }); - test('getGaugeDataHavePR returns correct percentage of PRs', () => { - expect(getGaugeDataHavePR(mockData)).toEqual(7 / 10); + test('getGaugeDataIssuesHavePR returns correct percentage of PRs', () => { + expect(getGaugeDataIssuesHavePR(mockData)).toEqual(7 / 10); }); }); From d6985cffdb9374434a517666feded714f061afe3 Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Thu, 5 Jun 2025 09:42:52 +0200 Subject: [PATCH 08/20] Canviat nom a PullRequestsPage i afegit tests --- src/App.jsx | 4 +- ...{pullRequests.jsx => pullRequestsPage.jsx} | 0 src/tests/pullRequests.test.jsx | 119 ++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) rename src/pages/{pullRequests.jsx => pullRequestsPage.jsx} (100%) create mode 100644 src/tests/pullRequests.test.jsx diff --git a/src/App.jsx b/src/App.jsx index b6b33f4..728d299 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import Header from "./components/header"; import Index from "./pages/index"; -import PullRequests from "./pages/pullRequests"; +import PullRequestsPage from "./pages/PullRequestsPage"; import CommitsPage from "./pages/commitsPage"; import IssuesPage from "./pages/issuesPage"; import Individual from "./pages/individual"; @@ -86,7 +86,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> diff --git a/src/pages/pullRequests.jsx b/src/pages/pullRequestsPage.jsx similarity index 100% rename from src/pages/pullRequests.jsx rename to src/pages/pullRequestsPage.jsx diff --git a/src/tests/pullRequests.test.jsx b/src/tests/pullRequests.test.jsx new file mode 100644 index 0000000..30b6989 --- /dev/null +++ b/src/tests/pullRequests.test.jsx @@ -0,0 +1,119 @@ +import { + transformCreatedPRsDataForLineChart, + transformMergedPRsDataForLineChart, + getRadarAndPieDataForCreatedPRs, + getRadarAndPieDataForMergedPRs, + getGaugeChartDataMergedPRs, + getGaugeChartDataReviewedPRs, + getGaugeChartDataMergesPRs, + getGaugeDataCreatedPRsPerUser, + getGaugeDataMergedPRsPerUser, +} from '../domain/pullRequests'; + +describe('pullRequests', () => { + const mockPRsData = { + '2025-06-01': { + pull_requests: { + created: { + pau: 2, + lluis: 3, + }, + merged_per_member: { + pau: 1, + lluis: 2, + }, + }, + }, + '2025-06-02': { + pull_requests: { + created: { + pau: 1, + lluis: 4, + }, + merged_per_member: { + pau: 2, + lluis: 1, + }, + }, + }, + }; + + test('transformCreatedPRsDataForLineChart works as expected', () => { + const { xDataCreated, seriesDataCreated } = transformCreatedPRsDataForLineChart(mockPRsData); + expect(xDataCreated).toEqual(['2025-06-01', '2025-06-02']); + expect(seriesDataCreated).toEqual([ + { name: 'pau', data: [2, 1] }, + { name: 'lluis', data: [3, 4] }, + ]); + }); + + test('transformMergedPRsDataForLineChart works as expected', () => { + const { xDataMerged, seriesDataMerged } = transformMergedPRsDataForLineChart(mockPRsData); + expect(xDataMerged).toEqual(['2025-06-01', '2025-06-02']); + expect(seriesDataMerged).toEqual([ + { name: 'pau', data: [1, 2] }, + { name: 'lluis', data: [2, 1] }, + ]); + }); + + const aggregatedData = { + pull_requests: { + created: { + pau: 3, + lluis: 5, + }, + merged_per_member: { + pau: 2, + lluis: 3, + }, + total: 10, + merged: 5, + closed: 2, + not_merged_by_author: 3, + }, + commit_merges: 6, + }; + + test('getRadarAndPieDataForCreatedPRs returns correct radar and pie data', () => { + const { radarDataCreated, pieDataCreated } = getRadarAndPieDataForCreatedPRs(aggregatedData); + expect(radarDataCreated).toEqual({ pau: 3, lluis: 5 }); + expect(pieDataCreated).toEqual([['pau', 3], ['lluis', 5]]); + }); + + test('getRadarAndPieDataForMergedPRs returns correct radar and pie data', () => { + const { radarDataMerged, pieDataMerged } = getRadarAndPieDataForMergedPRs(aggregatedData); + expect(radarDataMerged).toEqual({ pau: 2, lluis: 3 }); + expect(pieDataMerged).toEqual([['pau', 2], ['lluis', 3]]); + }); + + test('getGaugeChartDataMergedPRs returns correct percentage', () => { + const result = getGaugeChartDataMergedPRs(aggregatedData); + expect(result).toBe(5 / (10 - 2)); // 5 / 8 + }); + + test('getGaugeChartDataReviewedPRs returns correct percentage', () => { + const result = getGaugeChartDataReviewedPRs(aggregatedData); + expect(result).toBe(3 / 5); + }); + + test('getGaugeChartDataMergesPRs returns correct percentage', () => { + const result = getGaugeChartDataMergesPRs(aggregatedData); + expect(result).toBe(5 / 6); + }); + + test('getGaugeDataCreatedPRsPerUser returns correct user percentages', () => { + const result = getGaugeDataCreatedPRsPerUser(aggregatedData); + expect(result).toEqual([ + { user: 'pau', percentage: 3 / 10 }, + { user: 'lluis', percentage: 5 / 10 }, + ]); + }); + + test('getGaugeDataMergedPRsPerUser returns correct user percentages', () => { + const result = getGaugeDataMergedPRsPerUser(aggregatedData); + expect(result).toEqual([ + { user: 'pau', percentage: 2 / 5 }, + { user: 'lluis', percentage: 3 / 5 }, + ]); + }); +}); From 4a0c2220ec20bd503b8326534b050f3fad7bd05c Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Thu, 5 Jun 2025 12:58:31 +0200 Subject: [PATCH 09/20] Canvit nom a PullRequestsPage --- src/pages/pullRequestsPage.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/pullRequestsPage.jsx b/src/pages/pullRequestsPage.jsx index 82a0939..a5e5362 100644 --- a/src/pages/pullRequestsPage.jsx +++ b/src/pages/pullRequestsPage.jsx @@ -17,7 +17,7 @@ import { } from '../domain/pullRequests' import { filterHistoricData } from '../domain/utils'; -function PullRequests({ data,historicData,features }) { +function PullRequestsPage({ data,historicData,features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalPR', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangePR', "7"); const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; @@ -233,4 +233,4 @@ function PullRequests({ data,historicData,features }) { ); } -export default PullRequests; +export default PullRequestsPage; From 0bb9baf532f670c14e88eceba4b17e15a1b14caf Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Fri, 6 Jun 2025 11:03:06 +0200 Subject: [PATCH 10/20] Refactor project --- src/domain/projects.jsx | 330 +++++++++++++++++++++++++++++ src/pages/projects.jsx | 409 ++++++------------------------------ src/tests/projects.test.jsx | 185 ++++++++++++++++ 3 files changed, 585 insertions(+), 339 deletions(-) create mode 100644 src/domain/projects.jsx create mode 100644 src/tests/projects.test.jsx diff --git a/src/domain/projects.jsx b/src/domain/projects.jsx new file mode 100644 index 0000000..21d4e19 --- /dev/null +++ b/src/domain/projects.jsx @@ -0,0 +1,330 @@ +export const getActiveIteration = (data) => { + const has_iterations = data.project.has_iterations; + const today = new Date(); + if (!has_iterations) return "total"; + const iterations = data.project.iterations + for (const key in iterations) { + const { startDate, endDate } = iterations[key]; + const start = new Date(startDate); + const end = new Date(endDate); + + if (today >= start && today <= end) { + return key; + } + } + return "total"; + }; + +export const filterHistoricDataByIteration = (data,dataHist, iterationName) => { + if (!data) return null; + if (iterationName === "total") return dataHist; + + const iteration = data.project.iterations[iterationName] || null; + if (!iteration) return dataHist; + + const startDate = new Date(iteration.startDate); + const endDate = new Date(iteration.endDate); + + const filtered = {}; + for (const dateStr in dataHist) { + const date = new Date(dateStr); + if (date >= startDate && date <= endDate) { + filtered[dateStr] = dataHist[dateStr]; + } + } + return filtered; + }; + +export const transformAssignedTasksDataForLineChart = (data,dataHistoric, selectedIteration, iterations) => { + let allDates = []; + if (!dataHistoric) return { xDataAssigned: [], seriesDataAssigned: [] } + + if (selectedIteration === "total") { + allDates = Object.keys(dataHistoric).sort((a,b) => new Date(a) - new Date(b)); + } else { + const iteration = iterations[selectedIteration]; + if (!iteration) return { xDataAssigned: [], seriesDataAssigned: [] }; + + const startDate = new Date(iteration.startDate); + const endDate = new Date(iteration.endDate); + + const dates = []; + let currentDate = new Date(startDate); + while (currentDate <= endDate) { + const dateStr = currentDate.toISOString().split('T')[0]; + dates.push(dateStr); + currentDate.setDate(currentDate.getDate() + 1); + } + allDates = dates; + } + + const userSeries = {}; + const allUsers = new Set(); + + const assignedPerMember = data.project.metrics_by_iteration["total"]?.assigned_per_member || {}; + + Object.keys(assignedPerMember) + .filter(u => u !== "non_assigned") + .forEach(u => allUsers.add(u)); + + allUsers.forEach(u => { + userSeries[u] = []; + }); + + for (const date of allDates) { + const dayData = dataHistoric[date]; + const iterationData = dayData?.project?.metrics_by_iteration?.[selectedIteration]; + const assignedPerMember = iterationData?.assigned_per_member || {}; + allUsers.forEach(u => { + userSeries[u].push(assignedPerMember[u] || null); + }); + } + + const seriesDataAssigned = Array.from(allUsers).map(u => ({ + name: u, + data: userSeries[u], + })); + + return { xDataAssigned: allDates, seriesDataAssigned }; +}; + +export const transformFeatureDataForAreaChart = (data) => { + const xDataFeature = []; + const knownKeys = { + total_features_done: 'Done', + total_features_in_progress: 'In Progress', + total_features_todo: 'To Do', + }; + + const baseFeatureData = { + 'Done': [], + 'In Progress': [], + 'To Do': [], + }; + + const otherKeysData = {}; + const allOtherKeys = new Set(); + + for (const date in data) { + const metrics = data[date]?.project?.metrics_by_iteration?.total; + if (!metrics) continue; + + for (const [key, value] of Object.entries(metrics)) { + if ( + !Object.keys(knownKeys).includes(key) && + typeof value === 'number' && + key.startsWith('total_features_') && + Number.isInteger(value) + ) { + allOtherKeys.add(key); + } + } + } + + for (const key of allOtherKeys) { + otherKeysData[key] = []; + } + + for (const date in data) { + const metrics = data[date]?.project?.metrics_by_iteration?.total || {}; + + xDataFeature.push(date); + + for (const [key, label] of Object.entries(knownKeys)) { + baseFeatureData[label].push(metrics[key] || 0); + } + + for (const key of allOtherKeys) { + const value = metrics[key]; + otherKeysData[key].push(Number.isInteger(value) ? value : 0); + } + } + const baseSeries = [ + { label: 'Done', data: baseFeatureData['Done'], color: 'rgb(0, 255, 0)' }, + { label: 'In Progress', data: baseFeatureData['In Progress'], color: 'orange' }, + { label: 'To Do', data: baseFeatureData['To Do'], color: 'rgb(255, 0, 0)' } + ]; + + const otherSeries = Object.entries(otherKeysData).map(([key, data]) => ({ + label: key + .replace('total_features_', '') + .replace(/_/g, ' ') + .replace(/\b\w/g, c => c.toUpperCase()), + data + })); + + const allSeries = [...baseSeries, ...otherSeries]; + return {xDataFeature, allSeries }; + }; + +const formatDate = (dateStr) => { + const d = new Date(dateStr); + const day = String(d.getDate()).padStart(2, '0'); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const year = d.getFullYear(); + return `${day}/${month}/${year}`; +} + +export const getRadarPieDataAssignedTasks = (data,iteration) => { + const { non_assigned, ...assignedPerMember } = data.project.metrics_by_iteration[iteration].assigned_per_member; + const radarDataAssigned = assignedPerMember; + const pieDataAssigned = Object.entries(radarDataAssigned) + return {radarDataAssigned,pieDataAssigned} +} +export const getDateRangeForIteration = (data,iterationName) => { + const iteration = data.project.iterations[iterationName]; + + if (!iteration) return ''; + return `${formatDate(iteration.startDate)} - ${formatDate(iteration.endDate)}`; + } + +export const getIssueTypeDataForChart = (projectData,selectedIteration) => { + + const iterationData = projectData?.project?.metrics_by_iteration?.[selectedIteration]; + + if (!iterationData) return []; + + const { + total_tasks = 0, + total_bugs = 0, + total_features = 0, + } = iterationData; + const typeColorsPieChart = ["orange", "#0f58ff","red"]; + const typePieChartData = [ + ['Tasks', total_tasks], + ['Features', total_features], + ['Bugs', total_bugs], + ] + return { typePieChartData, typeColorsPieChart} + }; + +export const getFeatureDataForChart = (projectData, selectedIteration) => { + const iterationData = projectData?.project?.metrics_by_iteration?.[selectedIteration]; + if (!iterationData) return { baseData: [], otherData: [] }; + + const baseData = []; + const otherData = []; + + const knownKeys = { + total_features_todo: 'To Do', + total_features_in_progress: 'In Progress', + total_features_done: 'Done', + }; + + for (const [key, label] of Object.entries(knownKeys)) { + const value = iterationData[key] || 0; + baseData.push({ label, value }); + } + + for (const [key, value] of Object.entries(iterationData)) { + if ( + key.startsWith('total_features_') && + !knownKeys.hasOwnProperty(key) + ) { + const label = key + .replace('total_features_', '') + .replace(/_/g, ' ') + .replace(/\b\w/g, c => c.toUpperCase()); + otherData.push({ label, value }); + } + } + const featurePieChartData = [ + ...baseData.map(item => [item.label, item.value]), + ...otherData.map(item => [item.label, item.value]) + ]; const featureColorsPieChart = ["green", "orange","red"]; + return {featurePieChartData,featureColorsPieChart}; +} + +export const getGaugeDataTasksAssigned = (data,iteration) => { + const { non_assigned, ...assignedPerMember } = data.project.metrics_by_iteration[iteration].assigned_per_member; + const totalTasks = data.project.metrics_by_iteration[iteration].total_tasks; + const totalAssigned = Object.values(assignedPerMember).reduce((sum, current) => sum + current, 0); + const percentageTasksAssigned = totalTasks > 0 ? totalAssigned / totalTasks : 0; + return percentageTasksAssigned; +} + +export const getGaugeDataTasksStandardStatus = (data,iteration) => { + const totalTasks = data.project.metrics_by_iteration[iteration].total_tasks; + const totalInProgress = data.project.metrics_by_iteration[iteration].in_progress || 0 + const totalDone = data.project.metrics_by_iteration[iteration].done || 0 + const totalToDo = data.project.metrics_by_iteration[iteration].todo || 0 + + const totalStandardStatuses = totalInProgress + totalDone + totalToDo + const percentageStandardStatus = totalTasks > 0 ? totalStandardStatuses / totalTasks : 0; + return percentageStandardStatus; +} + +export const getGaugeDataItemsIssues = (data,iteration) => { + const totalIssues = data.project.metrics_by_iteration[iteration].total_issues; + const totalItems = data.project.metrics_by_iteration[iteration].total + const percentageItemsIssues = totalItems > 0 ? totalIssues / totalItems : 0; + return percentageItemsIssues; +} + +export const getGaugeDataItemIssuesWithType= (data,iteration) => { + const totalIssues = data.project.metrics_by_iteration[iteration].total_issues; + const totalIssuesWithType = data.project.metrics_by_iteration[iteration].total_issues_with_type + const percentageItemIssuesWithType = totalIssues > 0 ? totalIssuesWithType / totalIssues : 0 + + return percentageItemIssuesWithType; +} + +export const getGaugeDataItemIssuesWithIteration= (data) => { + const taskDataNoIteration = data.project.metrics_by_iteration['no_iteration'] + const taskDataTotal = data.project.metrics_by_iteration['total'] + const itemsTotal = taskDataTotal.total + const itemsNoIteration = taskDataNoIteration.total + const percentageIteration = itemsTotal > 0 ? (itemsTotal - itemsNoIteration) / itemsTotal : 0 + + return percentageIteration; +} + +export const getGaugeDataAssignedTasksPerUser = (data,iteration) => { + const { non_assigned, ...assignedPerMember } = data.project.metrics_by_iteration[iteration].assigned_per_member; + const totalAssigned = Object.values(assignedPerMember).reduce((sum, current) => sum + current, 0); + return Object.entries(assignedPerMember).map(([user, userTasks]) => { + return { + user, + percentage: totalAssigned > 0 ? userTasks / totalAssigned : 0, + }; + }); +}; + +export const getGaugeDataInProgressTasksPerUser = (data,iteration) => { + const inProgresPerMember = data.project.metrics_by_iteration[iteration].in_progress_per_member || 0; + return Object.entries(inProgresPerMember).map(([user, inProgress]) => { + return { + user, + percentage: inProgress > 0 ? 1 : 0, + }; + }); +}; + +export const getGaugeDataDoneTasksPerUser = (data,iteration) => { + const { non_assigned, ...assignedPerMember } = data.project.metrics_by_iteration[iteration].assigned_per_member; + const donePerMember = data.project.metrics_by_iteration[iteration].done_per_member || 0; + return Object.entries(donePerMember).map(([user, done]) => { + const assigned = assignedPerMember[user] + return { + user, + percentage: assigned > 0 ? done / assigned : 0, + }; + }); +}; + +export const getGaugeDataStandardStatusTasksPerUser = (data,iteration) => { + const { non_assigned, ...assignedPerMember } = data.project.metrics_by_iteration[iteration].assigned_per_member; + const donePerMember = data.project.metrics_by_iteration[iteration].done_per_member || 0; + const inProgressPerMember = data.project.metrics_by_iteration[iteration].in_progress_per_member || 0; + const toDoPerMember = data.project.metrics_by_iteration[iteration].todo_per_member || 0; + return Object.entries(assignedPerMember).map(([user, assigned]) => { + const done = donePerMember[user] + const inProgress = inProgressPerMember[user] + const toDo = toDoPerMember[user] + const standardStatus = done + inProgress + toDo + return { + user, + percentage: assigned > 0 ? standardStatus / assigned : 0, + }; + }); +}; \ No newline at end of file diff --git a/src/pages/projects.jsx b/src/pages/projects.jsx index b1c65f8..6cb3dcb 100644 --- a/src/pages/projects.jsx +++ b/src/pages/projects.jsx @@ -5,66 +5,33 @@ import PieChart from '../components/pieChart'; import LineChartMultiple from '../components/lineChartMultiple'; import AreaChartMultiple from '../components/areaChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; - +import { + getActiveIteration, + filterHistoricDataByIteration, + getRadarPieDataAssignedTasks, + transformAssignedTasksDataForLineChart, + transformFeatureDataForAreaChart, + getDateRangeForIteration, + getIssueTypeDataForChart, + getFeatureDataForChart, + getGaugeDataTasksAssigned, + getGaugeDataTasksStandardStatus, + getGaugeDataItemsIssues, + getGaugeDataItemIssuesWithType, + getGaugeDataItemIssuesWithIteration, + getGaugeDataAssignedTasksPerUser, + getGaugeDataInProgressTasksPerUser, + getGaugeDataDoneTasksPerUser, + getGaugeDataStandardStatusTasksPerUser +} from '../domain/projects' +import { filterHistoricData } from '../domain/utils'; import '../styles/commits.css'; function Projects({ data,historicData,features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalProject', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeProject', "7"); - const today = new Date(); - -const getActiveIteration = () => { - const has_iterations = data.project.has_iterations; - if (!has_iterations) return "total"; - const iterations = data.project.iterations - for (const key in iterations) { - const { startDate, endDate } = iterations[key]; - const start = new Date(startDate); - const end = new Date(endDate); - - if (today >= start && today <= end) { - return key; - } - } - return "total"; - }; - const [selectedIteration, setSelectedIteration] = usePersistentStateSession("activeIteration",getActiveIteration()); - const filterHistoricData = (data, days) => { - if (days === "lifetime") return data; - - const today = new Date(); - const cutoff = new Date(today); - cutoff.setDate(today.getDate() - parseInt(days)); - const cutoffDateString = cutoff.toISOString().split("T")[0]; - const filtered = {}; - for (const date in data) { - if (date >= cutoffDateString) { - filtered[date] = data[date]; - } - } - - return filtered; - }; - const filterHistoricDataByIteration = (dataHist, iterationName) => { - if (!data) return null; - if (iterationName === "total") return dataHist; - - const iteration = data.project.iterations[iterationName] || null; - if (!iteration) return dataHist; - - const startDate = new Date(iteration.startDate); - const endDate = new Date(iteration.endDate); - - const filtered = {}; - for (const dateStr in dataHist) { - const date = new Date(dateStr); - if (date >= startDate && date <= endDate) { - filtered[dateStr] = dataHist[dateStr]; - } - } - return filtered; - }; + const [selectedIteration, setSelectedIteration] = usePersistentStateSession("activeIteration",getActiveIteration(data)); const iterationsOrdered = Object.keys(data.project.iterations); const iterations = Object.keys(data.project.metrics_by_iteration); @@ -73,256 +40,37 @@ const getActiveIteration = () => { ...iterationsOrdered, ...iterations.filter(name => name === "total"), ]; + const totalPeople = Object.keys(data.avatars).length; const filteredhistoricaData = historicData ? (selectedIteration === "total" ? filterHistoricData(historicData, dateRange) - : filterHistoricDataByIteration(historicData, selectedIteration)) + : filterHistoricDataByIteration(data,historicData, selectedIteration)) : null; - const taskData = data.project.metrics_by_iteration[selectedIteration]; - const totalTasks = taskData.total_tasks || 0 - const totalInProgress = taskData.in_progress || 0 - const totalDone = taskData.done || 0 - const totalToDo = taskData.todo || 0 - - const totalStandardStatuses = totalInProgress + totalDone + totalToDo - - const { non_assigned, ...assignedPerMember } = taskData.assigned_per_member; - const totalAssigned = Object.values(assignedPerMember).reduce((sum, current) => sum + current, 0); - const inProgresPerMember = taskData.in_progress_per_member || 0; - const donePerMember = taskData.done_per_member || 0; - const todoPerMember = taskData.todo_per_member || 0; - const totalPeople = Object.keys(data.avatars).length; - - const totalIssues = taskData.total_issues - const totalIssuesWithType = taskData.total_issues_with_type - const totalItems = taskData.total - - const taskDataNoIteration = data.project.metrics_by_iteration['no_iteration'] - const taskDataTotal = data.project.metrics_by_iteration['total'] - const itemsTotal = taskDataTotal.total - const itemsNoIteration = taskDataNoIteration.total - const transformAssignedDataForLineChart = (dataHistoric, selectedIteration, iterations) => { - let allDates = []; - if (!dataHistoric) return { xDataAssigned: [], seriesDataAssigned: [] } - - if (selectedIteration === "total") { - allDates = Object.keys(dataHistoric).sort((a,b) => new Date(a) - new Date(b)); - } else { - const iteration = iterations[selectedIteration]; - if (!iteration) return { xDataAssigned: [], seriesDataAssigned: [] }; - - const startDate = new Date(iteration.startDate); - const endDate = new Date(iteration.endDate); - - const dates = []; - let currentDate = new Date(startDate); - while (currentDate <= endDate) { - const dateStr = currentDate.toISOString().split('T')[0]; - dates.push(dateStr); - currentDate.setDate(currentDate.getDate() + 1); - } - allDates = dates; - } - - const userSeries = {}; - const allUsers = new Set(); - - const assignedPerMember = data.project.metrics_by_iteration["total"]?.assigned_per_member || {}; - - Object.keys(assignedPerMember) - .filter(u => u !== "non_assigned") - .forEach(u => allUsers.add(u)); - - allUsers.forEach(u => { - userSeries[u] = []; - }); - - for (const date of allDates) { - const dayData = dataHistoric[date]; - const iterationData = dayData?.project?.metrics_by_iteration?.[selectedIteration]; - const assignedPerMember = iterationData?.assigned_per_member || {}; - allUsers.forEach(u => { - userSeries[u].push(assignedPerMember[u] || null); - }); - } - - const seriesDataAssigned = Array.from(allUsers).map(u => ({ - name: u, - data: userSeries[u], - })); - - return { xDataAssigned: allDates, seriesDataAssigned }; -}; - - const { xDataAssigned, seriesDataAssigned } = transformAssignedDataForLineChart( + const {radarDataAssigned,pieDataAssigned} = getRadarPieDataAssignedTasks(data,selectedIteration) + const { xDataAssigned, seriesDataAssigned } = transformAssignedTasksDataForLineChart( + data, filteredhistoricaData, selectedIteration, data.project.iterations ); - const transformFeatureDataForAreaChart = (data) => { - const xDataFeature = []; - const knownKeys = { - total_features_done: 'Done', - total_features_in_progress: 'In Progress', - total_features_todo: 'To Do', - }; - - const baseFeatureData = { - Done: [], - 'In Progress': [], - 'To Do': [], - }; - - const otherKeysData = {}; - const allOtherKeys = new Set(); - - for (const date in data) { - const metrics = data[date]?.project?.metrics_by_iteration?.total; - if (!metrics) continue; - - for (const [key, value] of Object.entries(metrics)) { - if ( - !Object.keys(knownKeys).includes(key) && - typeof value === 'number' && - key.startsWith('total_features_') && - Number.isInteger(value) - ) { - allOtherKeys.add(key); - } - } - } - - for (const key of allOtherKeys) { - otherKeysData[key] = []; - } - - for (const date in data) { - const metrics = data[date]?.project?.metrics_by_iteration?.total || {}; - - xDataFeature.push(date); - - for (const [key, label] of Object.entries(knownKeys)) { - baseFeatureData[label].push(metrics[key] || 0); - } - - for (const key of allOtherKeys) { - const value = metrics[key]; - otherKeysData[key].push(Number.isInteger(value) ? value : 0); - } - } - - return { - xDataFeature, - doneFeature: baseFeatureData['Done'], - inProgressFeature: baseFeatureData['In Progress'], - toDoFeature: baseFeatureData['To Do'], - otherKeysData, - }; - }; - - - const { - xDataFeature, - doneFeature, - inProgressFeature, - toDoFeature, - otherKeysData - } = transformFeatureDataForAreaChart(filteredhistoricaData); - - const baseSeries = [ - { label: 'Done', data: doneFeature, color: 'rgb(0, 255, 0)' }, - { label: 'In Progress', data: inProgressFeature, color: 'orange' }, - { label: 'To Do', data: toDoFeature, color: 'rgb(255, 0, 0)' } - ]; - - const otherSeries = Object.entries(otherKeysData).map(([key, data]) => ({ - label: key.replace('total_features_', '') - .replace(/_/g, ' ') - .replace(/\b\w/g, c => c.toUpperCase()), - data - })); - - const allSeries = [...baseSeries, ...otherSeries]; - - - function formatDate(dateStr) { - const d = new Date(dateStr); - const day = String(d.getDate()).padStart(2, '0'); - const month = String(d.getMonth() + 1).padStart(2, '0'); - const year = d.getFullYear(); - return `${day}/${month}/${year}`; - } - function getDateRangeForIteration(iterationName) { - const iteration = data.project.iterations[iterationName]; - - if (!iteration) return ''; - return `${formatDate(iteration.startDate)} - ${formatDate(iteration.endDate)}`; - } + const { xDataFeature,allSeries } = transformFeatureDataForAreaChart(filteredhistoricaData); - function getTypeDataForChart(projectData,selectedIteration) { - - const iterationData = projectData?.project?.metrics_by_iteration?.[selectedIteration]; - - if (!iterationData) return []; - - const { - total_tasks = 0, - total_bugs = 0, - total_features = 0, - } = iterationData; - - return [ - ['Tasks', total_tasks], - ['Features', total_features], - ['Bugs', total_bugs], - ]; - }; - -function getFeatureDataForChart(projectData, selectedIteration) { - const iterationData = projectData?.project?.metrics_by_iteration?.[selectedIteration]; - if (!iterationData) return { baseData: [], otherData: [] }; - - const baseData = []; - const otherData = []; - - const knownKeys = { - total_features_todo: 'To Do', - total_features_in_progress: 'In Progress', - total_features_done: 'Done', - }; + const {typePieChartData,typeColorsPieChart} = getIssueTypeDataForChart(data, selectedIteration); - for (const [key, label] of Object.entries(knownKeys)) { - const value = iterationData[key] || 0; - baseData.push({ label, value }); - } + const {featurePieChartData,featureColorsPieChart} = getFeatureDataForChart(data, selectedIteration); - for (const [key, value] of Object.entries(iterationData)) { - if ( - key.startsWith('total_features_') && - !knownKeys.hasOwnProperty(key) - ) { - const label = key - .replace('total_features_', '') - .replace(/_/g, ' ') - .replace(/\b\w/g, c => c.toUpperCase()); - otherData.push({ label, value }); - } - } - return { baseData, otherData }; -} - - const typePieChartData = getTypeDataForChart(data, selectedIteration); - const typeColorsPieChart = ["orange", "#0f58ff","red"]; - - const { baseData, otherData } = getFeatureDataForChart(data, selectedIteration); - - const featurePieChartData = [ - ...baseData.map(item => [item.label, item.value]), - ...otherData.map(item => [item.label, item.value]) - ]; const featureColorsPieChart = ["green", "orange","red"]; + const percentageTasksAssigned = getGaugeDataTasksAssigned(data,selectedIteration) + const percentageStandardStatus = getGaugeDataTasksStandardStatus(data,selectedIteration) + const percentageItemsIssues = getGaugeDataItemsIssues(data,selectedIteration) + const percentageItemIssuesWithType = getGaugeDataItemIssuesWithType(data,selectedIteration) + const percentageIteration = getGaugeDataItemIssuesWithIteration(data) + const gaugeDataAssignedTasks = getGaugeDataAssignedTasksPerUser(data,selectedIteration); + const gaugeDataInProgressTasks = getGaugeDataInProgressTasksPerUser(data,selectedIteration); + const gaugeDataDoneTasks = getGaugeDataDoneTasksPerUser(data,selectedIteration); + const gaugeDataStandardTasks = getGaugeDataStandardStatusTasksPerUser(data,selectedIteration) return (
@@ -397,7 +145,7 @@ function getFeatureDataForChart(projectData, selectedIteration) { ))}
- {getDateRangeForIteration(selectedIteration)} + {getDateRangeForIteration(data,selectedIteration)}
@@ -405,8 +153,9 @@ function getFeatureDataForChart(projectData, selectedIteration) {
@@ -433,12 +182,13 @@ function getFeatureDataForChart(projectData, selectedIteration) { 0 ? totalAssigned / totalTasks : 0} + percentage={percentageTasksAssigned} totalPeople= {1} />
-
+

Tasks with Standard Status @@ -447,8 +197,9 @@ function getFeatureDataForChart(projectData, selectedIteration) {

0 ? totalStandardStatuses / totalTasks : 0} + key="Standard" + user="Tasks" + percentage={percentageStandardStatus} totalPeople= {1} />
@@ -461,8 +212,9 @@ function getFeatureDataForChart(projectData, selectedIteration) { 0 ? totalIssues / totalItems : 0} + percentage={percentageItemsIssues} totalPeople= {1} />
@@ -475,8 +227,9 @@ function getFeatureDataForChart(projectData, selectedIteration) { 0 ? totalIssuesWithType / totalIssues : 0} + percentage={percentageItemIssuesWithType} totalPeople= {1} />
@@ -491,7 +244,7 @@ function getFeatureDataForChart(projectData, selectedIteration) { 0 ? (itemsTotal - itemsNoIteration) / itemsTotal : 0} + percentage={percentageIteration} totalPeople= {1} />
)} @@ -507,20 +260,14 @@ function getFeatureDataForChart(projectData, selectedIteration) {
- {Object.keys(assignedPerMember).map((user) => { - if (user !== 'non_assigned') { - const userTasks = assignedPerMember[user]; - const percentage = totalAssigned > 0 ? userTasks / totalAssigned : 0; - return ( - - ); - } - return null; - })} + {gaugeDataAssignedTasks.map(({ user, percentage }) => ( + + ))}

Tasks In Progress per user @@ -534,17 +281,14 @@ function getFeatureDataForChart(projectData, selectedIteration) {

- {Object.keys(inProgresPerMember).map((user) => { - const inProgress = inProgresPerMember[user] - const percentage = inProgress > 0 ? 1 : 0; - return ( + {gaugeDataInProgressTasks.map(({ user, percentage }) => ( - ); - })} + ))}

Tasks Done per user @@ -556,18 +300,14 @@ function getFeatureDataForChart(projectData, selectedIteration) {

- {Object.keys(donePerMember).map((user) => { - const doneCount = donePerMember[user]; - const assigned = assignedPerMember[user]; - const percentage = assigned > 0 ? doneCount / assigned : 0; - return ( + {gaugeDataDoneTasks.map(({ user, percentage }) => ( - ); - })} + ))}

@@ -580,23 +320,14 @@ function getFeatureDataForChart(projectData, selectedIteration) {

- {Object.keys(assignedPerMember).map((user) => { - const assigned = assignedPerMember[user] || 0; - const inProgress = inProgresPerMember[user] || 0; - const doneCount = donePerMember[user] || 0; - const todoCount = todoPerMember[user] || 0; - const standardTotal = inProgress + doneCount + todoCount; - const percentage = assigned > 0 ? standardTotal / assigned : 0; - - return ( + {gaugeDataStandardTasks.map(({ user, percentage }) => ( - ); - })} + ))}
)} diff --git a/src/tests/projects.test.jsx b/src/tests/projects.test.jsx new file mode 100644 index 0000000..263da4d --- /dev/null +++ b/src/tests/projects.test.jsx @@ -0,0 +1,185 @@ +import { + getActiveIteration, + filterHistoricDataByIteration, + transformAssignedTasksDataForLineChart, + transformFeatureDataForAreaChart, + getRadarPieDataAssignedTasks, + getDateRangeForIteration, + getIssueTypeDataForChart, + getFeatureDataForChart, + getGaugeDataTasksAssigned, + getGaugeDataTasksStandardStatus, + getGaugeDataItemsIssues, + getGaugeDataItemIssuesWithType, + getGaugeDataItemIssuesWithIteration, + getGaugeDataAssignedTasksPerUser, + getGaugeDataInProgressTasksPerUser, + getGaugeDataDoneTasksPerUser, + getGaugeDataStandardStatusTasksPerUser +} from '../domain/projects'; + +describe('projects', () => { + const mockData = { + project: { + has_iterations: true, + iterations: { + it1: { + startDate: '2025-06-01', + endDate: '2025-06-03', + }, + }, + metrics_by_iteration: { + it1: { + assigned_per_member: { + pau: 3, + lluis: 2, + non_assigned: 6, + }, + in_progress_per_member: { + pau: 1, + lluis: 0, + }, + done_per_member: { + pau: 2, + lluis: 1, + }, + todo_per_member: { + pau: 0, + lluis: 1, + }, + new_state_per_member: { + pau: 0, + lluis: 1, + }, + in_progress: 3, + done: 4, + todo: 3, + new_state: 1, + total_tasks: 11, + total_issues: 4, + total_issues_with_type: 3, + total: 12, + total_features_todo: 1, + total_features_in_progress: 2, + total_features_done: 3, + total_features_new_state: 5, + total_bugs: 2, + total_features: 3 + }, + no_iteration: { + total: 1 + }, + total: { + total: 13, + } + } + } + }; + + const mockHistoricData = { + '2025-06-01': mockData, + '2025-06-02': mockData, + }; + +test('getActiveIteration returns correct iteration with mocked date', () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2025-06-02')); + + const result = getActiveIteration(mockData); + expect(result).toBe('it1'); + + vi.useRealTimers(); +}); + + test('filterHistoricDataByIteration filters data correctly', () => { + const result = filterHistoricDataByIteration(mockData, mockHistoricData, 'it1'); + expect(Object.keys(result)).toEqual(['2025-06-01', '2025-06-02']); + }); + + test('getRadarPieDataAssignedTasks returns radar and pie data', () => { + const result = getRadarPieDataAssignedTasks(mockData, 'it1'); + expect(result).toEqual({ + radarDataAssigned: { pau: 3, lluis: 2 }, + pieDataAssigned: [['pau', 3], ['lluis', 2]], + }); + }); + + test('getGaugeDataTasksAssigned returns correct percentage', () => { + expect(getGaugeDataTasksAssigned(mockData, 'it1')).toBe((5 / 11)); + }); + + test('getGaugeDataTasksStandardStatus returns correct percentage', () => { + expect(getGaugeDataTasksStandardStatus(mockData, 'it1')).toBe((10 / 11)); + }); + + test('getGaugeDataItemsIssues returns correct percentage', () => { + expect(getGaugeDataItemsIssues(mockData, 'it1')).toBe(4 / 12); + }); + + test('getGaugeDataItemIssuesWithType returns correct percentage', () => { + expect(getGaugeDataItemIssuesWithType(mockData, 'it1')).toBe(3 / 4); + }); + + test('getGaugeDataItemIssuesWithIteration returns correct percentage', () => { + expect(getGaugeDataItemIssuesWithIteration(mockData)).toBe((13 - 1) / 13); + }); + + test('getGaugeDataAssignedTasksPerUser returns per-user assignment percentages', () => { + expect(getGaugeDataAssignedTasksPerUser(mockData, 'it1')).toEqual([ + { user: 'pau', percentage: 3 / 5 }, + { user: 'lluis', percentage: 2 / 5 }, + ]); + }); + + test('getGaugeDataInProgressTasksPerUser returns 1 if in progress > 0', () => { + expect(getGaugeDataInProgressTasksPerUser(mockData, 'it1')).toEqual([ + { user: 'pau', percentage: 1 }, + { user: 'lluis', percentage: 0 }, + ]); + }); + + test('getGaugeDataDoneTasksPerUser returns done / assigned per user', () => { + expect(getGaugeDataDoneTasksPerUser(mockData, 'it1')).toEqual([ + { user: 'pau', percentage: 2 / 3 }, + { user: 'lluis', percentage: 1 / 2 }, + ]); + }); + + test('getGaugeDataStandardStatusTasksPerUser returns correct per user', () => { + expect(getGaugeDataStandardStatusTasksPerUser(mockData, 'it1')).toEqual([ + { user: 'pau', percentage: 1 }, + { user: 'lluis', percentage: 1 }, + ]); + }); + + test('getDateRangeForIteration returns formatted date range', () => { + expect(getDateRangeForIteration(mockData, 'it1')).toBe('01/06/2025 - 03/06/2025'); + }); + + test('getIssueTypeDataForChart returns pie data and colors', () => { + const { typePieChartData, typeColorsPieChart } = getIssueTypeDataForChart(mockData, 'it1'); + expect(typePieChartData).toEqual([ + ['Tasks', 11], + ['Features', 3], + ['Bugs', 2], + ]); + expect(typeColorsPieChart).toEqual(['orange', '#0f58ff', 'red']); + }); + + test('getFeatureDataForChart returns correct pie data and colors', () => { + const { featurePieChartData, featureColorsPieChart } = getFeatureDataForChart(mockData, 'it1'); + expect(featurePieChartData).toEqual([ + ['To Do', 1], + ['In Progress', 2], + ['Done', 3], + ['New State', 5], + ]); + expect(featureColorsPieChart).toEqual(['green', 'orange', 'red']); + }); + + test('transformFeatureDataForAreaChart transforms correctly', () => { + const result = transformFeatureDataForAreaChart(mockHistoricData); + expect(result.xDataFeature).toEqual(['2025-06-01', '2025-06-02']); + expect(result.allSeries.some(s => s.label === 'Done')).toBe(true); + }); +}); From db01d2e260c9c915b5377bbb4d40f0481c4676bf Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Fri, 6 Jun 2025 11:04:55 +0200 Subject: [PATCH 11/20] Fet que radarPieToggle guardi el seu estat entre sessions --- src/components/radarPieToggle.jsx | 5 +++-- src/pages/commitsPage.jsx | 4 ++-- src/pages/issuesPage.jsx | 1 + src/pages/pullRequestsPage.jsx | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/radarPieToggle.jsx b/src/components/radarPieToggle.jsx index e328759..8ce8e49 100644 --- a/src/components/radarPieToggle.jsx +++ b/src/components/radarPieToggle.jsx @@ -1,11 +1,12 @@ import React, { useState } from 'react'; +import usePersistentState from './usePersistentState' import RadarChart from './radarChart'; import PieChart from './pieChart'; import '../styles/radarPieToggle.css'; -const RadarPieToggle = ({ radarData, pieData, title }) => { - const [selectedChart, setSelectedChart] = useState('radar'); +const RadarPieToggle = ({ RadarPieID, radarData, pieData, title }) => { + const [selectedChart, setSelectedChart] = usePersistentState(RadarPieID,'radar'); const selectRadar = () => setSelectedChart('radar'); const selectPie = () => setSelectedChart('pie'); diff --git a/src/pages/commitsPage.jsx b/src/pages/commitsPage.jsx index 1430c32..d64d986 100644 --- a/src/pages/commitsPage.jsx +++ b/src/pages/commitsPage.jsx @@ -70,10 +70,10 @@ function CommitsPage({ data, historicData, features }) {

Summary

- +
- +

diff --git a/src/pages/issuesPage.jsx b/src/pages/issuesPage.jsx index c47faf2..ed06568 100644 --- a/src/pages/issuesPage.jsx +++ b/src/pages/issuesPage.jsx @@ -70,6 +70,7 @@ function IssuesPage({ data,historicData,features }) {
Date: Fri, 6 Jun 2025 11:05:32 +0200 Subject: [PATCH 12/20] Renamed projectPage --- src/pages/{projects.jsx => projectsPage.jsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/pages/{projects.jsx => projectsPage.jsx} (100%) diff --git a/src/pages/projects.jsx b/src/pages/projectsPage.jsx similarity index 100% rename from src/pages/projects.jsx rename to src/pages/projectsPage.jsx From 7aa30eaf8c45bf455cd8f5b61a176d0643c00ac6 Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Fri, 6 Jun 2025 11:07:49 +0200 Subject: [PATCH 13/20] Actualitzat App.jsx amb ProjectsPage --- src/App.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 728d299..6fd9c1d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,7 +5,7 @@ import PullRequestsPage from "./pages/PullRequestsPage"; import CommitsPage from "./pages/commitsPage"; import IssuesPage from "./pages/issuesPage"; import Individual from "./pages/individual"; -import Projects from "./pages/projects"; +import ProjectsPage from "./pages/projectsPage"; import { HashRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; function App() { @@ -87,7 +87,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> From e4fbf14fde3ca64886c758faee8b6e61b053f32a Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Fri, 6 Jun 2025 13:38:23 +0200 Subject: [PATCH 14/20] Refactor individual --- src/domain/commits.jsx | 25 ++++ src/domain/issues.jsx | 24 ++++ src/domain/projects.jsx | 68 ++++++++- src/domain/pullRequests.jsx | 26 +++- src/domain/utils.jsx | 4 + src/pages/individual.jsx | 236 +++++++++----------------------- src/pages/projectsPage.jsx | 4 +- src/tests/commits.test.jsx | 14 ++ src/tests/issues.test.jsx | 15 ++ src/tests/projects.test.jsx | 75 +++++++++- src/tests/pullRequests.test.jsx | 15 +- src/tests/utils.test.jsx | 19 ++- 12 files changed, 344 insertions(+), 181 deletions(-) diff --git a/src/domain/commits.jsx b/src/domain/commits.jsx index 4f5afd5..6cd9d83 100644 --- a/src/domain/commits.jsx +++ b/src/domain/commits.jsx @@ -99,3 +99,28 @@ export const getGaugeChartDataModifiedLines = (data) => { percentage: totalModified > 0 ? val.modified / totalModified : 0, })); }; + +export const transformCommitsDataForUser = (data, username) => { + const xDataCommits = []; + const yDataCommits = []; + + for (const date in data) { + xDataCommits.push(date); + yDataCommits.push(data[date].commits[username] || 0); + } + + return { xDataCommits, yDataCommits }; + }; + +export const transformModifiedLinesDataForUser = (data, username) => { + const xDataModifiedLines = []; + const yDataModifiedLines = []; + + for (const date in data) { + const userData = data[date].modified_lines[username].modified; + xDataModifiedLines.push(date); + yDataModifiedLines.push(userData); + } + + return { xDataModifiedLines, yDataModifiedLines }; + }; diff --git a/src/domain/issues.jsx b/src/domain/issues.jsx index c108188..2456a40 100644 --- a/src/domain/issues.jsx +++ b/src/domain/issues.jsx @@ -71,3 +71,27 @@ export const getGaugeDataIssuesHavePR = (data) => { const havePullRequest = data.issues.have_pull_request || 0; return totalClosed > 0 ? havePullRequest / totalClosed : 0; }; + +export const transformAssignedIssuesDataForUser = (data, username) => { + const xDataAssignedIssues = []; + const yDataAssignedIssues = []; + + for (const date in data) { + xDataAssignedIssues.push(date); + yDataAssignedIssues.push(data[date].issues?.assigned?.[username] || 0); + } + + return { xDataAssignedIssues, yDataAssignedIssues }; + }; + +export const transformClosedIssuesDataForUser = (data, username) => { + const xDataClosedIssues = []; + const yDataClosedIssues = []; + + for (const date in data) { + xDataClosedIssues.push(date); + yDataClosedIssues.push(data[date].issues?.closed?.[username] || 0); + } + + return { xDataClosedIssues, yDataClosedIssues }; + }; \ No newline at end of file diff --git a/src/domain/projects.jsx b/src/domain/projects.jsx index 21d4e19..0cf3bb3 100644 --- a/src/domain/projects.jsx +++ b/src/domain/projects.jsx @@ -327,4 +327,70 @@ export const getGaugeDataStandardStatusTasksPerUser = (data,iteration) => { percentage: assigned > 0 ? standardStatus / assigned : 0, }; }); -}; \ No newline at end of file +}; + +export const transformTasksAssignedDataForUser = (data, username) => { + const xDataTasksAssigned = []; + const yDataTasksAssigned = []; + + for (const date in data) { + xDataTasksAssigned.push(date); + const assigned = data[date]?.project?.metrics_by_iteration?.total?.assigned_per_member?.[username] || 0; + yDataTasksAssigned.push(assigned); + } + + return { xDataTasksAssigned, yDataTasksAssigned }; + }; + +export const transformTasksToDoDataForUser = (data, username) => { + const xDataTasksToDo = []; + const yDataTasksToDo = []; + + for (const date in data) { + xDataTasksToDo.push(date); + yDataTasksToDo.push(data[date].project?.metrics_by_iteration?.total?.todo_per_member?.[username] || 0); + } + + return { xDataTasksToDo, yDataTasksToDo }; + }; + +export const transformTasksInProgressDataForUser = (data, username) => { + const xDataTasksInProgress = []; + const yDataTasksInProgress = []; + + for (const date in data) { + xDataTasksInProgress.push(date); + yDataTasksInProgress.push(data[date].project?.metrics_by_iteration?.total?.in_progress_per_member?.[username] || 0); + } + + return { xDataTasksInProgress, yDataTasksInProgress }; + }; + + +export const transformTasksDoneDataForUser = (data, username) => { + const xDataTasksDone = []; + const yDataTasksDone = []; + + for (const date in data) { + xDataTasksDone.push(date); + const done = data[date]?.project?.metrics_by_iteration?.total?.done_per_member?.[username] || 0; + yDataTasksDone.push(done); + } + + return { xDataTasksDone, yDataTasksDone }; + }; + +export const transformTasksStandardDataForUser = (data, username) => { + const xDataTasksStandard = []; + const yDataTasksStandard = []; + + for (const date in data) { + xDataTasksStandard.push(date); + const todo = data[date]?.project?.metrics_by_iteration?.total?.todo_per_member?.[username] || 0; + const inProgress = data[date]?.project?.metrics_by_iteration?.total?.in_progress_per_member?.[username] || 0; + const done = data[date]?.project?.metrics_by_iteration?.total?.done_per_member?.[username] || 0; + yDataTasksStandard.push(todo + inProgress + done); + } + + return { xDataTasksStandard, yDataTasksStandard }; + }; diff --git a/src/domain/pullRequests.jsx b/src/domain/pullRequests.jsx index 70b7e35..f096074 100644 --- a/src/domain/pullRequests.jsx +++ b/src/domain/pullRequests.jsx @@ -104,4 +104,28 @@ export const getGaugeDataMergedPRsPerUser = (data) => { percentage: totalMerged > 0 ? merged / totalMerged : 0, }; }); -}; \ No newline at end of file +}; + +export const transformCreatedPRsDataForUser = (data, username) => { + const xDataCreatedPRs = []; + const yDataCreatedPRs = []; + + for (const date in data) { + xDataCreatedPRs.push(date); + yDataCreatedPRs.push(data[date].pull_requests?.created?.[username] || 0); + } + + return { xDataCreatedPRs, yDataCreatedPRs }; + }; + +export const transformMergedPRsDataForUser = (data, username) => { + const xDataMergedPRs = []; + const yDataMergedPRs = []; + + for (const date in data) { + xDataMergedPRs.push(date); + yDataMergedPRs.push(data[date].pull_requests?.merged_per_member?.[username] || 0); + } + + return { xDataMergedPRs, yDataMergedPRs }; + }; \ No newline at end of file diff --git a/src/domain/utils.jsx b/src/domain/utils.jsx index 1eed2e3..a1bb271 100644 --- a/src/domain/utils.jsx +++ b/src/domain/utils.jsx @@ -15,3 +15,7 @@ export const filterHistoricData = (data, days) => { return filtered; }; + +export const truncateName = (name, maxLength = 18) => { + return name.length > maxLength ? name.slice(0, maxLength) + '...' : name; +}; diff --git a/src/pages/individual.jsx b/src/pages/individual.jsx index b0a811d..08515f1 100644 --- a/src/pages/individual.jsx +++ b/src/pages/individual.jsx @@ -4,27 +4,45 @@ import LineChart from '../components/lineChart'; import GaugeChart from '../components/gaugeChart'; import usePersistentStateSession from '../components/usePersistentStateSession'; import usePersistentState from '../components/usePersistentState'; - +import { + transformCommitsDataForUser, + getGaugeChartDataCommits, + getGaugeChartDataModifiedLines, + transformModifiedLinesDataForUser +} from '../domain/commits' +import { + getGaugeDataAssignedIssuesPerUser, + getGaugeDataClosedIssuesPerUser, + transformAssignedIssuesDataForUser, + transformClosedIssuesDataForUser +} from '../domain/issues' +import { + getGaugeDataCreatedPRsPerUser, + getGaugeDataMergedPRsPerUser, + transformCreatedPRsDataForUser, + transformMergedPRsDataForUser +} from '../domain/pullRequests' +import { + getGaugeDataAssignedTasksPerUser, + getGaugeDataInProgressTasksPerUser, + getGaugeDataDoneTasksPerUser, + getGaugeDataStandardStatusTasksPerUser, + transformTasksAssignedDataForUser, + transformTasksToDoDataForUser, + transformTasksInProgressDataForUser, + transformTasksDoneDataForUser, + transformTasksStandardDataForUser, +} from '../domain/projects' +import { + filterHistoricData, + truncateName } +from '../domain/utils'; function Individual({ data, historicData, features }) { const [selectedUser, setSelectedUser] = usePersistentState("selectedUser",Object.keys(data.avatars)[0]); const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalIndividual', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeIndividual', "7"); - const filterHistoricData = (data, days) => { - if (days === "lifetime") return data; - - const today = new Date(); - const cutoff = new Date(today); - cutoff.setDate(today.getDate() - parseInt(days)); - const cutoffDateString = cutoff.toISOString().split("T")[0]; - const filtered = {}; - for (const date in data) { - if (date >= cutoffDateString) { - filtered[date] = data[date]; - } - } - return filtered; - }; + //Statistics const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; const users = Object.keys(data.avatars); const avatar = data.avatars[selectedUser] || ""; @@ -34,188 +52,60 @@ function Individual({ data, historicData, features }) { const longestStreak = data.longest_commit_streak_per_user[selectedUser] || 0; const issuesAssigned = data.issues.assigned[selectedUser] || 0; const issuesClosed = data.issues.closed[selectedUser] || 0; - const totalCommits = data.commits['total'] - const totalModifiedLines = data.modified_lines['total'].modified + const pullRequestsCreated = data.pull_requests.created[selectedUser] + const pullRequestsMerged = data.pull_requests.merged_per_member[selectedUser] const totalPeople = Object.keys(data.avatars).length; const tasksAssigned = data.project.metrics_by_iteration.total.assigned_per_member[selectedUser] || 0 const tasksClosed = data.project.metrics_by_iteration.total.done_per_member[selectedUser] || 0 const tasksInProgress = data.project.metrics_by_iteration.total.in_progress_per_member[selectedUser] || 0 const tasksTodo = data.project.metrics_by_iteration.total.todo_per_member[selectedUser] || 0 const tasksStandard = tasksClosed + tasksInProgress + tasksTodo - const truncateName = (name, maxLength = 18) => { - return name.length > maxLength ? name.slice(0, maxLength) + '...' : name; - }; - const userCommits = data.commits[selectedUser]; - const percentageCommits = totalCommits > 0 ? userCommits / totalCommits: 0; - const userModifiedLines = data.modified_lines[selectedUser].modified - const percentageModifiedlLines = totalModifiedLines > 0 ? userModifiedLines / totalModifiedLines : 0 - const totalAssignedIssues = data.issues.total - data.issues.assigned["non_assigned"] - const pullRequestsCreated = data.pull_requests.created[selectedUser] - const pullRequestsMerged = data.pull_requests.merged_per_member[selectedUser] - const percentageAssigned = totalAssignedIssues > 0 ? issuesAssigned / totalAssignedIssues: 0 - const percentageIssuesClosed = issuesAssigned > 0 ? issuesClosed/issuesAssigned: 0 - const percentageCreated = data.pull_requests.total > 0 ? data.pull_requests.created[selectedUser] / data.pull_requests.total: 0 - const percentageMerged = data.pull_requests.merged > 0 ? data.pull_requests.merged_per_member[selectedUser] / data.pull_requests.merged: 0 - const { non_assigned, ...assignedPerMember } = data.project.metrics_by_iteration.total.assigned_per_member; - const totalAssigned = Object.values(assignedPerMember).reduce((sum, current) => sum + current, 0); - const percentageTasksAssigned = totalAssigned > 0 ? tasksAssigned / totalAssigned : 0; - const percentageTasksDone = tasksAssigned > 0 ? tasksClosed / tasksAssigned : 0; - const percentageTasksInProgress = tasksInProgress > 0 ? tasksInProgress / tasksInProgress : 0; - const percentageTasksStandard = tasksAssigned > 0 ? tasksStandard / tasksAssigned : 0; - const transformCommitsDataForUser = (data, username) => { - const xDataCommits = []; - const yDataCommits = []; - - for (const date in data) { - xDataCommits.push(date); - yDataCommits.push(data[date].commits[username] || 0); - } - - return { xDataCommits, yDataCommits }; - }; - const { xDataCommits, yDataCommits } = transformCommitsDataForUser(filteredhistoricaData, selectedUser) - - const transformModifiedLinesDataForUser = (data, username) => { - const xDataModifiedLines = []; - const yDataModifiedLines = []; + //Gauges + const usersCommits = getGaugeChartDataCommits(data) + const percentageCommits = usersCommits.find(userObj => userObj.user === selectedUser).percentage; + const usersModifiedLines = getGaugeChartDataModifiedLines(data) + const percentageModifiedlLines = usersModifiedLines.find(userObj => userObj.user === selectedUser).percentage; + + const usersAssignedIssues = getGaugeDataAssignedIssuesPerUser(data) + const percentageAssigned = usersAssignedIssues.find(userObj => userObj.user === selectedUser).percentage; + const usersClosedIssues = getGaugeDataClosedIssuesPerUser(data) + const percentageIssuesClosed = usersClosedIssues.find(userObj => userObj.user === selectedUser).percentage; + + const usersCreatedPRs = getGaugeDataCreatedPRsPerUser(data) + const percentageCreated = usersCreatedPRs.find(userObj => userObj.user === selectedUser).percentage; + const usersMergedPRs = getGaugeDataMergedPRsPerUser(data) + const percentageMerged = usersMergedPRs.find(userObj => userObj.user === selectedUser).percentage; - for (const date in data) { - const userData = data[date].modified_lines[username].modified; - xDataModifiedLines.push(date); - yDataModifiedLines.push(userData); - } - - return { xDataModifiedLines, yDataModifiedLines }; - }; + const usersTasksAssigned = getGaugeDataAssignedTasksPerUser(data,"total"); + const percentageTasksAssigned = usersTasksAssigned.find(userObj => userObj.user === selectedUser).percentage; + const usersTasksInProgress = getGaugeDataInProgressTasksPerUser(data,"total"); + const percentageTasksInProgress = usersTasksInProgress.find(userObj => userObj.user === selectedUser).percentage; + const usersTasksDone = getGaugeDataDoneTasksPerUser(data,"total"); + const percentageTasksDone = usersTasksDone.find(userObj => userObj.user === selectedUser).percentage; + const usersTasksStandard = getGaugeDataStandardStatusTasksPerUser(data,"total"); + const percentageTasksStandard = usersTasksStandard.find(userObj => userObj.user === selectedUser).percentage; + + //Historic + const { xDataCommits, yDataCommits } = transformCommitsDataForUser(filteredhistoricaData, selectedUser) + const { xDataModifiedLines, yDataModifiedLines } = transformModifiedLinesDataForUser(filteredhistoricaData, selectedUser) - const transformAssignedIssuesDataForUser = (data, username) => { - const xDataAssignedIssues = []; - const yDataAssignedIssues = []; - - for (const date in data) { - xDataAssignedIssues.push(date); - yDataAssignedIssues.push(data[date].issues?.assigned?.[username] || 0); - } - - return { xDataAssignedIssues, yDataAssignedIssues }; - }; const { xDataAssignedIssues, yDataAssignedIssues } = transformAssignedIssuesDataForUser(filteredhistoricaData, selectedUser); - const transformClosedIssuesDataForUser = (data, username) => { - const xDataClosedIssues = []; - const yDataClosedIssues = []; - - for (const date in data) { - xDataClosedIssues.push(date); - yDataClosedIssues.push(data[date].issues?.closed?.[username] || 0); - } - - return { xDataClosedIssues, yDataClosedIssues }; - }; - const { xDataClosedIssues, yDataClosedIssues } = transformClosedIssuesDataForUser(filteredhistoricaData, selectedUser); - - const transformCreatedPRsDataForUser = (data, username) => { - const xDataCreatedPRs = []; - const yDataCreatedPRs = []; - - for (const date in data) { - xDataCreatedPRs.push(date); - yDataCreatedPRs.push(data[date].pull_requests?.created?.[username] || 0); - } - - return { xDataCreatedPRs, yDataCreatedPRs }; - }; const { xDataCreatedPRs, yDataCreatedPRs } = transformCreatedPRsDataForUser(filteredhistoricaData, selectedUser); - const transformMergedPRsDataForUser = (data, username) => { - const xDataMergedPRs = []; - const yDataMergedPRs = []; - - for (const date in data) { - xDataMergedPRs.push(date); - yDataMergedPRs.push(data[date].pull_requests?.merged_per_member?.[username] || 0); - } - - return { xDataMergedPRs, yDataMergedPRs }; - }; const { xDataMergedPRs, yDataMergedPRs } = transformMergedPRsDataForUser(filteredhistoricaData, selectedUser); - const transformTasksAssignedDataForUser = (data, username) => { - const xDataTasksAssigned = []; - const yDataTasksAssigned = []; - - for (const date in data) { - xDataTasksAssigned.push(date); - const assigned = data[date]?.project?.metrics_by_iteration?.total?.assigned_per_member?.[username] || 0; - yDataTasksAssigned.push(assigned); - } - - return { xDataTasksAssigned, yDataTasksAssigned }; - }; const { xDataTasksAssigned, yDataTasksAssigned } = transformTasksAssignedDataForUser(filteredhistoricaData, selectedUser); - - const transformTasksToDoDataForUser = (data, username) => { - const xDataTasksToDo = []; - const yDataTasksToDo = []; - - for (const date in data) { - xDataTasksToDo.push(date); - yDataTasksToDo.push(data[date].project?.metrics_by_iteration?.total?.todo_per_member?.[username] || 0); - } - - return { xDataTasksToDo, yDataTasksToDo }; - }; - const { xDataTasksToDo, yDataTasksToDo } = transformTasksToDoDataForUser(filteredhistoricaData, selectedUser); - - const transformTasksInProgressDataForUser = (data, username) => { - const xDataTasksInProgress = []; - const yDataTasksInProgress = []; - - for (const date in data) { - xDataTasksInProgress.push(date); - yDataTasksInProgress.push(data[date].project?.metrics_by_iteration?.total?.in_progress_per_member?.[username] || 0); - } - - return { xDataTasksInProgress, yDataTasksInProgress }; - }; const { xDataTasksInProgress, yDataTasksInProgress } = transformTasksInProgressDataForUser(filteredhistoricaData, selectedUser); - const transformTasksDoneDataForUser = (data, username) => { - const xDataTasksDone = []; - const yDataTasksDone = []; - - for (const date in data) { - xDataTasksDone.push(date); - const done = data[date]?.project?.metrics_by_iteration?.total?.done_per_member?.[username] || 0; - yDataTasksDone.push(done); - } - - return { xDataTasksDone, yDataTasksDone }; - }; - const { xDataTasksDone, yDataTasksDone } = transformTasksDoneDataForUser(filteredhistoricaData, selectedUser); - - const transformTasksStandardDataForUser = (data, username) => { - const xDataTasksStandard = []; - const yDataTasksStandard = []; - - for (const date in data) { - xDataTasksStandard.push(date); - const todo = data[date]?.project?.metrics_by_iteration?.total?.todo_per_member?.[username] || 0; - const inProgress = data[date]?.project?.metrics_by_iteration?.total?.in_progress_per_member?.[username] || 0; - const done = data[date]?.project?.metrics_by_iteration?.total?.done_per_member?.[username] || 0; - yDataTasksStandard.push(todo + inProgress + done); - } - - return { xDataTasksStandard, yDataTasksStandard }; - }; const { xDataTasksStandard, yDataTasksStandard } = transformTasksStandardDataForUser(filteredhistoricaData, selectedUser); diff --git a/src/pages/projectsPage.jsx b/src/pages/projectsPage.jsx index 6cb3dcb..ec0a173 100644 --- a/src/pages/projectsPage.jsx +++ b/src/pages/projectsPage.jsx @@ -27,7 +27,7 @@ import { import { filterHistoricData } from '../domain/utils'; import '../styles/commits.css'; -function Projects({ data,historicData,features }) { +function ProjectsPage({ data,historicData,features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalProject', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeProject', "7"); @@ -377,4 +377,4 @@ function Projects({ data,historicData,features }) { ); } -export default Projects; +export default ProjectsPage; diff --git a/src/tests/commits.test.jsx b/src/tests/commits.test.jsx index 924a678..3d2a7c4 100644 --- a/src/tests/commits.test.jsx +++ b/src/tests/commits.test.jsx @@ -7,6 +7,8 @@ import { getPieChartDataModifiedLines, getGaugeChartDataCommits, getGaugeChartDataModifiedLines, + transformCommitsDataForUser, + transformModifiedLinesDataForUser } from '../domain/commits'; describe('commits ', () => { @@ -138,4 +140,16 @@ describe('commits ', () => { { user: 'lluis', percentage: 300 / 400 }, ]); }); + + test('transformCommitsDataForUser returns correct data for given user', () => { + const { xDataCommits, yDataCommits } = transformCommitsDataForUser(mockHistoricData, 'pau'); + expect(xDataCommits).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataCommits).toEqual([3, 4]); + }); + + test('transformModifiedLinesDataForUser returns correct data for given user', () => { + const { xDataModifiedLines, yDataModifiedLines } = transformModifiedLinesDataForUser(mockHistoricData, 'lluis'); + expect(xDataModifiedLines).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataModifiedLines).toEqual([200, 250]); + }); }); diff --git a/src/tests/issues.test.jsx b/src/tests/issues.test.jsx index c125068..9c3b5cc 100644 --- a/src/tests/issues.test.jsx +++ b/src/tests/issues.test.jsx @@ -5,6 +5,8 @@ import { getGaugeDataAssignedIssuesPerUser, getGaugeDataClosedIssuesPerUser, getGaugeDataIssuesHavePR, + transformAssignedIssuesDataForUser, + transformClosedIssuesDataForUser } from '../domain/issues'; describe('issues', () => { @@ -12,11 +14,13 @@ describe('issues', () => { '2025-06-01': { issues: { assigned: { pau: 3, lluis: 5, non_assigned: 1 }, + closed: { pau: 2, lluis: 3, non_assigned: 1 }, }, }, '2025-06-02': { issues: { assigned: { pau: 4, lluis: 6, non_assigned: 2 }, + closed: { pau: 2, lluis: 3}, }, }, }; @@ -76,4 +80,15 @@ describe('issues', () => { test('getGaugeDataIssuesHavePR returns correct percentage of PRs', () => { expect(getGaugeDataIssuesHavePR(mockData)).toEqual(7 / 10); }); + test('transformAssignedIssuesDataForUser returns correct data for given user', () => { + const { xDataAssignedIssues, yDataAssignedIssues } = transformAssignedIssuesDataForUser(mockHistoricData, 'pau'); + expect(xDataAssignedIssues).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataAssignedIssues).toEqual([3, 4]); + }); + + test('transformClosedIssuesDataForUser returns correct data for given user', () => { + const { xDataClosedIssues, yDataClosedIssues } = transformClosedIssuesDataForUser(mockHistoricData, 'pau'); + expect(xDataClosedIssues).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataClosedIssues).toEqual([2, 2]); + }); }); diff --git a/src/tests/projects.test.jsx b/src/tests/projects.test.jsx index 263da4d..75ecd13 100644 --- a/src/tests/projects.test.jsx +++ b/src/tests/projects.test.jsx @@ -15,7 +15,12 @@ import { getGaugeDataAssignedTasksPerUser, getGaugeDataInProgressTasksPerUser, getGaugeDataDoneTasksPerUser, - getGaugeDataStandardStatusTasksPerUser + getGaugeDataStandardStatusTasksPerUser, + transformTasksAssignedDataForUser, + transformTasksToDoDataForUser, + transformTasksInProgressDataForUser, + transformTasksDoneDataForUser, + transformTasksStandardDataForUser, } from '../domain/projects'; describe('projects', () => { @@ -70,8 +75,41 @@ describe('projects', () => { total: 1 }, total: { + assigned_per_member: { + pau: 3, + lluis: 2, + non_assigned: 6, + }, + in_progress_per_member: { + pau: 1, + lluis: 0, + }, + done_per_member: { + pau: 2, + lluis: 1, + }, + todo_per_member: { + pau: 0, + lluis: 1, + }, + new_state_per_member: { + pau: 0, + lluis: 1, + }, + in_progress: 3, + done: 4, + todo: 3, + new_state: 1, + total_tasks: 12, + total_issues: 4, + total_issues_with_type: 3, total: 13, - } + total_features_todo: 1, + total_features_in_progress: 2, + total_features_done: 3, + total_features_new_state: 5, + total_bugs: 2, + total_features: 3 } } } }; @@ -182,4 +220,37 @@ test('getActiveIteration returns correct iteration with mocked date', () => { expect(result.xDataFeature).toEqual(['2025-06-01', '2025-06-02']); expect(result.allSeries.some(s => s.label === 'Done')).toBe(true); }); + + test('transformTasksAssignedDataForUser returns correct assigned tasks data for pau', () => { + const { xDataTasksAssigned, yDataTasksAssigned } = transformTasksAssignedDataForUser(mockHistoricData, 'pau'); + expect(xDataTasksAssigned).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataTasksAssigned).toEqual([3, 3]); + }); + + test('transformTasksToDoDataForUser returns correct todo tasks data for lluis', () => { + const { xDataTasksToDo, yDataTasksToDo } = transformTasksToDoDataForUser(mockHistoricData, 'lluis'); + expect(xDataTasksToDo).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataTasksToDo).toEqual([1, 1]); + }); + + test('transformTasksInProgressDataForUser returns correct in-progress tasks data for pau', () => { + const { xDataTasksInProgress, yDataTasksInProgress } = transformTasksInProgressDataForUser(mockHistoricData, 'pau'); + expect(xDataTasksInProgress).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataTasksInProgress).toEqual([1, 1]); + }); + + test('transformTasksDoneDataForUser returns correct done tasks data for lluis', () => { + const { xDataTasksDone, yDataTasksDone } = transformTasksDoneDataForUser(mockHistoricData, 'lluis'); + expect(xDataTasksDone).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataTasksDone).toEqual([1, 1]); + }); + + test('transformTasksStandardDataForUser returns sum todo + in_progress + done for pau', () => { + const { xDataTasksStandard, yDataTasksStandard } = transformTasksStandardDataForUser(mockHistoricData, 'pau'); + expect(xDataTasksStandard).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataTasksStandard).toEqual([ + 0 + 1 + 2, + 0 + 1 + 2, + ]); + }); }); diff --git a/src/tests/pullRequests.test.jsx b/src/tests/pullRequests.test.jsx index 30b6989..1f408b3 100644 --- a/src/tests/pullRequests.test.jsx +++ b/src/tests/pullRequests.test.jsx @@ -8,6 +8,8 @@ import { getGaugeChartDataMergesPRs, getGaugeDataCreatedPRsPerUser, getGaugeDataMergedPRsPerUser, + transformCreatedPRsDataForUser, + transformMergedPRsDataForUser } from '../domain/pullRequests'; describe('pullRequests', () => { @@ -88,7 +90,7 @@ describe('pullRequests', () => { test('getGaugeChartDataMergedPRs returns correct percentage', () => { const result = getGaugeChartDataMergedPRs(aggregatedData); - expect(result).toBe(5 / (10 - 2)); // 5 / 8 + expect(result).toBe(5 / (10 - 2)); }); test('getGaugeChartDataReviewedPRs returns correct percentage', () => { @@ -116,4 +118,15 @@ describe('pullRequests', () => { { user: 'lluis', percentage: 3 / 5 }, ]); }); + test('transformCreatedPRsDataForUser returns correct created PRs data for pau', () => { + const { xDataCreatedPRs, yDataCreatedPRs } = transformCreatedPRsDataForUser(mockPRsData, 'pau'); + expect(xDataCreatedPRs).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataCreatedPRs).toEqual([2, 1]); + }); + + test('transformMergedPRsDataForUser returns correct merged PRs data for lluis', () => { + const { xDataMergedPRs, yDataMergedPRs } = transformMergedPRsDataForUser(mockPRsData, 'lluis'); + expect(xDataMergedPRs).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataMergedPRs).toEqual([2, 1]); + }); }); diff --git a/src/tests/utils.test.jsx b/src/tests/utils.test.jsx index ee93174..19a240b 100644 --- a/src/tests/utils.test.jsx +++ b/src/tests/utils.test.jsx @@ -1,4 +1,4 @@ -import { filterHistoricData } from '../domain/utils'; +import { filterHistoricData,truncateName } from '../domain/utils'; describe('utils', () => { const mockHistoricData = { @@ -43,4 +43,21 @@ describe('utils', () => { vi.useRealTimers(); }); + + + test('truncateName works correctly with long strings', () => { + + const string = truncateName("thisShouldBeTruncated"); + expect(string).toEqual("thisShouldBeTrunca..."); + + }); + + test('truncateName works correctly with short strings', () => { + + const string = truncateName("Pau"); + expect(string).toEqual("Pau"); + + }); }); + + From a441df796eaa59f30f3fbae11cc08787e99dba11 Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 11 Jun 2025 07:41:37 +0200 Subject: [PATCH 15/20] Refactor index --- src/domain/commits.jsx | 26 ++++ src/domain/issues.jsx | 28 +++- src/domain/projects.jsx | 218 +++++++++++++++++++++------- src/domain/pullRequests.jsx | 31 +++- src/pages/commitsPage.jsx | 6 +- src/pages/index.jsx | 250 +++++++------------------------- src/pages/projectsPage.jsx | 2 +- src/tests/commits.test.jsx | 26 +++- src/tests/issues.test.jsx | 26 +++- src/tests/projects.test.jsx | 54 ++++++- src/tests/pullRequests.test.jsx | 41 +++++- 11 files changed, 438 insertions(+), 270 deletions(-) diff --git a/src/domain/commits.jsx b/src/domain/commits.jsx index 6cd9d83..439b922 100644 --- a/src/domain/commits.jsx +++ b/src/domain/commits.jsx @@ -1,3 +1,7 @@ +export const getGaugeDataAnonymous = (data) => { + return data.commits?.total > 0 ? ((data.commits?.total || 0) - (data.commits?.anonymous || 0)) / data.commits?.total : 0 +} + export const transformCommitsDataForLineChart = (data) => { const xData = []; const userSeries = {}; @@ -124,3 +128,25 @@ export const transformModifiedLinesDataForUser = (data, username) => { return { xDataModifiedLines, yDataModifiedLines }; }; + + export const transformDataForLineChartCommits = (data) => { + const xData = []; + const yData = []; + for (const date in data) { + xData.push(date); + yData.push(data[date].commits.total); + } + + return { xData, yData }; + }; + + export const transformDataForLineChartModifiedLines = (data) => { + const xDataModifedLines = []; + const yDataModifedLines = []; + for (const date in data) { + xDataModifedLines.push(date); + yDataModifedLines.push(data[date].modified_lines.total.modified); + } + + return { xDataModifedLines, yDataModifedLines }; + }; diff --git a/src/domain/issues.jsx b/src/domain/issues.jsx index 2456a40..dfa10eb 100644 --- a/src/domain/issues.jsx +++ b/src/domain/issues.jsx @@ -94,4 +94,30 @@ export const transformClosedIssuesDataForUser = (data, username) => { } return { xDataClosedIssues, yDataClosedIssues }; - }; \ No newline at end of file + }; + +export const transformIssuesDataForAreaChart = (data) => { + const xDataIssues = []; + const closedIssues = []; + const openIssues = []; + + for (const date in data) { + const total = data[date].issues.total || 0; + const closed = data[date].issues.total_closed || 0; + const open = total - closed; + xDataIssues.push(date); + closedIssues.push(closed); + openIssues.push(open); + } + return { xDataIssues, closedIssues, openIssues }; + }; + +export const getPieDataIssuesStatus = (data) => { + const issues = data.issues + const pieDataIssuesStatus = [ + ["Open",issues.total - issues.total_closed], + ["Closed",issues.total_closed] + ] + const pieDataIssuesStatusColor = ["red","green"]; + return {pieDataIssuesStatus,pieDataIssuesStatusColor}; +} \ No newline at end of file diff --git a/src/domain/projects.jsx b/src/domain/projects.jsx index 0cf3bb3..db0d266 100644 --- a/src/domain/projects.jsx +++ b/src/domain/projects.jsx @@ -88,74 +88,100 @@ export const transformAssignedTasksDataForLineChart = (data,dataHistoric, select return { xDataAssigned: allDates, seriesDataAssigned }; }; -export const transformFeatureDataForAreaChart = (data) => { - const xDataFeature = []; - const knownKeys = { - total_features_done: 'Done', - total_features_in_progress: 'In Progress', - total_features_todo: 'To Do', - }; +export const transformFeatureDataForAreaChart = (dataHistoric, selectedIteration, iterations) => { + if (!dataHistoric) return { xDataFeature: [], allSeries: [] }; - const baseFeatureData = { - 'Done': [], - 'In Progress': [], - 'To Do': [], - }; + let allDates = []; - const otherKeysData = {}; - const allOtherKeys = new Set(); + if (selectedIteration === "total") { + allDates = Object.keys(dataHistoric).sort((a, b) => new Date(a) - new Date(b)); + } else { + const iteration = iterations[selectedIteration]; + if (!iteration) return { xDataFeature: [], allSeries: [] }; - for (const date in data) { - const metrics = data[date]?.project?.metrics_by_iteration?.total; - if (!metrics) continue; + const startDate = new Date(iteration.startDate); + const endDate = new Date(iteration.endDate); - for (const [key, value] of Object.entries(metrics)) { - if ( - !Object.keys(knownKeys).includes(key) && - typeof value === 'number' && - key.startsWith('total_features_') && - Number.isInteger(value) - ) { - allOtherKeys.add(key); - } - } + let currentDate = new Date(startDate); + while (currentDate <= endDate) { + const dateStr = currentDate.toISOString().split('T')[0]; + allDates.push(dateStr); + currentDate.setDate(currentDate.getDate() + 1); } + } - for (const key of allOtherKeys) { - otherKeysData[key] = []; - } + const knownKeys = { + total_features_done: 'Done', + total_features_in_progress: 'In Progress', + total_features_todo: 'To Do', + }; - for (const date in data) { - const metrics = data[date]?.project?.metrics_by_iteration?.total || {}; + const baseFeatureData = { + 'Done': [], + 'In Progress': [], + 'To Do': [], + }; - xDataFeature.push(date); + const otherKeysData = {}; + const allOtherKeys = new Set(); - for (const [key, label] of Object.entries(knownKeys)) { - baseFeatureData[label].push(metrics[key] || 0); + // Identificar claves adicionales desde los datos totales (ya que puede no haber por iteración) + for (const date of allDates) { + const metrics = dataHistoric[date]?.project?.metrics_by_iteration?.total; + if (!metrics) continue; + + for (const [key, value] of Object.entries(metrics)) { + if ( + !Object.keys(knownKeys).includes(key) && + typeof value === 'number' && + key.startsWith('total_features_') && + Number.isInteger(value) + ) { + allOtherKeys.add(key); } + } + } - for (const key of allOtherKeys) { - const value = metrics[key]; - otherKeysData[key].push(Number.isInteger(value) ? value : 0); - } + for (const key of allOtherKeys) { + otherKeysData[key] = []; + } + + const xDataFeature = []; + + for (const date of allDates) { + const metrics = + selectedIteration === "total" + ? dataHistoric[date]?.project?.metrics_by_iteration?.total + : dataHistoric[date]?.project?.metrics_by_iteration?.[selectedIteration] + + xDataFeature.push(date); + for (const [key, label] of Object.entries(knownKeys)) { + baseFeatureData[label].push(metrics?.[key] || 0); } - const baseSeries = [ - { label: 'Done', data: baseFeatureData['Done'], color: 'rgb(0, 255, 0)' }, - { label: 'In Progress', data: baseFeatureData['In Progress'], color: 'orange' }, - { label: 'To Do', data: baseFeatureData['To Do'], color: 'rgb(255, 0, 0)' } - ]; - - const otherSeries = Object.entries(otherKeysData).map(([key, data]) => ({ - label: key - .replace('total_features_', '') - .replace(/_/g, ' ') - .replace(/\b\w/g, c => c.toUpperCase()), - data - })); - const allSeries = [...baseSeries, ...otherSeries]; - return {xDataFeature, allSeries }; - }; + for (const key of allOtherKeys) { + const value = metrics?.[key]; + otherKeysData[key].push(Number.isInteger(value) ? value : 0); + } + } + + const baseSeries = [ + { label: 'Done', data: baseFeatureData['Done'], color: 'rgb(0, 255, 0)' }, + { label: 'In Progress', data: baseFeatureData['In Progress'], color: 'orange' }, + { label: 'To Do', data: baseFeatureData['To Do'], color: 'rgb(255, 0, 0)' } + ]; + + const otherSeries = Object.entries(otherKeysData).map(([key, data]) => ({ + label: key + .replace('total_features_', '') + .replace(/_/g, ' ') + .replace(/\b\w/g, c => c.toUpperCase()), + data + })); + + const allSeries = [...baseSeries, ...otherSeries]; + return { xDataFeature, allSeries }; +}; const formatDate = (dateStr) => { const d = new Date(dateStr); @@ -394,3 +420,85 @@ export const transformTasksStandardDataForUser = (data, username) => { return { xDataTasksStandard, yDataTasksStandard }; }; + +export const transformTaskDataForAreaChart = (data) => { + const xDataTask = []; + const doneTask = []; + const inProgressTask = []; + const toDoTask = [] + const otherKeysData = {} + const allOtherKeys = new Set(); + + for (const date in data) { + const metrics = data[date].project.metrics_by_iteration.total; + for (const [key, value] of Object.entries(metrics)) { + if ( + !['done', 'in_progress', 'todo', 'total'].includes(key) && + !key.startsWith('total_') && + !key.startsWith('total') && + typeof value === 'number' && + Number.isInteger(value) + ) { + allOtherKeys.add(key); + } + } + } + + for (const key of allOtherKeys) { + otherKeysData[key] = []; + } + for (const date in data) { + const metrics = data[date].project.metrics_by_iteration.total; + const done = metrics.done || 0; + const todo = metrics.todo || 0; + const inProgress = metrics.in_progress || 0; + + xDataTask.push(date); + doneTask.push(done); + inProgressTask.push(inProgress); + toDoTask.push(todo) + for (const key of allOtherKeys) { + const value = metrics[key]; + otherKeysData[key].push(Number.isInteger(value) ? value : 0); + } + } + const baseSeries = [ + { label: 'Done', data: doneTask, color: "rgb(0, 255, 0)" }, + { label: 'In Progress', data: inProgressTask, color: 'orange' }, + { label: 'To Do', data: toDoTask, color: "rgb(255, 0, 0)" } + ]; + + const otherSeries = Object.entries(otherKeysData).map(([key, data]) => ({ + label: key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()), + data, + })); + const allSeries = [...baseSeries, ...otherSeries]; + + return { xDataTask, allSeries }; + }; + +export const getPieDataTasksStatus = (data) => { + const taskData = data.project.metrics_by_iteration.total; + const totalInProgress = taskData.in_progress; + const totalDone = taskData.done; + const totalToDo = taskData.todo; + const pieDataTasksStatus = [ + ["Todo",totalToDo], + ["In Progress",totalInProgress], + ["Done", totalDone] + ] + for (const [key, value] of Object.entries(taskData)) { + if ( + !["todo", "in_progress", "done", "total"].includes(key) && + !key.startsWith("total_") && + !key.startsWith("total") && + typeof value === "number" && + Number.isInteger(value) + ) { + const label = key.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()); + pieDataTasksStatus.push([label, value]); + } + } + const pieDataTasksStatusColor = ["red", "orange","green"]; + return {pieDataTasksStatus,pieDataTasksStatusColor}; +} \ No newline at end of file diff --git a/src/domain/pullRequests.jsx b/src/domain/pullRequests.jsx index f096074..2275f57 100644 --- a/src/domain/pullRequests.jsx +++ b/src/domain/pullRequests.jsx @@ -128,4 +128,33 @@ export const transformMergedPRsDataForUser = (data, username) => { } return { xDataMergedPRs, yDataMergedPRs }; - }; \ No newline at end of file + }; + +export const transformPRDataForAreaChart = (data) => { + const xDataPRs = []; + const mergedPRs = []; + const openPRs = []; + + for (const date in data) { + const total = data[date].pull_requests.total || 0; + const merged = data[date].pull_requests.merged || 0; + const closed = data[date].pull_requests.closed || 0; + const open = total - merged - closed; + xDataPRs.push(date); + mergedPRs.push(merged); + openPRs.push(open); + } + return { xDataPRs, mergedPRs, openPRs }; + }; + +export const getPieDataPullRequestStatus = (data) => { + const pullRequests = data.pull_requests; + const open = pullRequests.total - pullRequests.merged - pullRequests.closed + const pieDataPullRequestStatus = [ + ["Open", open], + ["Closed", pullRequests.closed], + ["Merged", pullRequests.merged], + ]; + const pieDataPullRequestStatusColor = ["red", "orange","green"]; + return {pieDataPullRequestStatus,pieDataPullRequestStatusColor}; +} \ No newline at end of file diff --git a/src/pages/commitsPage.jsx b/src/pages/commitsPage.jsx index d64d986..e4188c7 100644 --- a/src/pages/commitsPage.jsx +++ b/src/pages/commitsPage.jsx @@ -27,6 +27,9 @@ function CommitsPage({ data, historicData, features }) { const { xData: xDataCommits, seriesData: seriesDataCommits } = transformCommitsDataForLineChart(filteredHistoricData || {}); const { xData: xDataModified, seriesData: seriesDataModified } = transformModifiedLinesDataForLineChart(filteredHistoricData || {}); + const totalCommits = data.commits?.total || 0; + const gaugeDataAnonymous = totalCommits > 0 ? ((data.commits?.total || 0) - (data.commits?.anonymous || 0)) / totalCommits : 0 + const dataPieChartCommits = getPieChartDataCommits(data); const dataPieChartModifiedLines = getPieChartDataModifiedLines(data); const commitsGaugeData = getGaugeChartDataCommits(data); @@ -34,7 +37,6 @@ function CommitsPage({ data, historicData, features }) { const radarChartCommits = GetRadarDataCommits(data); const radarChartModifiedLines = GetRadarDataModifiedLines(data); - const totalCommits = data.commits?.total || 0; const totalPeople = Object.keys(data.avatars || {}).length || 1; return ( @@ -86,7 +88,7 @@ function CommitsPage({ data, historicData, features }) { 0 ? (totalCommits - (data.commits?.anonymous || 0)) / totalCommits : 0} + percentage={gaugeDataAnonymous} totalPeople={1} />
diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 909580e..1493e65 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -7,235 +7,93 @@ import AreaChart from '../components/areaChart'; import GaugeChart from '../components/gaugeChart'; import AreaChartMultiple from '../components/areaChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; +import {filterHistoricData } from '../domain/utils'; +import { + getGaugeDataAnonymous, + transformDataForLineChartCommits, + transformDataForLineChartModifiedLines +} from '../domain/commits'; +import { + getGaugeDataIssuesAssigned, + getGaugeDataIssuesHavePR, + transformIssuesDataForAreaChart, + getPieDataIssuesStatus +} from '../domain/issues'; +import { + getGaugeChartDataMergedPRs, + getGaugeChartDataReviewedPRs, + getGaugeChartDataMergesPRs, + transformPRDataForAreaChart, + getPieDataPullRequestStatus +} from '../domain/pullRequests'; +import { + getGaugeDataTasksAssigned, + getGaugeDataTasksStandardStatus, + getGaugeDataItemsIssues, + getGaugeDataItemIssuesWithType, + getGaugeDataItemIssuesWithIteration, + transformTaskDataForAreaChart, + getPieDataTasksStatus +} from '../domain/projects'; + function Index({data,historicData,features}) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalIndex', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeIndex', "7"); - const pullRequests = data.pull_requests; - const open = pullRequests.total - pullRequests.merged - pullRequests.closed - const issues = data.issues - const datapullRequests = [ - ["Open", open], - ["Closed", pullRequests.closed], - ["Merged", pullRequests.merged], - ]; - const dataissues = [ - ["Open",issues.total - issues.total_closed], - ["Closed",issues.total_closed] - ] - const filterHistoricData = (data, days) => { - if (days === "lifetime") return data; - - const today = new Date(); - const cutoff = new Date(today); - cutoff.setDate(today.getDate() - parseInt(days)); - const cutoffDateString = cutoff.toISOString().split("T")[0]; - const filtered = {}; - for (const date in data) { - if (date >= cutoffDateString) { - filtered[date] = data[date]; - } - } - - return filtered; - }; const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; - const taskData = data.project.metrics_by_iteration.total; - const totalTasks = taskData.total; - const totalInProgress = taskData.in_progress; - const totalDone = taskData.done; - const totalToDo = taskData.todo; - const dataTasks = [ - ["Todo",totalToDo], - ["In Progress",totalInProgress], - ["Done", totalDone] - ] - - for (const [key, value] of Object.entries(taskData)) { - if ( - !["todo", "in_progress", "done", "total"].includes(key) && - !key.startsWith("total_") && - !key.startsWith("total") && - typeof value === "number" && - Number.isInteger(value) - ) { - const label = key.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()); - dataTasks.push([label, value]); - } - } - const colorsPR = ["red", "orange","green"]; - const colorsIssues = ["red","green"]; - const colorsProject = ["red", "orange","green"]; let radarData = {}; radarData['Non-Anonymous Commits'] = - data.commits.total > 0 ? (data.commits.total - data.commits.anonymous) / data.commits.total : 0; + getGaugeDataAnonymous(data) if (features.includes('issues')) { radarData['Issues Assigned'] = - data.issues.total > 0 ? (data.issues.total - data.issues.assigned.non_assigned) / data.issues.total : 0; + getGaugeDataIssuesAssigned(data); radarData['Issues associated PR'] = - data.issues.total_closed > 0 ? data.issues.have_pull_request / data.issues.total_closed : 0; + getGaugeDataIssuesHavePR(data); } if (features.includes('pull-requests')) { radarData['Pull Requests Merged'] = - (data.pull_requests.total - data.pull_requests.closed) > 0 ? data.pull_requests.merged / (data.pull_requests.total - data.pull_requests.closed) : 0; + getGaugeChartDataMergedPRs(data); radarData['Pull Requests Reviewed'] = - data.pull_requests.merged > 0 ? data.pull_requests.not_merged_by_author / data.pull_requests.merged : 0; + getGaugeChartDataReviewedPRs(data); radarData['Pull Requests Merges'] = - data.commit_merges > 0 ? data.pull_requests.merged / data.commit_merges : 0; + getGaugeChartDataMergesPRs(data); } if (features.includes('projects')) { - const taskData = data.project.metrics_by_iteration.total; - const { non_assigned, ...assignedPerMember } = taskData.assigned_per_member; - const totalAssigned = Object.values(assignedPerMember).reduce((sum, current) => sum + current, 0); - const totalTasks = taskData.total_tasks; radarData['Tasks Assigned'] = - totalTasks > 0 ? totalAssigned / totalTasks : 0 + getGaugeDataTasksAssigned(data,"total") - const todo = taskData.todo - const inProgress = taskData.in_progress - const done = taskData.done - const standard = todo + inProgress + done; radarData['Tasks With Standard Status'] = - totalTasks > 0 ? standard / totalTasks : 0 - - const totalDraftIssues = taskData.total - const totalIssues = taskData.total_issues + getGaugeDataTasksStandardStatus(data,"total") radarData['Items that are Issues'] = - totalDraftIssues > 0 ? totalIssues / totalDraftIssues : 0 - const IssuesWithType = taskData.total_issues_with_type + getGaugeDataItemsIssues(data,"total") radarData['Projects Issues with type'] = - totalIssues > 0 ? IssuesWithType / totalIssues : 0 + getGaugeDataItemIssuesWithType(data,"total") if(data.project.has_iterations) { - const noIterationDraftIssues = data.project.metrics_by_iteration.no_iteration.total - const iterationDraftIssues = totalDraftIssues - noIterationDraftIssues radarData['Items with iteration'] = - totalDraftIssues > 0 ? iterationDraftIssues / totalDraftIssues : 0 + getGaugeDataItemIssuesWithIteration(data,"total") } } - const transformDataForLineChart = (data) => { - const xData = []; - const yData = []; - for (const date in data) { - xData.push(date); - yData.push(data[date].commits.total); - } - - return { xData, yData }; - }; - const { xData, yData } = transformDataForLineChart(filteredhistoricaData); + const {pieDataPullRequestStatus,pieDataPullRequestStatusColor} = getPieDataPullRequestStatus(data) + const {pieDataIssuesStatus,pieDataIssuesStatusColor} = getPieDataIssuesStatus(data) + const {pieDataTasksStatus,pieDataTasksStatusColor} = getPieDataTasksStatus(data) + + const { xData, yData } = transformDataForLineChartCommits(filteredhistoricaData); - const transformDataForLineChartModifiedLines = (data) => { - const xDataModifedLines = []; - const yDataModifedLines = []; - for (const date in data) { - xDataModifedLines.push(date); - yDataModifedLines.push(data[date].modified_lines.total.modified); - } - - return { xDataModifedLines, yDataModifedLines }; - }; const { xDataModifedLines, yDataModifedLines } = transformDataForLineChartModifiedLines(filteredhistoricaData); - const transformIssuesDataForAreaChart = (data) => { - const xDataIssues = []; - const closedIssues = []; - const openIssues = []; - - for (const date in data) { - const total = data[date].issues.total || 0; - const closed = data[date].issues.total_closed || 0; - const open = total - closed; - xDataIssues.push(date); - closedIssues.push(closed); - openIssues.push(open); - } - return { xDataIssues, closedIssues, openIssues }; - }; const { xDataIssues, closedIssues, openIssues } = transformIssuesDataForAreaChart(filteredhistoricaData); - const transformPRDataForAreaChart = (data) => { - const xDataPRs = []; - const mergedPRs = []; - const openPRs = []; - - for (const date in data) { - const total = data[date].pull_requests.total || 0; - const merged = data[date].pull_requests.merged || 0; - const closed = data[date].pull_requests.closed || 0; - const open = total - merged - closed; - xDataPRs.push(date); - mergedPRs.push(merged); - openPRs.push(open); - } - return { xDataPRs, mergedPRs, openPRs }; - }; + const { xDataPRs, mergedPRs, openPRs } = transformPRDataForAreaChart(filteredhistoricaData); - const transformTaskDataForAreaChart = (data) => { - const xDataTask = []; - const doneTask = []; - const inProgressTask = []; - const toDoTask = [] - const otherKeysData = {} - const allOtherKeys = new Set(); - - for (const date in data) { - const metrics = data[date].project.metrics_by_iteration.total; - for (const [key, value] of Object.entries(metrics)) { - if ( - !['done', 'in_progress', 'todo', 'total'].includes(key) && - !key.startsWith('total_') && - !key.startsWith('total') && - typeof value === 'number' && - Number.isInteger(value) - ) { - allOtherKeys.add(key); - } - } - } - - for (const key of allOtherKeys) { - otherKeysData[key] = []; - } - for (const date in data) { - const metrics = data[date].project.metrics_by_iteration.total; - const done = metrics.done || 0; - const todo = metrics.todo || 0; - const inProgress = metrics.in_progress || 0; - - xDataTask.push(date); - doneTask.push(done); - inProgressTask.push(inProgress); - toDoTask.push(todo) - for (const key of allOtherKeys) { - const value = metrics[key]; - otherKeysData[key].push(Number.isInteger(value) ? value : 0); - } - } - return { xDataTask, doneTask, inProgressTask,toDoTask,otherKeysData }; - }; - const { xDataTask, doneTask, inProgressTask,toDoTask,otherKeysData } = transformTaskDataForAreaChart(filteredhistoricaData); - - const baseSeries = [ - { label: 'Done', data: doneTask, color: "rgb(0, 255, 0)" }, - { label: 'In Progress', data: inProgressTask, color: 'orange' }, - { label: 'To Do', data: toDoTask, color: "rgb(255, 0, 0)" } - ]; - - const otherSeries = Object.entries(otherKeysData).map(([key, data]) => ({ - label: key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()), - data, - })); - - const allSeries = [...baseSeries, ...otherSeries]; - + const { xDataTask, allSeries } = transformTaskDataForAreaChart(filteredhistoricaData); return (
@@ -246,7 +104,7 @@ function Index({data,historicData,features}) {

Project Stats

-
Total Members: {Object.keys(data.commits).filter(user => user !== 'anonymous' && user !== 'total').length}
+
Total Members: {Object.keys(data.avatars).length}
Total Commits: {data.commits.total}
{features.includes("issues") && (
Total Issues: {data.issues.total}
) } @@ -312,8 +170,8 @@ function Index({data,historicData,features}) {
)} @@ -321,8 +179,8 @@ function Index({data,historicData,features}) {
)} @@ -330,8 +188,8 @@ function Index({data,historicData,features}) {
)} diff --git a/src/pages/projectsPage.jsx b/src/pages/projectsPage.jsx index ec0a173..d8c4aff 100644 --- a/src/pages/projectsPage.jsx +++ b/src/pages/projectsPage.jsx @@ -55,7 +55,7 @@ function ProjectsPage({ data,historicData,features }) { data.project.iterations ); - const { xDataFeature,allSeries } = transformFeatureDataForAreaChart(filteredhistoricaData); + const { xDataFeature,allSeries } = transformFeatureDataForAreaChart(filteredhistoricaData,selectedIteration,data.project.iterations); const {typePieChartData,typeColorsPieChart} = getIssueTypeDataForChart(data, selectedIteration); diff --git a/src/tests/commits.test.jsx b/src/tests/commits.test.jsx index 3d2a7c4..1c64a31 100644 --- a/src/tests/commits.test.jsx +++ b/src/tests/commits.test.jsx @@ -1,4 +1,5 @@ import { + getGaugeDataAnonymous, transformCommitsDataForLineChart, transformModifiedLinesDataForLineChart, GetRadarDataCommits, @@ -8,7 +9,9 @@ import { getGaugeChartDataCommits, getGaugeChartDataModifiedLines, transformCommitsDataForUser, - transformModifiedLinesDataForUser + transformModifiedLinesDataForUser, + transformDataForLineChartCommits, + transformDataForLineChartModifiedLines } from '../domain/commits'; describe('commits ', () => { @@ -152,4 +155,25 @@ describe('commits ', () => { expect(xDataModifiedLines).toEqual(['2025-06-01', '2025-06-02']); expect(yDataModifiedLines).toEqual([200, 250]); }); + + test('transformDataForLineChartCommits returns correct data', () => { + const { xData, yData } = transformDataForLineChartCommits(mockHistoricData); + expect(xData).toEqual(['2025-06-01', '2025-06-02']); + expect(yData).toEqual([8, 10]); + }); + + test('transformDataForLineChartModifiedLines returns correct data', () => { + const { xDataModifedLines, yDataModifedLines } = transformDataForLineChartModifiedLines(mockHistoricData); + expect(xDataModifedLines).toEqual(['2025-06-01', '2025-06-02']); + expect(yDataModifedLines).toEqual([300, 400]); + }); + test('getGaugeDataAnonymous returns correct ratio of non-anonymous to total', () => { + const input = { commits: { total: 10, anonymous: 2 } }; + expect(getGaugeDataAnonymous(input)).toBe(0.8); + }); + + test('getGaugeDataAnonymous returns 0 when total is 0', () => { + const input = { commits: { total: 0, anonymous: 0 } }; + expect(getGaugeDataAnonymous(input)).toBe(0); + }); }); diff --git a/src/tests/issues.test.jsx b/src/tests/issues.test.jsx index 9c3b5cc..3e95462 100644 --- a/src/tests/issues.test.jsx +++ b/src/tests/issues.test.jsx @@ -6,7 +6,9 @@ import { getGaugeDataClosedIssuesPerUser, getGaugeDataIssuesHavePR, transformAssignedIssuesDataForUser, - transformClosedIssuesDataForUser + transformClosedIssuesDataForUser, + transformIssuesDataForAreaChart, + getPieDataIssuesStatus } from '../domain/issues'; describe('issues', () => { @@ -15,12 +17,17 @@ describe('issues', () => { issues: { assigned: { pau: 3, lluis: 5, non_assigned: 1 }, closed: { pau: 2, lluis: 3, non_assigned: 1 }, + total_closed: 5, + total: 9 }, }, '2025-06-02': { issues: { assigned: { pau: 4, lluis: 6, non_assigned: 2 }, closed: { pau: 2, lluis: 3}, + total_closed: 5, + total: 12 + }, }, }; @@ -91,4 +98,21 @@ describe('issues', () => { expect(xDataClosedIssues).toEqual(['2025-06-01', '2025-06-02']); expect(yDataClosedIssues).toEqual([2, 2]); }); + + test('transformIssuesDataForAreaChart transforms correctly', () => { + const result = transformIssuesDataForAreaChart(mockHistoricData); + expect(result.xDataIssues).toEqual(['2025-06-01', '2025-06-02']); + expect(result.closedIssues).toEqual([5,5]); + expect(result.openIssues).toEqual([4,7]); + + }); + test('getPieDataIssuesStatus returns correct open and closed issues data and colors', () => { + expect(getPieDataIssuesStatus(mockData)).toEqual({ + pieDataIssuesStatus: [ + ['Open', 10], + ['Closed', 10], + ], + pieDataIssuesStatusColor: ['red', 'green'], + }); +}); }); diff --git a/src/tests/projects.test.jsx b/src/tests/projects.test.jsx index 75ecd13..338f3b7 100644 --- a/src/tests/projects.test.jsx +++ b/src/tests/projects.test.jsx @@ -21,6 +21,8 @@ import { transformTasksInProgressDataForUser, transformTasksDoneDataForUser, transformTasksStandardDataForUser, + transformTaskDataForAreaChart, + getPieDataTasksStatus } from '../domain/projects'; describe('projects', () => { @@ -109,7 +111,7 @@ describe('projects', () => { total_features_done: 3, total_features_new_state: 5, total_bugs: 2, - total_features: 3 } + total_features: 11 } } } }; @@ -214,11 +216,20 @@ test('getActiveIteration returns correct iteration with mocked date', () => { ]); expect(featureColorsPieChart).toEqual(['green', 'orange', 'red']); }); - - test('transformFeatureDataForAreaChart transforms correctly', () => { - const result = transformFeatureDataForAreaChart(mockHistoricData); + + test('transformFeatureDataForAreaChart transforms correctly with selectedIteration "total"', () => { + const result = transformFeatureDataForAreaChart(mockHistoricData, 'total', {}); expect(result.xDataFeature).toEqual(['2025-06-01', '2025-06-02']); - expect(result.allSeries.some(s => s.label === 'Done')).toBe(true); + + const doneSeries = result.allSeries.find(s => s.label === 'Done'); + const inProgressSeries = result.allSeries.find(s => s.label === 'In Progress'); + const toDoSeries = result.allSeries.find(s => s.label === 'To Do'); + const customSeries = result.allSeries.find(s => s.label === 'New State'); + + expect(doneSeries.data).toEqual([3, 3]); + expect(inProgressSeries.data).toEqual([2, 2]); + expect(toDoSeries.data).toEqual([1, 1]); + expect(customSeries.data).toEqual([5, 5]); }); test('transformTasksAssignedDataForUser returns correct assigned tasks data for pau', () => { @@ -253,4 +264,37 @@ test('getActiveIteration returns correct iteration with mocked date', () => { 0 + 1 + 2, ]); }); + + test('transformTaskDataForAreaChart estructura correcta con métricas adicionales', () => { + const result = transformTaskDataForAreaChart(mockHistoricData); + + expect(result.xDataTask).toEqual(['2025-06-01', '2025-06-02']); + + const doneSeries = result.allSeries.find(s => s.label === 'Done'); + const inProgressSeries = result.allSeries.find(s => s.label === 'In Progress'); + const toDoSeries = result.allSeries.find(s => s.label === 'To Do'); + const newStateSeries = result.allSeries.find(s => s.label === 'New State'); + + expect(doneSeries?.data).toEqual([4, 4]); + expect(inProgressSeries?.data).toEqual([3, 3]); + expect(toDoSeries?.data).toEqual([3, 3]); + expect(newStateSeries?.data).toEqual([1, 1]); + }); + + test('getPieDataTasksStatus genera datos correctos de pastel con métricas adicionales', () => { + const pie = getPieDataTasksStatus(mockData); + + expect(pie.pieDataTasksStatus).toEqual(expect.arrayContaining([ + ['Todo', 3], + ['In Progress', 3], + ['Done', 4], + ['New State', 1], + ])); + + expect(pie.pieDataTasksStatusColor).toEqual(expect.arrayContaining([ + 'red', + 'orange', + 'green', + ])); + }); }); diff --git a/src/tests/pullRequests.test.jsx b/src/tests/pullRequests.test.jsx index 1f408b3..deb8ce2 100644 --- a/src/tests/pullRequests.test.jsx +++ b/src/tests/pullRequests.test.jsx @@ -9,7 +9,9 @@ import { getGaugeDataCreatedPRsPerUser, getGaugeDataMergedPRsPerUser, transformCreatedPRsDataForUser, - transformMergedPRsDataForUser + transformMergedPRsDataForUser, + transformPRDataForAreaChart, + getPieDataPullRequestStatus } from '../domain/pullRequests'; describe('pullRequests', () => { @@ -24,18 +26,24 @@ describe('pullRequests', () => { pau: 1, lluis: 2, }, + total: 5, + merged: 3, + closed: 1, }, }, '2025-06-02': { pull_requests: { created: { - pau: 1, + pau: 3, lluis: 4, }, merged_per_member: { pau: 2, - lluis: 1, + lluis: 3, }, + total: 7, + merged: 5, + closed: 1, }, }, }; @@ -44,7 +52,7 @@ describe('pullRequests', () => { const { xDataCreated, seriesDataCreated } = transformCreatedPRsDataForLineChart(mockPRsData); expect(xDataCreated).toEqual(['2025-06-01', '2025-06-02']); expect(seriesDataCreated).toEqual([ - { name: 'pau', data: [2, 1] }, + { name: 'pau', data: [2, 3] }, { name: 'lluis', data: [3, 4] }, ]); }); @@ -54,7 +62,7 @@ describe('pullRequests', () => { expect(xDataMerged).toEqual(['2025-06-01', '2025-06-02']); expect(seriesDataMerged).toEqual([ { name: 'pau', data: [1, 2] }, - { name: 'lluis', data: [2, 1] }, + { name: 'lluis', data: [2, 3] }, ]); }); @@ -121,12 +129,31 @@ describe('pullRequests', () => { test('transformCreatedPRsDataForUser returns correct created PRs data for pau', () => { const { xDataCreatedPRs, yDataCreatedPRs } = transformCreatedPRsDataForUser(mockPRsData, 'pau'); expect(xDataCreatedPRs).toEqual(['2025-06-01', '2025-06-02']); - expect(yDataCreatedPRs).toEqual([2, 1]); + expect(yDataCreatedPRs).toEqual([2, 3]); }); test('transformMergedPRsDataForUser returns correct merged PRs data for lluis', () => { const { xDataMergedPRs, yDataMergedPRs } = transformMergedPRsDataForUser(mockPRsData, 'lluis'); expect(xDataMergedPRs).toEqual(['2025-06-01', '2025-06-02']); - expect(yDataMergedPRs).toEqual([2, 1]); + expect(yDataMergedPRs).toEqual([2, 3]); }); + + test('transformPRDataForAreaChart works as expected', () => { + const { xDataPRs, mergedPRs, openPRs } = transformPRDataForAreaChart(mockPRsData); + + expect(xDataPRs).toEqual(['2025-06-01', '2025-06-02']); + expect(mergedPRs).toEqual([3, 5]); + expect(openPRs).toEqual([1, 1]); +}); + +test('getPieDataPullRequestStatus returns correct pie data and colors', () => { + const { pieDataPullRequestStatus, pieDataPullRequestStatusColor } = getPieDataPullRequestStatus(aggregatedData); + + expect(pieDataPullRequestStatus).toEqual([ + ['Open', aggregatedData.pull_requests.total - aggregatedData.pull_requests.merged - aggregatedData.pull_requests.closed], + ['Closed', aggregatedData.pull_requests.closed], + ['Merged', aggregatedData.pull_requests.merged], + ]); + expect(pieDataPullRequestStatusColor).toEqual(['red', 'orange', 'green']); +}); }); From dcef9ff24706be7cdacb72e2e100eca5161c00ab Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 11 Jun 2025 11:41:38 +0200 Subject: [PATCH 16/20] Fet consistents els noms d'algunes estadistiques --- src/pages/index.jsx | 8 ++++---- src/pages/individual.jsx | 14 +++++++------- src/pages/projectsPage.jsx | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 1493e65..c22b6ac 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -106,14 +106,14 @@ function Index({data,historicData,features}) {
Total Members: {Object.keys(data.avatars).length}
Total Commits: {data.commits.total}
- {features.includes("issues") && ( -
Total Issues: {data.issues.total}
) } - {features.includes("pull-requests") && ( -
Total Pull Requests: {data.pull_requests.total}
)}
Total Additions: {data.modified_lines.total.additions}
Total Deletions: {data.modified_lines.total.deletions}
Total Modifications: {data.modified_lines.total.modified}
Total Lines of code: {data.modified_lines.total.additions - data.modified_lines.total.deletions}
+ {features.includes("issues") && ( +
Total Issues: {data.issues.total}
) } + {features.includes("pull-requests") && ( +
Total Pull Requests: {data.pull_requests.total}
)} {features.includes("projects") && (
Total Tasks: {data.project.metrics_by_iteration.total.total_tasks}
)} {features.includes("projects") && ( diff --git a/src/pages/individual.jsx b/src/pages/individual.jsx index 08515f1..db7f513 100644 --- a/src/pages/individual.jsx +++ b/src/pages/individual.jsx @@ -133,19 +133,19 @@ function Individual({ data, historicData, features }) { {features.includes("issues") && (
Issues Closed: {issuesClosed}
)} {features.includes("pull-requests") && ( -
Pull requests Created: {pullRequestsCreated}
)} +
Pull Requests Created: {pullRequestsCreated}
)} {features.includes("pull-requests") && ( -
Pull requests merged: {pullRequestsMerged}
)} +
Pull Requests merged: {pullRequestsMerged}
)} {features.includes("projects") && (
Tasks assigned: {tasksAssigned}
)} {features.includes("projects") && ( -
Tasks todo: {tasksTodo}
)} +
Tasks ToDo: {tasksTodo}
)} {features.includes("projects") && ( -
Tasks in progress: {tasksInProgress}
)} +
Tasks In Progress: {tasksInProgress}
)} {features.includes("projects") && ( -
Tasks done: {tasksClosed}
)} +
Tasks Done: {tasksClosed}
)} {features.includes("projects") && ( -
Tasks with Standard Status: {tasksStandard}
)} +
Tasks with a standard status: {tasksStandard}
)}
@@ -326,7 +326,7 @@ function Individual({ data, historicData, features }) { {features.includes("projects") && (

- Tasks with Standard Status + Tasks with standard status Percentage of tasks with standard status (ToDo, In Progress, Done) of the user relative to the user assigned tasks diff --git a/src/pages/projectsPage.jsx b/src/pages/projectsPage.jsx index d8c4aff..5225689 100644 --- a/src/pages/projectsPage.jsx +++ b/src/pages/projectsPage.jsx @@ -190,7 +190,7 @@ function ProjectsPage({ data,historicData,features }) {

- Tasks with Standard Status + Tasks with standard status Percentage of tasks that have a standard status (ToDo, In Progress, Done) to the total of tasks @@ -311,7 +311,7 @@ function ProjectsPage({ data,historicData,features }) {

- Tasks with Standard Status per user + Tasks with standard status per user From 41971c7dcbd6438623d82d4cd6a6a7d9a37563ea Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 18 Jun 2025 00:09:11 +0200 Subject: [PATCH 17/20] Petits ajustos --- src/components/dateRangeSelector.jsx | 22 ++++++++ src/components/historicalToggle.jsx | 22 ++++++++ src/domain/pullRequests.jsx | 40 ++++++++------ src/pages/commitsPage.jsx | 53 ++++++++++--------- src/pages/index.jsx | 78 ++++++++++------------------ src/pages/individual.jsx | 45 +++++----------- src/pages/issuesPage.jsx | 42 ++++----------- src/pages/projectsPage.jsx | 70 ++++++------------------- 8 files changed, 161 insertions(+), 211 deletions(-) create mode 100644 src/components/dateRangeSelector.jsx create mode 100644 src/components/historicalToggle.jsx diff --git a/src/components/dateRangeSelector.jsx b/src/components/dateRangeSelector.jsx new file mode 100644 index 0000000..60432b1 --- /dev/null +++ b/src/components/dateRangeSelector.jsx @@ -0,0 +1,22 @@ +const DateRangeSelector = ({ showHistorical, dateRange, setDateRange }) => { + if (!showHistorical) return null; + + return ( +
+ +
+ ); +}; + +export default DateRangeSelector; \ No newline at end of file diff --git a/src/components/historicalToggle.jsx b/src/components/historicalToggle.jsx new file mode 100644 index 0000000..7eabd43 --- /dev/null +++ b/src/components/historicalToggle.jsx @@ -0,0 +1,22 @@ +const HistoricalToggle = ({ showHistorical, setShowHistorical }) => { + return ( +
+
+ + +
+
+ ); +}; + +export default HistoricalToggle; \ No newline at end of file diff --git a/src/domain/pullRequests.jsx b/src/domain/pullRequests.jsx index 2275f57..17fb0e5 100644 --- a/src/domain/pullRequests.jsx +++ b/src/domain/pullRequests.jsx @@ -131,21 +131,31 @@ export const transformMergedPRsDataForUser = (data, username) => { }; export const transformPRDataForAreaChart = (data) => { - const xDataPRs = []; - const mergedPRs = []; - const openPRs = []; - - for (const date in data) { - const total = data[date].pull_requests.total || 0; - const merged = data[date].pull_requests.merged || 0; - const closed = data[date].pull_requests.closed || 0; - const open = total - merged - closed; - xDataPRs.push(date); - mergedPRs.push(merged); - openPRs.push(open); - } - return { xDataPRs, mergedPRs, openPRs }; - }; + const xDataPRs = []; + const mergedPRs = []; + const closedPRs = []; + const openPRs = []; + + for (const date in data) { + const total = data[date].pull_requests.total || 0; + const merged = data[date].pull_requests.merged || 0; + const closed = data[date].pull_requests.closed || 0; + const open = total - merged - closed; + + xDataPRs.push(date); + mergedPRs.push(merged); + closedPRs.push(closed); + openPRs.push(open); + } + + const areaPRData = [ + { label: 'Merged', data: mergedPRs, color:'rgb(0, 255, 0)' }, + { label: 'Closed', data: closedPRs, color: 'orange' }, + { label: 'Open', data: openPRs, color: 'rgb(255, 0, 0)' }, + ]; + + return { xDataPRs, areaPRData }; +}; export const getPieDataPullRequestStatus = (data) => { const pullRequests = data.pull_requests; diff --git a/src/pages/commitsPage.jsx b/src/pages/commitsPage.jsx index e4188c7..71d3017 100644 --- a/src/pages/commitsPage.jsx +++ b/src/pages/commitsPage.jsx @@ -3,9 +3,12 @@ import GaugeChart from '../components/gaugeChart.jsx'; import RadarPieToggle from '../components/radarPieToggle.jsx'; import LineChartMultiple from '../components/lineChartMultiple.jsx'; import usePersistentStateSession from '../components/usePersistentStateSession.jsx'; +import HistoricalToggle from '../components/historicalToggle.jsx'; +import DateRangeSelector from '../components/dateRangeSelector.jsx'; import '../styles/commits.css'; import { + getGaugeDataAnonymous, transformCommitsDataForLineChart, transformModifiedLinesDataForLineChart, getPieChartDataCommits, @@ -28,8 +31,8 @@ function CommitsPage({ data, historicData, features }) { const { xData: xDataModified, seriesData: seriesDataModified } = transformModifiedLinesDataForLineChart(filteredHistoricData || {}); const totalCommits = data.commits?.total || 0; - const gaugeDataAnonymous = totalCommits > 0 ? ((data.commits?.total || 0) - (data.commits?.anonymous || 0)) / totalCommits : 0 - + const gaugeDataAnonymous = getGaugeDataAnonymous(data) + const dataPieChartCommits = getPieChartDataCommits(data); const dataPieChartModifiedLines = getPieChartDataModifiedLines(data); const commitsGaugeData = getGaugeChartDataCommits(data); @@ -42,29 +45,17 @@ function CommitsPage({ data, historicData, features }) { return (

Commits

-
-
- - -
-
- {showHistorical && ( -
- -
- )} + + + {!showHistorical && ( <> @@ -141,9 +132,17 @@ function CommitsPage({ data, historicData, features }) {

) : ( -
- No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop s'hagi fet la primera execució del bot. +
+ No s'ha trobat historic_metrics.json.
+ Si és el primer dia, torna demà un cop + s'hagi fet la primera execució del + workflow Daily Metrics.
)} diff --git a/src/pages/index.jsx b/src/pages/index.jsx index c22b6ac..5ba3068 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -7,6 +7,8 @@ import AreaChart from '../components/areaChart'; import GaugeChart from '../components/gaugeChart'; import AreaChartMultiple from '../components/areaChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; +import HistoricalToggle from '../components/historicalToggle.jsx'; +import DateRangeSelector from '../components/dateRangeSelector.jsx'; import {filterHistoricData } from '../domain/utils'; import { getGaugeDataAnonymous, @@ -91,17 +93,18 @@ function Index({data,historicData,features}) { const { xDataIssues, closedIssues, openIssues } = transformIssuesDataForAreaChart(filteredhistoricaData); - const { xDataPRs, mergedPRs, openPRs } = transformPRDataForAreaChart(filteredhistoricaData); + const { xDataPRs, areaPRData } = transformPRDataForAreaChart(filteredhistoricaData); const { xDataTask, allSeries } = transformTaskDataForAreaChart(filteredhistoricaData); + const gaugeAnonymousData = getGaugeDataAnonymous(data) return (

General overview

-

Project Stats

+

Project Statistics

Total Members: {Object.keys(data.avatars).length}
@@ -122,41 +125,19 @@ function Index({data,historicData,features}) {
{(features.includes("issues") || features.includes("pull-requests") || features.includes("projects")) && ( -
-
- - -
-
)} + )} - {showHistorical && ( -
- -
- )} + {!showHistorical && ( - <> + <> {(features.includes("issues") || features.includes("pull-requests") || features.includes("projects")) ? (
@@ -187,7 +168,7 @@ function Index({data,historicData,features}) { {features.includes("projects") && (
@@ -200,7 +181,7 @@ function Index({data,historicData,features}) {
0 ? (data.commits.total - data.commits.anonymous) / data.commits.total : 0 } + percentage={gaugeAnonymousData } totalPeople={1} />
@@ -278,24 +259,19 @@ function Index({data,historicData,features}) { bottomLabel="Closed" xLabel="Date" yLabel="Issues" - title="Open and Closed Issues Over Time" + title="Issues State Over Time" />
)} {features.includes("pull-requests") && (
- -
)} + +
)} {features.includes("projects") && (
)}
diff --git a/src/pages/individual.jsx b/src/pages/individual.jsx index db7f513..5be2b72 100644 --- a/src/pages/individual.jsx +++ b/src/pages/individual.jsx @@ -4,6 +4,8 @@ import LineChart from '../components/lineChart'; import GaugeChart from '../components/gaugeChart'; import usePersistentStateSession from '../components/usePersistentStateSession'; import usePersistentState from '../components/usePersistentState'; +import HistoricalToggle from '../components/historicalToggle.jsx'; +import DateRangeSelector from '../components/dateRangeSelector.jsx'; import { transformCommitsDataForUser, getGaugeChartDataCommits, @@ -120,7 +122,8 @@ function Individual({ data, historicData, features }) { {users.map(user => ( ))} -
+ +
Commits: {commits}
Additions: {modifiedLines.additions}
@@ -150,38 +153,16 @@ function Individual({ data, historicData, features }) {
-
-
- - -
-
+ - {showHistorical && ( -
- -
- )} + {!showHistorical && ( <> diff --git a/src/pages/issuesPage.jsx b/src/pages/issuesPage.jsx index ed06568..c992ae0 100644 --- a/src/pages/issuesPage.jsx +++ b/src/pages/issuesPage.jsx @@ -3,6 +3,8 @@ import GaugeChart from '../components/gaugeChart'; import RadarPieToggle from '../components/radarPieToggle'; import LineChartMultiple from '../components/lineChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; +import HistoricalToggle from '../components/historicalToggle.jsx'; +import DateRangeSelector from '../components/dateRangeSelector.jsx'; import { transformAssignedIssuesDataForLineChart, getRadarAndPieDataIssuesAssigned, @@ -30,38 +32,16 @@ function IssuesPage({ data,historicData,features }) { return (

Issues

-
-
- - -
-
+ - {showHistorical && ( -
- -
- )} + {!showHistorical && ( <> diff --git a/src/pages/projectsPage.jsx b/src/pages/projectsPage.jsx index 5225689..f7a50f9 100644 --- a/src/pages/projectsPage.jsx +++ b/src/pages/projectsPage.jsx @@ -5,6 +5,8 @@ import PieChart from '../components/pieChart'; import LineChartMultiple from '../components/lineChartMultiple'; import AreaChartMultiple from '../components/areaChartMultiple'; import usePersistentStateSession from '../components/usePersistentStateSession'; +import HistoricalToggle from '../components/historicalToggle.jsx'; +import DateRangeSelector from '../components/dateRangeSelector.jsx'; import { getActiveIteration, filterHistoricDataByIteration, @@ -61,7 +63,6 @@ function ProjectsPage({ data,historicData,features }) { const {featurePieChartData,featureColorsPieChart} = getFeatureDataForChart(data, selectedIteration); - const percentageTasksAssigned = getGaugeDataTasksAssigned(data,selectedIteration) const percentageStandardStatus = getGaugeDataTasksStandardStatus(data,selectedIteration) const percentageItemsIssues = getGaugeDataItemsIssues(data,selectedIteration) @@ -75,25 +76,10 @@ function ProjectsPage({ data,historicData,features }) {

Projects

-
-
- - -
-
- - {showHistorical && ( - <> +
+ {!showHistorical && ( +
+ {getDateRangeForIteration(data,selectedIteration)} +
)}
{selectedIteration === "total" && ( -
- -
- )} - + )} - - {!showHistorical && ( <> -
- -
- {getDateRangeForIteration(data,selectedIteration)} -
-

Summary

From a6fa1adb15c12c4e7b3b7612e5703ac4ae1a9724 Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 18 Jun 2025 18:42:25 +0200 Subject: [PATCH 18/20] Refactor i canvis de nom --- .../commitsPageHistoricalSection.jsx | 37 ++ .../indexHistoricalSection.jsx | 62 +++ .../individualHistoricalSection.jsx | 167 +++++++ .../issuesPageHistoricalSection.jsx | 38 ++ .../projectsPageHistoricalSection.jsx | 55 +++ .../pullRequestsPageHistoricalSection.jsx | 48 ++ .../commitsPageMetricsSection.jsx | 43 ++ src/metricsSections/indexGraphicsSection.jsx | 59 +++ .../individualMetricsSection.jsx | 177 ++++++++ .../issuesPageMetricsSection.jsx | 53 +++ .../projectsPageMetricsSection.jsx | 98 +++++ .../pullRequestsPageMetricsSection.jsx | 52 +++ src/pages/commitsPage.jsx | 127 ++---- src/pages/index.jsx | 324 ++++---------- src/pages/individual.jsx | 414 ++++-------------- src/pages/issuesPage.jsx | 166 ++----- src/pages/projectsPage.jsx | 332 +++----------- src/pages/pullRequestsPage.jsx | 247 +++-------- src/styles/{commits.css => elements.css} | 0 .../commitsPageSummarySection.jsx | 49 +++ src/summarySections/indexStatsSection.jsx | 35 ++ .../individualStatisticsSection.jsx | 65 +++ .../issuesPageSummarySection.jsx | 62 +++ .../projectsPageSummarySection.jsx | 131 ++++++ .../pullRequestsPageSummarySection.jsx | 90 ++++ 25 files changed, 1662 insertions(+), 1269 deletions(-) create mode 100644 src/historicalSections/commitsPageHistoricalSection.jsx create mode 100644 src/historicalSections/indexHistoricalSection.jsx create mode 100644 src/historicalSections/individualHistoricalSection.jsx create mode 100644 src/historicalSections/issuesPageHistoricalSection.jsx create mode 100644 src/historicalSections/projectsPageHistoricalSection.jsx create mode 100644 src/historicalSections/pullRequestsPageHistoricalSection.jsx create mode 100644 src/metricsSections/commitsPageMetricsSection.jsx create mode 100644 src/metricsSections/indexGraphicsSection.jsx create mode 100644 src/metricsSections/individualMetricsSection.jsx create mode 100644 src/metricsSections/issuesPageMetricsSection.jsx create mode 100644 src/metricsSections/projectsPageMetricsSection.jsx create mode 100644 src/metricsSections/pullRequestsPageMetricsSection.jsx rename src/styles/{commits.css => elements.css} (100%) create mode 100644 src/summarySections/commitsPageSummarySection.jsx create mode 100644 src/summarySections/indexStatsSection.jsx create mode 100644 src/summarySections/individualStatisticsSection.jsx create mode 100644 src/summarySections/issuesPageSummarySection.jsx create mode 100644 src/summarySections/projectsPageSummarySection.jsx create mode 100644 src/summarySections/pullRequestsPageSummarySection.jsx diff --git a/src/historicalSections/commitsPageHistoricalSection.jsx b/src/historicalSections/commitsPageHistoricalSection.jsx new file mode 100644 index 0000000..12d707b --- /dev/null +++ b/src/historicalSections/commitsPageHistoricalSection.jsx @@ -0,0 +1,37 @@ +// components/historicalSection.jsx +import React from 'react'; +import LineChartMultiple from '../components/lineChartMultiple.jsx'; + +function CommitsPageHistoricalSection({ xDataCommits, seriesDataCommits, xDataModified, seriesDataModified, hasHistoric }) { + if (!hasHistoric) { + return ( +
+ No s'ha trobat historic_metrics.json.
+ Si és el primer dia, torna demà un cop + s'hagi fet la primera execució del + workflow Daily Metrics. +
+ ); + } + + return ( +
+
+
+ +
+
+ +
+
+
+ ); +} + +export default CommitsPageHistoricalSection; diff --git a/src/historicalSections/indexHistoricalSection.jsx b/src/historicalSections/indexHistoricalSection.jsx new file mode 100644 index 0000000..0806dfa --- /dev/null +++ b/src/historicalSections/indexHistoricalSection.jsx @@ -0,0 +1,62 @@ +import React from 'react'; +import LineChart from '../components/lineChart'; +import AreaChart from '../components/areaChart'; +import AreaChartMultiple from '../components/areaChartMultiple'; + +function IndexHistoricalSection({ features, historicData, xData, yData, xDataModifedLines, yDataModifedLines, xDataIssues, openIssues, closedIssues, xDataPRs, areaPRData, xDataTask, allSeries }) { + if (!historicData) { + return ( +
+ No s'ha trobat historic_metrics.json.
+ Si és el primer dia, torna demà. +
+ ); + } + + return ( +
+
+ +
+
+ +
+ {features.includes("issues") && ( +
+ +
+ )} + {features.includes("pull-requests") && ( +
+ +
+ )} + {features.includes("projects") && ( +
+ +
+ )} +
+ ); +} + +export default IndexHistoricalSection; diff --git a/src/historicalSections/individualHistoricalSection.jsx b/src/historicalSections/individualHistoricalSection.jsx new file mode 100644 index 0000000..797d178 --- /dev/null +++ b/src/historicalSections/individualHistoricalSection.jsx @@ -0,0 +1,167 @@ +// HistoricalCharts.jsx +import React from 'react'; +import LineChart from '../components/lineChart.jsx'; + +function IndividualHistoricSection({ + historicData, + features, + xDataCommits, + yDataCommits, + xDataModifiedLines, + yDataModifiedLines, + xDataAssignedIssues, + yDataAssignedIssues, + xDataClosedIssues, + yDataClosedIssues, + xDataCreatedPRs, + yDataCreatedPRs, + xDataMergedPRs, + yDataMergedPRs, + xDataTasksAssigned, + yDataTasksAssigned, + xDataTasksToDo, + yDataTasksToDo, + xDataTasksInProgress, + yDataTasksInProgress, + xDataTasksDone, + yDataTasksDone, + xDataTasksStandard, + yDataTasksStandard +}) { + if (!historicData) { + return ( +
+ No s'ha trobat historic_metrics.json.
+ Si és el primer dia, torna demà un cop + s'hagi fet la primera execució del + workflow Daily Metrics. +
+ ); + } + return ( +
+
+ +
+
+ +
+ {features.includes("issues") && ( + +
+ +
)} + {features.includes("issues") && ( +
+ +
)} + {features.includes("pull-requests") && ( +
+ +
)} + {features.includes("pull-requests") && ( +
+ +
)} + {features.includes("projects") && ( +
+ +
+ )} + {features.includes("projects") && ( +
+ +
+ )} + {features.includes("projects") && ( +
+ +
+ )} + {features.includes("projects") && ( +
+ +
+ )} + {features.includes("projects") && ( +
+ +
+ )} +
+ ); +} + +export default IndividualHistoricSection; diff --git a/src/historicalSections/issuesPageHistoricalSection.jsx b/src/historicalSections/issuesPageHistoricalSection.jsx new file mode 100644 index 0000000..a69b05f --- /dev/null +++ b/src/historicalSections/issuesPageHistoricalSection.jsx @@ -0,0 +1,38 @@ +// components/issuesHistoricalSection.jsx +import React from 'react'; +import LineChartMultiple from '../components/lineChartMultiple.jsx'; + +function IssuesPageHistoricalSection({ historicData, xDataAssigned, seriesDataAssigned }) { + if (!historicData) { + return ( +
+ No s'ha trobat historic_metrics.json.
+ Si és el primer dia, torna demà un cop s'hagi fet la primera execució del workflow Daily Metrics. +
+ ); + } + + return ( +
+
+
+ +
+
+
+ ); +} + +export default IssuesPageHistoricalSection; diff --git a/src/historicalSections/projectsPageHistoricalSection.jsx b/src/historicalSections/projectsPageHistoricalSection.jsx new file mode 100644 index 0000000..5162830 --- /dev/null +++ b/src/historicalSections/projectsPageHistoricalSection.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import LineChartMultiple from '../components/lineChartMultiple'; +import AreaChartMultiple from '../components/areaChartMultiple'; + +function ProjectsPageHistoricalSection({ + historicData, + xDataAssigned, + seriesDataAssigned, + xDataFeature, + allSeries, +}) { + if (!historicData) { + return ( +
+ No s'ha trobat historic_metrics.json.
+ Si és el primer dia, torna demà un cop + s'hagi fet la primera execució del + workflow Daily Metrics. +
+ ); + } + + return ( +
+
+
+ +
+
+ +
+
+
+ ); +} + +export default ProjectsPageHistoricalSection; \ No newline at end of file diff --git a/src/historicalSections/pullRequestsPageHistoricalSection.jsx b/src/historicalSections/pullRequestsPageHistoricalSection.jsx new file mode 100644 index 0000000..4cdfe29 --- /dev/null +++ b/src/historicalSections/pullRequestsPageHistoricalSection.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import LineChartMultiple from '../components/lineChartMultiple.jsx'; + +function PullRequestsPageHistoricalSection({ historicData, xDataCreated, seriesDataCreated, xDataMerged, seriesDataMerged }) { + if (!historicData) { + return ( +
+ No s'ha trobat historic_metrics.json.
+ Si és el primer dia, torna demà un cop s'hagi fet la primera execució del workflow Daily Metrics. +
+ ); + } + + return ( +
+
+
+ +
+
+ +
+
+
+ ); +} + +export default PullRequestsPageHistoricalSection; diff --git a/src/metricsSections/commitsPageMetricsSection.jsx b/src/metricsSections/commitsPageMetricsSection.jsx new file mode 100644 index 0000000..db1ff0a --- /dev/null +++ b/src/metricsSections/commitsPageMetricsSection.jsx @@ -0,0 +1,43 @@ +// components/metricsByUserSection.jsx +import React from 'react'; +import GaugeChart from '../components/gaugeChart.jsx'; + +function CommitsPageMetricsSection({ commitsGaugeData, modifiedLinesGaugeData, totalPeople }) { + return ( +
+

Metrics by User

+ +

+ Commits per user + + ⓘ + + Percentage of commits per user relative to the total number of commits + + +

+
+ {commitsGaugeData.map(({ user, percentage }) => ( + + ))} +
+ +

+ Modified lines per user + + ⓘ + + Percentage of modified lines per user relative to the total number of modified lines + + +

+
+ {modifiedLinesGaugeData.map(({ user, percentage }) => ( + + ))} +
+
+ ); +} + +export default CommitsPageMetricsSection; diff --git a/src/metricsSections/indexGraphicsSection.jsx b/src/metricsSections/indexGraphicsSection.jsx new file mode 100644 index 0000000..f4657a1 --- /dev/null +++ b/src/metricsSections/indexGraphicsSection.jsx @@ -0,0 +1,59 @@ +import React from 'react'; +import RadarChart from '../components/radarChart'; +import PieChart from '../components/pieChart'; +import GaugeChart from '../components/gaugeChart'; +import LineChart from '../components/lineChart'; + +function IndexGraphicsSection({ features, radarData, pieDataPullRequestStatus, pieDataPullRequestStatusColor, pieDataIssuesStatus, pieDataIssuesStatusColor, pieDataTasksStatus, pieDataTasksStatusColor, historicData, gaugeAnonymousData, xData, yData, xDataModifedLines, yDataModifedLines }) { + const onlyCommitsFallback = ( +
+
+ +
+ {historicData ? ( +
+
+ +
+
+ +
+
+ ) : ( +
+ No s'ha trobat historic_metrics.json.
+ Si és el primer dia, torna demà. +
+ )} +
+ ); + + if (!(features.includes("issues") || features.includes("pull-requests") || features.includes("projects"))) { + return onlyCommitsFallback; + } + + return ( +
+
+ +
+ {features.includes("pull-requests") && ( +
+ +
+ )} + {features.includes("issues") && ( +
+ +
+ )} + {features.includes("projects") && ( +
+ +
+ )} +
+ ); +} + +export default IndexGraphicsSection; diff --git a/src/metricsSections/individualMetricsSection.jsx b/src/metricsSections/individualMetricsSection.jsx new file mode 100644 index 0000000..c208a9f --- /dev/null +++ b/src/metricsSections/individualMetricsSection.jsx @@ -0,0 +1,177 @@ +// UserGaugeSection.jsx +import React from 'react'; +import GaugeChart from '../components/gaugeChart.jsx'; + +function IndividualMetricsSection({ + selectedUser, + totalPeople, + percentageCommits, + percentageModifiedlLines, + percentageAssigned, + percentageIssuesClosed, + percentageCreated, + percentageMerged, + percentageTasksAssigned, + percentageTasksInProgress, + percentageTasksDone, + percentageTasksStandard, + features +}) { + return (
+
+

+ Commits + + ⓘ + Percentage of commits made by the user relative to the total number of commits + +

+ +
+
+

+ Modified Lines + + ⓘ + Percentage of commits made by the user relative to the total number of commits + +

+ +
+ {features.includes("issues") && ( +
+

+ Issues assigned + + ⓘ + Percentage of issues assigned to the user relative to the number of assigned issues + +

+ +
)} + {features.includes("issues") && ( +
+

+ Issues closed + + ⓘ + Percentage of issues closed by the user relative to the issues assigned to the user + +

+ +
)} + {features.includes("pull-requests") && ( +
+

+ Pull Requests created + + ⓘ + Percentage of pull requests created by the user relative to the total number of pull requests + +

+ +
)} + {features.includes("pull-requests") && ( +
+

+ Pull Requests merged + + ⓘ + Percentage of pull requests merged by the user relative to the total number of pull requests merged + +

+ +
)} + {features.includes("projects") && ( +
+

+ Tasks assigned + + ⓘ + Percentage of tasks assigned to the user relative to the number of assigned tasks + +

+ +
)} + {features.includes("projects") && ( +
+

+ Tasks In Progress + + ⓘ + + Shows if the user is actively working on at least one task. + If the gauge is at 100%, the user has at least one task in progress. + If it's at 0%, the user currently has no tasks in progress. + + +

+ +
)} + {features.includes("projects") && ( +
+

+ Tasks Done + + ⓘ + Percentage of tasks done by the user relative to the tasks assigned to the user + +

+ +
)} + {features.includes("projects") && ( +
+

+ Tasks with standard status + + ⓘ + Percentage of tasks with standard status (ToDo, In Progress, Done) of the user relative to the user assigned tasks + +

+ +
)} +
+ ); +} + +export default IndividualMetricsSection; \ No newline at end of file diff --git a/src/metricsSections/issuesPageMetricsSection.jsx b/src/metricsSections/issuesPageMetricsSection.jsx new file mode 100644 index 0000000..81d319a --- /dev/null +++ b/src/metricsSections/issuesPageMetricsSection.jsx @@ -0,0 +1,53 @@ +// components/issuesMetricsByUserSection.jsx +import React from 'react'; +import GaugeChart from '../components/gaugeChart.jsx'; + +function IssuesPageMetricsSection({ gaugeDataAssigned, gaugeDataClosed, totalPeople }) { + return ( +
+

Metrics by User

+ +

+ Issues assigned per user + + ⓘ + + Percentage of issues assigned per user relative to the number of assigned issues + + +

+
+ {gaugeDataAssigned.map(({ user, percentage }) => ( + + ))} +
+ +

+ Issues closed per user + + ⓘ + + Percentage of issues closed per user relative to the issues assigned to that user + + +

+
+ {gaugeDataClosed.map(({ user, percentage }) => ( + + ))} +
+
+ ); +} + +export default IssuesPageMetricsSection; diff --git a/src/metricsSections/projectsPageMetricsSection.jsx b/src/metricsSections/projectsPageMetricsSection.jsx new file mode 100644 index 0000000..09727c5 --- /dev/null +++ b/src/metricsSections/projectsPageMetricsSection.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import GaugeChart from '../components/gaugeChart'; + +function ProjectsPageMetricsSection({ + gaugeDataAssignedTasks, + gaugeDataInProgressTasks, + gaugeDataDoneTasks, + gaugeDataStandardTasks, + totalPeople, +}) { + return ( +
+

Metrics by User

+ +

+ Tasks assigned per user + + ⓘ + Percentage of tasks assigned per user relative to the number of assigned tasks + +

+
+ {gaugeDataAssignedTasks.map(({ user, percentage }) => ( + + ))} +
+ +

+ Tasks In Progress per user + + ⓘ + + Shows if the user is actively working on at least one task. + If the gauge is at 100%, the user has at least one task in progress. + If it's at 0%, the user currently has no tasks in progress. + + +

+
+ {gaugeDataInProgressTasks.map(({ user, percentage }) => ( + + ))} +
+ +

+ Tasks Done per user + + ⓘ + + Percentage of tasks done per user relative to the tasks assigned to that user + + +

+
+ {gaugeDataDoneTasks.map(({ user, percentage }) => ( + + ))} +
+ +

+ Tasks with standard status per user + + ⓘ + + Percentage of tasks with standard status (ToDo, In Progress, Done) per user relative to their assigned tasks + + +

+
+ {gaugeDataStandardTasks.map(({ user, percentage }) => ( + + ))} +
+
+ ); +} + +export default ProjectsPageMetricsSection; \ No newline at end of file diff --git a/src/metricsSections/pullRequestsPageMetricsSection.jsx b/src/metricsSections/pullRequestsPageMetricsSection.jsx new file mode 100644 index 0000000..33ce11d --- /dev/null +++ b/src/metricsSections/pullRequestsPageMetricsSection.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import GaugeChart from '../components/gaugeChart.jsx'; + +function PullRequestsMetricsSection({ gaugeDataCreatedPRs, gaugeDataMergedPRs, totalPeople }) { + return ( +
+

Metrics by User

+ +

+ Pull requests created per user + + ⓘ + + Percentage of pull requests created per user relative to the total number of pull requests + + +

+
+ {gaugeDataCreatedPRs.map(({ user, percentage }) => ( + + ))} +
+ +

+ Pull requests merged per user + + ⓘ + + Percentage of pull requests merged per user relative to the number of merged pull requests + + +

+
+ {gaugeDataMergedPRs.map(({ user, percentage }) => ( + + ))} +
+
+ ); +} + +export default PullRequestsMetricsSection; diff --git a/src/pages/commitsPage.jsx b/src/pages/commitsPage.jsx index 71d3017..7ecf644 100644 --- a/src/pages/commitsPage.jsx +++ b/src/pages/commitsPage.jsx @@ -1,14 +1,13 @@ import React from 'react'; -import GaugeChart from '../components/gaugeChart.jsx'; -import RadarPieToggle from '../components/radarPieToggle.jsx'; -import LineChartMultiple from '../components/lineChartMultiple.jsx'; import usePersistentStateSession from '../components/usePersistentStateSession.jsx'; import HistoricalToggle from '../components/historicalToggle.jsx'; import DateRangeSelector from '../components/dateRangeSelector.jsx'; -import '../styles/commits.css'; +import CommitsPageSummarySection from '../summarySections/commitsPageSummarySection.jsx'; +import CommitsPageMetricsSection from '../metricsSections/commitsPageMetricsSection.jsx'; +import CommitsPageHistoricalSection from '../historicalSections/commitsPageHistoricalSection.jsx'; +import '../styles/elements.css'; import { - getGaugeDataAnonymous, transformCommitsDataForLineChart, transformModifiedLinesDataForLineChart, getPieChartDataCommits, @@ -31,8 +30,8 @@ function CommitsPage({ data, historicData, features }) { const { xData: xDataModified, seriesData: seriesDataModified } = transformModifiedLinesDataForLineChart(filteredHistoricData || {}); const totalCommits = data.commits?.total || 0; - const gaugeDataAnonymous = getGaugeDataAnonymous(data) - + const gaugeDataAnonymous = totalCommits > 0 ? ((totalCommits - (data.commits?.anonymous || 0)) / totalCommits) : 0; + const dataPieChartCommits = getPieChartDataCommits(data); const dataPieChartModifiedLines = getPieChartDataModifiedLines(data); const commitsGaugeData = getGaugeChartDataCommits(data); @@ -46,106 +45,34 @@ function CommitsPage({ data, historicData, features }) {

Commits

- - - + + {!showHistorical && ( <> -
-

Summary

-
-
- -
-
- -
-
-

- Non-anonymous commits - - ⓘ - Percentage of commits that have a member of the project as its author - -

- -
-
-
- -
-

Metrics by User

- -

- Commits per user - - ⓘ - Percentage of commits per user relative to the total number of commits - -

-
- {commitsGaugeData.map(({ user, percentage }) => ( - - ))} -
- -

- Modified lines per user - - ⓘ - Percentage of modified lines per user relative to the total number of modified lines - -

-
- {modifiedLinesGaugeData.map(({ user, percentage }) => ( - - ))} -
-
+ + )} {showHistorical && ( - <> - {historicData ? ( -
-
-
- -
-
- -
-
-
- ) : ( -
- No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop - s'hagi fet la primera execució del - workflow Daily Metrics. -
- )} - + )}
); diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 5ba3068..6a6f238 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -1,15 +1,14 @@ -import React, { useState } from 'react'; -import "../styles/index.css"; -import PieChart from '../components/pieChart'; -import RadarChart from '../components/radarChart'; -import LineChart from '../components/lineChart'; -import AreaChart from '../components/areaChart'; -import GaugeChart from '../components/gaugeChart'; -import AreaChartMultiple from '../components/areaChartMultiple'; -import usePersistentStateSession from '../components/usePersistentStateSession'; +import React from 'react'; +import "../styles/index.css"; +import usePersistentStateSession from '../components/usePersistentStateSession'; import HistoricalToggle from '../components/historicalToggle.jsx'; import DateRangeSelector from '../components/dateRangeSelector.jsx'; -import {filterHistoricData } from '../domain/utils'; + +import IndexStatsSection from '../summarySections/indexStatsSection.jsx'; +import IndexGraphicsSection from '../metricsSections/indexGraphicsSection.jsx'; +import IndexHistoricalSection from '../historicalSections/indexHistoricalSection.jsx'; + +import { filterHistoricData } from '../domain/utils'; import { getGaugeDataAnonymous, transformDataForLineChartCommits, @@ -38,269 +37,104 @@ import { getPieDataTasksStatus } from '../domain/projects'; - -function Index({data,historicData,features}) { +function Index({ data, historicData, features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalIndex', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeIndex', "7"); + const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; + // Radar Data let radarData = {}; - - radarData['Non-Anonymous Commits'] = - getGaugeDataAnonymous(data) + radarData['Non-Anonymous Commits'] = getGaugeDataAnonymous(data); if (features.includes('issues')) { - radarData['Issues Assigned'] = - getGaugeDataIssuesAssigned(data); - - radarData['Issues associated PR'] = - getGaugeDataIssuesHavePR(data); + radarData['Issues Assigned'] = getGaugeDataIssuesAssigned(data); + radarData['Issues associated PR'] = getGaugeDataIssuesHavePR(data); } if (features.includes('pull-requests')) { - radarData['Pull Requests Merged'] = - getGaugeChartDataMergedPRs(data); - - radarData['Pull Requests Reviewed'] = - getGaugeChartDataReviewedPRs(data); - - radarData['Pull Requests Merges'] = - getGaugeChartDataMergesPRs(data); + radarData['Pull Requests Merged'] = getGaugeChartDataMergedPRs(data); + radarData['Pull Requests Reviewed'] = getGaugeChartDataReviewedPRs(data); + radarData['Pull Requests Merges'] = getGaugeChartDataMergesPRs(data); } + if (features.includes('projects')) { - radarData['Tasks Assigned'] = - getGaugeDataTasksAssigned(data,"total") + radarData['Tasks Assigned'] = getGaugeDataTasksAssigned(data, "total"); + radarData['Tasks With Standard Status'] = getGaugeDataTasksStandardStatus(data, "total"); + radarData['Items that are Issues'] = getGaugeDataItemsIssues(data, "total"); + radarData['Projects Issues with type'] = getGaugeDataItemIssuesWithType(data, "total"); - radarData['Tasks With Standard Status'] = - getGaugeDataTasksStandardStatus(data,"total") - radarData['Items that are Issues'] = - getGaugeDataItemsIssues(data,"total") - radarData['Projects Issues with type'] = - getGaugeDataItemIssuesWithType(data,"total") - if(data.project.has_iterations) { - radarData['Items with iteration'] = - getGaugeDataItemIssuesWithIteration(data,"total") + if (data.project.has_iterations) { + radarData['Items with iteration'] = getGaugeDataItemIssuesWithIteration(data, "total"); } } - const {pieDataPullRequestStatus,pieDataPullRequestStatusColor} = getPieDataPullRequestStatus(data) - const {pieDataIssuesStatus,pieDataIssuesStatusColor} = getPieDataIssuesStatus(data) - const {pieDataTasksStatus,pieDataTasksStatusColor} = getPieDataTasksStatus(data) + // Pie Chart Data + const { pieDataPullRequestStatus, pieDataPullRequestStatusColor } = getPieDataPullRequestStatus(data); + const { pieDataIssuesStatus, pieDataIssuesStatusColor } = getPieDataIssuesStatus(data); + const { pieDataTasksStatus, pieDataTasksStatusColor } = getPieDataTasksStatus(data); + // Line/Area Chart Data const { xData, yData } = transformDataForLineChartCommits(filteredhistoricaData); - const { xDataModifedLines, yDataModifedLines } = transformDataForLineChartModifiedLines(filteredhistoricaData); - const { xDataIssues, closedIssues, openIssues } = transformIssuesDataForAreaChart(filteredhistoricaData); - const { xDataPRs, areaPRData } = transformPRDataForAreaChart(filteredhistoricaData); - const { xDataTask, allSeries } = transformTaskDataForAreaChart(filteredhistoricaData); - - const gaugeAnonymousData = getGaugeDataAnonymous(data) + + const gaugeAnonymousData = getGaugeDataAnonymous(data); + return (

General overview

-
-
-
-

Project Statistics

-
-
-
Total Members: {Object.keys(data.avatars).length}
-
Total Commits: {data.commits.total}
-
Total Additions: {data.modified_lines.total.additions}
-
Total Deletions: {data.modified_lines.total.deletions}
-
Total Modifications: {data.modified_lines.total.modified}
-
Total Lines of code: {data.modified_lines.total.additions - data.modified_lines.total.deletions}
- {features.includes("issues") && ( -
Total Issues: {data.issues.total}
) } - {features.includes("pull-requests") && ( -
Total Pull Requests: {data.pull_requests.total}
)} - {features.includes("projects") && ( -
Total Tasks: {data.project.metrics_by_iteration.total.total_tasks}
)} - {features.includes("projects") && ( -
Total Features: {data.project.metrics_by_iteration.total.total_features}
)} -
-
-
- {(features.includes("issues") || features.includes("pull-requests") || features.includes("projects")) && ( - )} - - - - {!showHistorical && ( - <> - {(features.includes("issues") || features.includes("pull-requests") || features.includes("projects")) ? ( -
-
- -
- {features.includes("pull-requests") && ( -
- -
- )} - {features.includes("issues") && ( -
- -
- )} - {features.includes("projects") && ( -
- -
- )} -
- ) : ( -
-
-
- -
- { historicData ? - - (
-
- -
-
- -
-
) :
- No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop
- s'hagi fet la primera execució del
- workflow Daily Metrics. -
} -
-
- )} - -)} - - {showHistorical && ( -
- {historicData ? ( -
-
- -
-
- -
- {features.includes("issues") && ( -
- -
)} - {features.includes("pull-requests") && ( -
- -
)} - {features.includes("projects") && ( -
- -
)} -
- ) : ( -
- No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop - s'hagi fet la primera execució del - workflow Daily Metrics. -
- )} -
+ + + {(features.includes("issues") || features.includes("pull-requests") || features.includes("projects")) && ( + + )} + + + + {!showHistorical ? ( + + ) : ( + )}
- ); + ); } export default Index; diff --git a/src/pages/individual.jsx b/src/pages/individual.jsx index 5be2b72..b19dfda 100644 --- a/src/pages/individual.jsx +++ b/src/pages/individual.jsx @@ -1,29 +1,33 @@ import React, { useState } from 'react'; import "../styles/individual.css"; -import LineChart from '../components/lineChart'; -import GaugeChart from '../components/gaugeChart'; -import usePersistentStateSession from '../components/usePersistentStateSession'; -import usePersistentState from '../components/usePersistentState'; +import LineChart from '../components/lineChart.jsx'; +import GaugeChart from '../components/gaugeChart.jsx'; +import usePersistentStateSession from '../components/usePersistentStateSession.jsx'; +import usePersistentState from '../components/usePersistentState.jsx'; import HistoricalToggle from '../components/historicalToggle.jsx'; import DateRangeSelector from '../components/dateRangeSelector.jsx'; +import IndividualStatsSection from '../summarySections/individualStatisticsSection.jsx'; +import IndividualMetricsSection from '../metricsSections/individualMetricsSection.jsx'; +import IndividualHistoricSection from '../historicalSections/individualHistoricalSection.jsx'; + import { transformCommitsDataForUser, getGaugeChartDataCommits, getGaugeChartDataModifiedLines, transformModifiedLinesDataForUser -} from '../domain/commits' +} from '../domain/commits.jsx' import { getGaugeDataAssignedIssuesPerUser, getGaugeDataClosedIssuesPerUser, transformAssignedIssuesDataForUser, transformClosedIssuesDataForUser -} from '../domain/issues' +} from '../domain/issues.jsx' import { getGaugeDataCreatedPRsPerUser, getGaugeDataMergedPRsPerUser, transformCreatedPRsDataForUser, transformMergedPRsDataForUser -} from '../domain/pullRequests' +} from '../domain/pullRequests.jsx' import { getGaugeDataAssignedTasksPerUser, getGaugeDataInProgressTasksPerUser, @@ -34,11 +38,11 @@ import { transformTasksInProgressDataForUser, transformTasksDoneDataForUser, transformTasksStandardDataForUser, -} from '../domain/projects' +} from '../domain/projects.jsx' import { filterHistoricData, truncateName } -from '../domain/utils'; +from '../domain/utils.jsx'; function Individual({ data, historicData, features }) { const [selectedUser, setSelectedUser] = usePersistentState("selectedUser",Object.keys(data.avatars)[0]); const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalIndividual', false); @@ -114,44 +118,26 @@ function Individual({ data, historicData, features }) { return (

Individual overview

-
-
-
- {selectedUser} - -
-
-
Commits: {commits}
-
Additions: {modifiedLines.additions}
-
Deletions: {modifiedLines.deletions}
-
Modifications: {modifiedLines.modified}
-
Commit Streak: {streak}
-
Longest Streak: {longestStreak}
- {features.includes("issues") && ( -
Issues Assigned: {issuesAssigned}
)} - {features.includes("issues") && ( -
Issues Closed: {issuesClosed}
)} - {features.includes("pull-requests") && ( -
Pull Requests Created: {pullRequestsCreated}
)} - {features.includes("pull-requests") && ( -
Pull Requests merged: {pullRequestsMerged}
)} - {features.includes("projects") && ( -
Tasks assigned: {tasksAssigned}
)} - {features.includes("projects") && ( -
Tasks ToDo: {tasksTodo}
)} - {features.includes("projects") && ( -
Tasks In Progress: {tasksInProgress}
)} - {features.includes("projects") && ( -
Tasks Done: {tasksClosed}
)} - {features.includes("projects") && ( -
Tasks with a standard status: {tasksStandard}
)} -
-
-
+ {!showHistorical && ( - <> -
-
-

- Commits - - ⓘ - Percentage of commits made by the user relative to the total number of commits - -

- -
-
-

- Modified Lines - - ⓘ - Percentage of commits made by the user relative to the total number of commits - -

- -
- {features.includes("issues") && ( -
-

- Issues assigned - - ⓘ - Percentage of issues assigned to the user relative to the number of assigned issues - -

- -
)} - {features.includes("issues") && ( -
-

- Issues closed - - ⓘ - Percentage of issues closed by the user relative to the issues assigned to the user - -

- -
)} - {features.includes("pull-requests") && ( -
-

- Pull Requests created - - ⓘ - Percentage of pull requests created by the user relative to the total number of pull requests - -

- -
)} - {features.includes("pull-requests") && ( -
-

- Pull Requests merged - - ⓘ - Percentage of pull requests merged by the user relative to the total number of pull requests merged - -

- -
)} - {features.includes("projects") && ( -
-

- Tasks assigned - - ⓘ - Percentage of tasks assigned to the user relative to the number of assigned tasks - -

- -
)} - {features.includes("projects") && ( -
-

- Tasks In Progress - - ⓘ - - Shows if the user is actively working on at least one task. - If the gauge is at 100%, the user has at least one task in progress. - If it's at 0%, the user currently has no tasks in progress. - - -

- -
)} - {features.includes("projects") && ( -
-

- Tasks Done - - ⓘ - Percentage of tasks done by the user relative to the tasks assigned to the user - -

- -
)} - {features.includes("projects") && ( -
-

- Tasks with standard status - - ⓘ - Percentage of tasks with standard status (ToDo, In Progress, Done) of the user relative to the user assigned tasks - -

- -
)} -
- )} + + )} {showHistorical && ( - <> - {historicData ? ( - <> -
-
- -
-
- -
- {features.includes("issues") && ( - -
- -
)} - {features.includes("issues") && ( -
- -
)} - {features.includes("pull-requests") && ( -
- -
)} - {features.includes("pull-requests") && ( -
- -
)} - {features.includes("projects") && ( -
- -
- )} - {features.includes("projects") && ( -
- -
- )} - {features.includes("projects") && ( -
- -
- )} - {features.includes("projects") && ( -
- -
- )} - - {features.includes("projects") && ( -
- -
- )} -
- ) : ( -
- No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop - s'hagi fet la primera execució del - workflow Daily Metrics. -
)} - - )} + + )}
); } diff --git a/src/pages/issuesPage.jsx b/src/pages/issuesPage.jsx index c992ae0..8325df5 100644 --- a/src/pages/issuesPage.jsx +++ b/src/pages/issuesPage.jsx @@ -1,8 +1,6 @@ -import React, { useState } from 'react'; -import GaugeChart from '../components/gaugeChart'; -import RadarPieToggle from '../components/radarPieToggle'; -import LineChartMultiple from '../components/lineChartMultiple'; -import usePersistentStateSession from '../components/usePersistentStateSession'; +// pages/IssuesPage.jsx +import React from 'react'; +import usePersistentStateSession from '../components/usePersistentStateSession'; import HistoricalToggle from '../components/historicalToggle.jsx'; import DateRangeSelector from '../components/dateRangeSelector.jsx'; import { @@ -15,23 +13,30 @@ import { } from '../domain/issues'; import { filterHistoricData } from '../domain/utils.jsx'; -import '../styles/commits.css'; +import IssuesSummarySection from '../summarySections/issuesPageSummarySection.jsx'; +import IssuesMetricsByUserSection from '../metricsSections/issuesPageMetricsSection.jsx'; +import IssuesHistoricalSection from '../historicalSections/issuesPageHistoricalSection.jsx'; -function IssuesPage({ data,historicData,features }) { +import '../styles/elements.css'; + +function IssuesPage({ data, historicData, features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalIssues', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeIssues', "7"); - - const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; + + const filteredHistoricalData = historicData ? filterHistoricData(historicData, dateRange) : null; const totalPeople = Object.keys(data.avatars).length; - const { xDataAssigned, seriesDataAssigned } = transformAssignedIssuesDataForLineChart(filteredhistoricaData); + + const { xDataAssigned, seriesDataAssigned } = transformAssignedIssuesDataForLineChart(filteredHistoricalData); const percentageAssigned = getGaugeDataIssuesAssigned(data); const gaugeDataHavePR = getGaugeDataIssuesHavePR(data); const gaugeDataAssigned = getGaugeDataAssignedIssuesPerUser(data); const gaugeDataClosed = getGaugeDataClosedIssuesPerUser(data); - const {radarData,pieData} = getRadarAndPieDataIssuesAssigned(data) + const { radarData, pieData } = getRadarAndPieDataIssuesAssigned(data); + return (

Issues

+ - {!showHistorical && ( - <> -
-

Summary

-
-
- -
-
-

- Issues assigned - - ⓘ - Percentage of Issues that are assigned to a user relative to the total number of Issues - -

- -
- {features.includes("pull-requests") && ( -
-

- Issues closed with Pull Request - - ⓘ - - Percentage of closed Issues that have a Pull Request associated - - -

- -
)} -
-
-
-

Metrics by User

-

- Issues assigned per user - - ⓘ - Percentage of issues assigned per user relative to the number of assigned issues - -

-
- {gaugeDataAssigned.map(({ user, percentage }) => ( - - ))} -
-

- Issues closed per user - - ⓘ - - Percentage of issues closed per user relative to the issues assigned to that user - - -

-
- {gaugeDataClosed.map(({ user, percentage }) => ( - - ))} -
-
- )} - -{showHistorical && ( - <> - {historicData ? ( + {!showHistorical && ( <> -
-
-
- -
-
-
- - ) : ( -
- No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop - s'hagi fet la primera execució del - workflow Daily Metrics. -
)} - + + )} + {showHistorical && ( + + )}
); } diff --git a/src/pages/projectsPage.jsx b/src/pages/projectsPage.jsx index f7a50f9..b46ef98 100644 --- a/src/pages/projectsPage.jsx +++ b/src/pages/projectsPage.jsx @@ -1,12 +1,12 @@ import React, { useState } from 'react'; -import GaugeChart from '../components/gaugeChart'; -import RadarPieToggle from '../components/radarPieToggle'; -import PieChart from '../components/pieChart'; -import LineChartMultiple from '../components/lineChartMultiple'; -import AreaChartMultiple from '../components/areaChartMultiple'; -import usePersistentStateSession from '../components/usePersistentStateSession'; +import usePersistentStateSession from '../components/usePersistentStateSession'; import HistoricalToggle from '../components/historicalToggle.jsx'; import DateRangeSelector from '../components/dateRangeSelector.jsx'; + +import ProjectsPageSummarySection from '../summarySections/projectsPageSummarySection.jsx'; +import ProjectsPageMetricsSection from '../metricsSections/projectsPageMetricsSection.jsx'; +import ProjectsPageHistoricalSection from '../historicalSections/projectsPageHistoricalSection.jsx'; + import { getActiveIteration, filterHistoricDataByIteration, @@ -25,16 +25,15 @@ import { getGaugeDataInProgressTasksPerUser, getGaugeDataDoneTasksPerUser, getGaugeDataStandardStatusTasksPerUser -} from '../domain/projects' +} from '../domain/projects'; import { filterHistoricData } from '../domain/utils'; -import '../styles/commits.css'; -function ProjectsPage({ data,historicData,features }) { +function ProjectsPage({ data, historicData, features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalProject', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeProject', "7"); - const [selectedIteration, setSelectedIteration] = usePersistentStateSession("activeIteration",getActiveIteration(data)); - + const [selectedIteration, setSelectedIteration] = usePersistentStateSession("activeIteration", getActiveIteration(data)); + const iterationsOrdered = Object.keys(data.project.iterations); const iterations = Object.keys(data.project.metrics_by_iteration); @@ -45,11 +44,13 @@ function ProjectsPage({ data,historicData,features }) { const totalPeople = Object.keys(data.avatars).length; const filteredhistoricaData = historicData - ? (selectedIteration === "total" - ? filterHistoricData(historicData, dateRange) - : filterHistoricDataByIteration(data,historicData, selectedIteration)) - : null; - const {radarDataAssigned,pieDataAssigned} = getRadarPieDataAssignedTasks(data,selectedIteration) + ? (selectedIteration === "total" + ? filterHistoricData(historicData, dateRange) + : filterHistoricDataByIteration(data, historicData, selectedIteration)) + : null; + + const { radarDataAssigned, pieDataAssigned } = getRadarPieDataAssignedTasks(data, selectedIteration); + const { xDataAssigned, seriesDataAssigned } = transformAssignedTasksDataForLineChart( data, filteredhistoricaData, @@ -57,23 +58,23 @@ function ProjectsPage({ data,historicData,features }) { data.project.iterations ); - const { xDataFeature,allSeries } = transformFeatureDataForAreaChart(filteredhistoricaData,selectedIteration,data.project.iterations); - - const {typePieChartData,typeColorsPieChart} = getIssueTypeDataForChart(data, selectedIteration); + const { xDataFeature, allSeries } = transformFeatureDataForAreaChart(filteredhistoricaData, selectedIteration, data.project.iterations); + + const { typePieChartData, typeColorsPieChart } = getIssueTypeDataForChart(data, selectedIteration); - const {featurePieChartData,featureColorsPieChart} = getFeatureDataForChart(data, selectedIteration); + const { featurePieChartData, featureColorsPieChart } = getFeatureDataForChart(data, selectedIteration); + + const percentageTasksAssigned = getGaugeDataTasksAssigned(data, selectedIteration); + const percentageStandardStatus = getGaugeDataTasksStandardStatus(data, selectedIteration); + const percentageItemsIssues = getGaugeDataItemsIssues(data, selectedIteration); + const percentageItemIssuesWithType = getGaugeDataItemIssuesWithType(data, selectedIteration); + const percentageIteration = getGaugeDataItemIssuesWithIteration(data); + const gaugeDataAssignedTasks = getGaugeDataAssignedTasksPerUser(data, selectedIteration); + const gaugeDataInProgressTasks = getGaugeDataInProgressTasksPerUser(data, selectedIteration); + const gaugeDataDoneTasks = getGaugeDataDoneTasksPerUser(data, selectedIteration); + const gaugeDataStandardTasks = getGaugeDataStandardStatusTasksPerUser(data, selectedIteration); - const percentageTasksAssigned = getGaugeDataTasksAssigned(data,selectedIteration) - const percentageStandardStatus = getGaugeDataTasksStandardStatus(data,selectedIteration) - const percentageItemsIssues = getGaugeDataItemsIssues(data,selectedIteration) - const percentageItemIssuesWithType = getGaugeDataItemIssuesWithType(data,selectedIteration) - const percentageIteration = getGaugeDataItemIssuesWithIteration(data) - const gaugeDataAssignedTasks = getGaugeDataAssignedTasksPerUser(data,selectedIteration); - const gaugeDataInProgressTasks = getGaugeDataInProgressTasksPerUser(data,selectedIteration); - const gaugeDataDoneTasks = getGaugeDataDoneTasksPerUser(data,selectedIteration); - const gaugeDataStandardTasks = getGaugeDataStandardStatusTasksPerUser(data,selectedIteration) return ( -

Projects

))} - + {!showHistorical && ( -
- {getDateRangeForIteration(data,selectedIteration)} -
)} +
+ {getDateRangeForIteration(data, selectedIteration)} +
+ )}
{selectedIteration === "total" && ( - + )} - {!showHistorical && ( - <> -
-

Summary

-
-
- -
-
- -
-
- -
-
-

- Tasks assigned - - ⓘ - Percentage of tasks that are assigned to a user relative to the total number of Tasks - -

- -
-
-

- Tasks with standard status - - ⓘ - Percentage of tasks that have a standard status (ToDo, In Progress, Done) to the total of tasks - -

- -
-
-

- Issues - - ⓘ - Percentage of Items that are Issues - -

- -
-
-

- Issues with type - - ⓘ - Percentage of Issues that have a type assigned - -

- -
- {data.project.has_iterations && ( -
-

- Items with iteration - - ⓘ - Percentage of Items that are assigned to an iteration - -

- -
)} -
-
-
-

Metrics by User

-

- Tasks assigned per user - - ⓘ - Percentage of tasks assigned per user relative to the number of assigned tasks - -

-
- {gaugeDataAssignedTasks.map(({ user, percentage }) => ( - - ))} -
-

- Tasks In Progress per user - - ⓘ - - Shows if the user is actively working on at least one task. - If the gauge is at 100%, the user has at least one task in progress. - If it's at 0%, the user currently has no tasks in progress. - - -

-
- {gaugeDataInProgressTasks.map(({ user, percentage }) => ( - - ))} -
-

- Tasks Done per user - - ⓘ - - Percentage of tasks done per user relative to the tasks assigned to that user - - -

-
- {gaugeDataDoneTasks.map(({ user, percentage }) => ( - - ))} -
- -

- Tasks with standard status per user - - ⓘ - - Percentage of tasks with standard status (ToDo, In Progress, Done) per user relative to their assigned tasks - - -

-
- {gaugeDataStandardTasks.map(({ user, percentage }) => ( - - ))} -
-
- )} - - {showHistorical && ( + {!showHistorical ? ( <> - {historicData ? ( - <> -
-
-
- -
-
- -
-
-
- ) : ( -
- No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop - s'hagi fet la primera execució del - workflow Daily Metrics. -
)} - + + ) : ( + )}
); diff --git a/src/pages/pullRequestsPage.jsx b/src/pages/pullRequestsPage.jsx index 46f98a0..b7e90ae 100644 --- a/src/pages/pullRequestsPage.jsx +++ b/src/pages/pullRequestsPage.jsx @@ -1,9 +1,5 @@ -import React, { useState } from 'react'; -import GaugeChart from '../components/gaugeChart'; -import '../styles/commits.css'; -import RadarPieToggle from '../components/radarPieToggle'; -import LineChartMultiple from '../components/lineChartMultiple'; -import usePersistentStateSession from '../components/usePersistentStateSession'; +import React from 'react'; +import usePersistentStateSession from '../components/usePersistentStateSession'; import { transformCreatedPRsDataForLineChart, transformMergedPRsDataForLineChart, @@ -14,222 +10,85 @@ import { getGaugeChartDataMergesPRs, getGaugeDataCreatedPRsPerUser, getGaugeDataMergedPRsPerUser -} from '../domain/pullRequests' +} from '../domain/pullRequests'; +import HistoricalToggle from '../components/historicalToggle'; +import DateRangeSelector from '../components/dateRangeSelector.jsx'; import { filterHistoricData } from '../domain/utils'; -function PullRequestsPage({ data,historicData,features }) { +import PullRequestsPageSummarySection from '../summarySections/pullRequestsPageSummarySection.jsx'; +import PullRequestsPageMetricsSection from '../metricsSections/pullRequestsPageMetricsSection.jsx'; +import PullRequestsPageHistoricalSection from '../historicalSections/pullRequestsPageHistoricalSection.jsx'; + +import '../styles/elements.css'; + +function PullRequestsPage({ data, historicData, features }) { const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalPR', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangePR', "7"); + const filteredhistoricaData = historicData ? filterHistoricData(historicData, dateRange) : null; - const pullRequests = data.pull_requests - const createdby = pullRequests.created - const mergedby = pullRequests.merged_per_member const totalPeople = Object.keys(data.avatars).length; - const total = data.pull_requests.total - const totalMerged = data.pull_requests.merged const { xDataCreated, seriesDataCreated } = transformCreatedPRsDataForLineChart(filteredhistoricaData); - const { xDataMerged, seriesDataMerged } = transformMergedPRsDataForLineChart(filteredhistoricaData); - const {radarDataCreated,pieDataCreated} = getRadarAndPieDataForCreatedPRs(data); - - const {radarDataMerged,pieDataMerged} = getRadarAndPieDataForMergedPRs(data); + const { radarDataCreated, pieDataCreated } = getRadarAndPieDataForCreatedPRs(data); + const { radarDataMerged, pieDataMerged } = getRadarAndPieDataForMergedPRs(data); const percentageMerged = getGaugeChartDataMergedPRs(data); const percentatgeReviewed = getGaugeChartDataReviewedPRs(data); const percentageMergesPR = getGaugeChartDataMergesPRs(data); - const gaugeDataCreatedPRs = getGaugeDataCreatedPRsPerUser(data) - const gaugeDataMergedPRs = getGaugeDataMergedPRsPerUser(data) + const gaugeDataCreatedPRs = getGaugeDataCreatedPRsPerUser(data); + const gaugeDataMergedPRs = getGaugeDataMergedPRsPerUser(data); + return (

Pull requests

+
-
- - +
-
- {showHistorical && ( -
- -
- )} - - {!showHistorical && ( - <> -
-

Summary

-
-
- -
-
- -
-
-
-
-

- Pull requests merged - - ⓘ - Percentage of pull requests that are merged relative to the total number of pull requests that are not closed - -

- -
-
-

- Pull requests reviewed - - ⓘ - - Percentage of pull requests merged by another user that is not the author relative to the total number of merged pull requests - - -

- -
-
-

- Merges from pull requests - - ⓘ - - Percentage of merges that are from pull requests - - -

- -
-
-
-
-

Metrics by User

-

- Pull requests created per user - - ⓘ - Percentage of pull requests created per user relative to the total number of pull requets - -

-
- {gaugeDataCreatedPRs.map(({ user, percentage }) => ( - - ))} -
-

- Pull requests merged per user - - ⓘ - Percentage of pull requests merged per user relative to the number of merged pull requets - -

-
- {gaugeDataMergedPRs.map(({ user, percentage }) => ( - - ))} -
-
- )} - {showHistorical && ( - <> - {historicData ? ( + + {!showHistorical && ( <> -
-
-
- -
-
- -
-
-
+ + )} - ) : ( -
- No s'ha trobat historic_metrics.json.
- Si és el primer dia, torna demà un cop - s'hagi fet la primera execució del - workflow Daily Metrics. -
)} - + {showHistorical && ( + )}
); diff --git a/src/styles/commits.css b/src/styles/elements.css similarity index 100% rename from src/styles/commits.css rename to src/styles/elements.css diff --git a/src/summarySections/commitsPageSummarySection.jsx b/src/summarySections/commitsPageSummarySection.jsx new file mode 100644 index 0000000..444edbb --- /dev/null +++ b/src/summarySections/commitsPageSummarySection.jsx @@ -0,0 +1,49 @@ +// components/summarySection.jsx +import React from 'react'; +import RadarPieToggle from '../components/radarPieToggle.jsx'; +import GaugeChart from '../components/gaugeChart.jsx'; + +function CommitsPageSummarySection({ radarChartCommits, radarChartModifiedLines, dataPieChartCommits, dataPieChartModifiedLines, gaugeDataAnonymous }) { + return ( +
+

Summary

+
+
+ +
+
+ +
+
+

+ Non-anonymous commits + + ⓘ + + Percentage of commits that have a member of the project as its author + + +

+ +
+
+
+ ); +} + +export default CommitsPageSummarySection; diff --git a/src/summarySections/indexStatsSection.jsx b/src/summarySections/indexStatsSection.jsx new file mode 100644 index 0000000..568c132 --- /dev/null +++ b/src/summarySections/indexStatsSection.jsx @@ -0,0 +1,35 @@ +import React from 'react'; + +function IndexStatsSection({ data, features }) { + return ( +
+
+
+

Project Statistics

+
+
+
Total Members: {Object.keys(data.avatars).length}
+
Total Commits: {data.commits.total}
+
Total Additions: {data.modified_lines.total.additions}
+
Total Deletions: {data.modified_lines.total.deletions}
+
Total Modifications: {data.modified_lines.total.modified}
+
Total Lines of code: {data.modified_lines.total.additions - data.modified_lines.total.deletions}
+ {features.includes("issues") && ( +
Total Issues: {data.issues.total}
+ )} + {features.includes("pull-requests") && ( +
Total Pull Requests: {data.pull_requests.total}
+ )} + {features.includes("projects") && ( + <> +
Total Tasks: {data.project.metrics_by_iteration.total.total_tasks}
+
Total Features: {data.project.metrics_by_iteration.total.total_features}
+ + )} +
+
+
+ ); +} + +export default IndexStatsSection; diff --git a/src/summarySections/individualStatisticsSection.jsx b/src/summarySections/individualStatisticsSection.jsx new file mode 100644 index 0000000..d8525a6 --- /dev/null +++ b/src/summarySections/individualStatisticsSection.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { truncateName } from '../domain/utils'; + +function IndividualStatsSection({ + selectedUser, + setSelectedUser, + users, + avatar, + commits, + modifiedLines, + streak, + longestStreak, + issuesAssigned, + issuesClosed, + pullRequestsCreated, + pullRequestsMerged, + tasksAssigned, + tasksTodo, + tasksInProgress, + tasksClosed, + tasksStandard, + features +}) { + return (
+
+
+ {selectedUser} + +
+
+
Commits: {commits}
+
Additions: {modifiedLines.additions}
+
Deletions: {modifiedLines.deletions}
+
Modifications: {modifiedLines.modified}
+
Commit Streak: {streak}
+
Longest Streak: {longestStreak}
+ {features.includes("issues") && ( +
Issues Assigned: {issuesAssigned}
)} + {features.includes("issues") && ( +
Issues Closed: {issuesClosed}
)} + {features.includes("pull-requests") && ( +
Pull Requests Created: {pullRequestsCreated}
)} + {features.includes("pull-requests") && ( +
Pull Requests merged: {pullRequestsMerged}
)} + {features.includes("projects") && ( +
Tasks assigned: {tasksAssigned}
)} + {features.includes("projects") && ( +
Tasks ToDo: {tasksTodo}
)} + {features.includes("projects") && ( +
Tasks In Progress: {tasksInProgress}
)} + {features.includes("projects") && ( +
Tasks Done: {tasksClosed}
)} + {features.includes("projects") && ( +
Tasks with a standard status: {tasksStandard}
)} +
+
+
+ ); +} + +export default IndividualStatsSection; diff --git a/src/summarySections/issuesPageSummarySection.jsx b/src/summarySections/issuesPageSummarySection.jsx new file mode 100644 index 0000000..f5eab44 --- /dev/null +++ b/src/summarySections/issuesPageSummarySection.jsx @@ -0,0 +1,62 @@ +// components/issuesSummarySection.jsx +import React from 'react'; +import GaugeChart from '../components/gaugeChart.jsx'; +import RadarPieToggle from '../components/radarPieToggle.jsx'; + +function IssuesPageSummarySection({ radarData, pieData, percentageAssigned, gaugeDataHavePR, features }) { + return ( +
+

Summary

+
+
+ +
+ +
+

+ Issues assigned + + ⓘ + + Percentage of Issues that are assigned to a user relative to the total number of Issues + + +

+ +
+ + {features.includes("pull-requests") && ( +
+

+ Issues closed with Pull Request + + ⓘ + + Percentage of closed Issues that have a Pull Request associated + + +

+ +
+ )} +
+
+ ); +} + +export default IssuesPageSummarySection; diff --git a/src/summarySections/projectsPageSummarySection.jsx b/src/summarySections/projectsPageSummarySection.jsx new file mode 100644 index 0000000..1593f4c --- /dev/null +++ b/src/summarySections/projectsPageSummarySection.jsx @@ -0,0 +1,131 @@ +import React from 'react'; +import RadarPieToggle from '../components/radarPieToggle'; +import PieChart from '../components/pieChart'; +import GaugeChart from '../components/gaugeChart'; + +function ProjectsPageSummarySection({ + radarDataAssigned, + pieDataAssigned, + typePieChartData, + typeColorsPieChart, + featurePieChartData, + featureColorsPieChart, + percentageTasksAssigned, + percentageStandardStatus, + percentageItemsIssues, + percentageItemIssuesWithType, + percentageIteration, + data, +}) { + return ( +
+

Summary

+
+
+ +
+
+ +
+
+ +
+
+

+ Tasks assigned + + ⓘ + + Percentage of tasks that are assigned to a user relative to the total number of Tasks + + +

+ +
+
+

+ Tasks with standard status + + ⓘ + + Percentage of tasks that have a standard status (ToDo, In Progress, Done) to the total of tasks + + +

+ +
+
+

+ Issues + + ⓘ + Percentage of Items that are Issues + +

+ +
+
+

+ Issues with type + + ⓘ + Percentage of Issues that have a type assigned + +

+ +
+ {data.project.has_iterations && ( +
+

+ Items with iteration + + ⓘ + Percentage of Items that are assigned to an iteration + +

+ +
+ )} +
+
+ ); +} + +export default ProjectsPageSummarySection; \ No newline at end of file diff --git a/src/summarySections/pullRequestsPageSummarySection.jsx b/src/summarySections/pullRequestsPageSummarySection.jsx new file mode 100644 index 0000000..94e1e13 --- /dev/null +++ b/src/summarySections/pullRequestsPageSummarySection.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import GaugeChart from '../components/gaugeChart.jsx'; +import RadarPieToggle from '../components/radarPieToggle.jsx'; + +function PullRequestsPageSummarySection({ + radarDataCreated, + pieDataCreated, + radarDataMerged, + pieDataMerged, + percentageMerged, + percentatgeReviewed, + percentageMergesPR, + features +}) { + return ( +
+

Summary

+
+
+ +
+
+ +
+
+
+
+

+ Pull requests merged + + ⓘ + + Percentage of pull requests that are merged relative to the total number of pull requests that are not closed + + +

+ +
+
+

+ Pull requests reviewed + + ⓘ + + Percentage of pull requests merged by another user that is not the author relative to the total number of merged pull requests + + +

+ +
+
+

+ Merges from pull requests + + ⓘ + + Percentage of merges that are from pull requests + + +

+ +
+
+
+ ); +} + +export default PullRequestsPageSummarySection; From ef0bb31a3c2111cc244cc4e55e210a9b81cc51a8 Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 18 Jun 2025 18:44:53 +0200 Subject: [PATCH 19/20] Renombrat per a consistencia --- src/App.jsx | 4 ++-- ...ion.jsx => individualPageHistoricalSection.jsx} | 0 ...ection.jsx => individualPageMetricsSection.jsx} | 0 src/pages/{individual.jsx => individualPage.jsx} | 14 +++++++------- ...ion.jsx => individualPageStatisticsSection.jsx} | 0 5 files changed, 9 insertions(+), 9 deletions(-) rename src/historicalSections/{individualHistoricalSection.jsx => individualPageHistoricalSection.jsx} (100%) rename src/metricsSections/{individualMetricsSection.jsx => individualPageMetricsSection.jsx} (100%) rename src/pages/{individual.jsx => individualPage.jsx} (97%) rename src/summarySections/{individualStatisticsSection.jsx => individualPageStatisticsSection.jsx} (100%) diff --git a/src/App.jsx b/src/App.jsx index 6fd9c1d..2567bf6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,7 +4,7 @@ import Index from "./pages/index"; import PullRequestsPage from "./pages/PullRequestsPage"; import CommitsPage from "./pages/commitsPage"; import IssuesPage from "./pages/issuesPage"; -import Individual from "./pages/individual"; +import IndividualPage from "./pages/individualPage"; import ProjectsPage from "./pages/projectsPage"; import { HashRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; @@ -88,7 +88,7 @@ function App() { } /> } /> } /> - } /> + } /> ); diff --git a/src/historicalSections/individualHistoricalSection.jsx b/src/historicalSections/individualPageHistoricalSection.jsx similarity index 100% rename from src/historicalSections/individualHistoricalSection.jsx rename to src/historicalSections/individualPageHistoricalSection.jsx diff --git a/src/metricsSections/individualMetricsSection.jsx b/src/metricsSections/individualPageMetricsSection.jsx similarity index 100% rename from src/metricsSections/individualMetricsSection.jsx rename to src/metricsSections/individualPageMetricsSection.jsx diff --git a/src/pages/individual.jsx b/src/pages/individualPage.jsx similarity index 97% rename from src/pages/individual.jsx rename to src/pages/individualPage.jsx index b19dfda..d9c75ea 100644 --- a/src/pages/individual.jsx +++ b/src/pages/individualPage.jsx @@ -6,9 +6,9 @@ import usePersistentStateSession from '../components/usePersistentStateSession. import usePersistentState from '../components/usePersistentState.jsx'; import HistoricalToggle from '../components/historicalToggle.jsx'; import DateRangeSelector from '../components/dateRangeSelector.jsx'; -import IndividualStatsSection from '../summarySections/individualStatisticsSection.jsx'; -import IndividualMetricsSection from '../metricsSections/individualMetricsSection.jsx'; -import IndividualHistoricSection from '../historicalSections/individualHistoricalSection.jsx'; +import IndividualStatsSection from '../summarySections/individualPageStatisticsSection.jsx'; +import IndividualMetricsSection from '../metricsSections/individualPageMetricsSection.jsx'; +import IndividualHistoricSection from '../historicalSections/individualPageHistoricalSection.jsx'; import { transformCommitsDataForUser, @@ -40,10 +40,10 @@ import { transformTasksStandardDataForUser, } from '../domain/projects.jsx' import { - filterHistoricData, - truncateName } + filterHistoricData, } from '../domain/utils.jsx'; -function Individual({ data, historicData, features }) { + +function IndividualPage({ data, historicData, features }) { const [selectedUser, setSelectedUser] = usePersistentState("selectedUser",Object.keys(data.avatars)[0]); const [showHistorical, setShowHistorical] = usePersistentStateSession('showHistoricalIndividual', false); const [dateRange, setDateRange] = usePersistentStateSession('dateRangeIndividual', "7"); @@ -199,4 +199,4 @@ function Individual({ data, historicData, features }) { ); } -export default Individual; +export default IndividualPage; diff --git a/src/summarySections/individualStatisticsSection.jsx b/src/summarySections/individualPageStatisticsSection.jsx similarity index 100% rename from src/summarySections/individualStatisticsSection.jsx rename to src/summarySections/individualPageStatisticsSection.jsx From bfef2291541417f50eee14cea34693abd4695559 Mon Sep 17 00:00:00 2001 From: DidacLinares Date: Wed, 18 Jun 2025 18:50:38 +0200 Subject: [PATCH 20/20] Corretgit test --- src/tests/pullRequests.test.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/tests/pullRequests.test.jsx b/src/tests/pullRequests.test.jsx index deb8ce2..8c8c005 100644 --- a/src/tests/pullRequests.test.jsx +++ b/src/tests/pullRequests.test.jsx @@ -139,11 +139,14 @@ describe('pullRequests', () => { }); test('transformPRDataForAreaChart works as expected', () => { - const { xDataPRs, mergedPRs, openPRs } = transformPRDataForAreaChart(mockPRsData); + const { xDataPRs, areaPRData } = transformPRDataForAreaChart(mockPRsData); expect(xDataPRs).toEqual(['2025-06-01', '2025-06-02']); - expect(mergedPRs).toEqual([3, 5]); - expect(openPRs).toEqual([1, 1]); + expect(areaPRData).toEqual([ + { label: 'Merged', data: [3, 5], color: 'rgb(0, 255, 0)' }, + { label: 'Closed', data: [1, 1], color: 'orange' }, + { label: 'Open', data: [1, 1], color: 'rgb(255, 0, 0)' }, + ]); }); test('getPieDataPullRequestStatus returns correct pie data and colors', () => {