diff --git a/libs/report/committers/src/lib/committers-monthly.spec.ts b/libs/report/committers/src/lib/committers-monthly.spec.ts new file mode 100644 index 0000000..8bf56c2 --- /dev/null +++ b/libs/report/committers/src/lib/committers-monthly.spec.ts @@ -0,0 +1,94 @@ +import { parseMonthly } from './parse-monthly'; + +describe('parseMonthly', () => { + const sampleCommits = [ + { + date: new Date('2023-01-15T12:00:00Z'), + committer: 'Marco', + commitHash: 'abc123', + }, + { + date: new Date('2023-01-20T12:00:00Z'), + committer: 'Marco', + commitHash: 'def456', + }, + { + date: new Date('2023-02-10T12:00:00Z'), + committer: 'George', + commitHash: 'ghi789', + }, + { + date: new Date('2023-02-15T12:00:00Z'), + committer: 'Marco', + commitHash: 'jkl012', + }, + ]; + + it('should group commits by month and count them per committer', () => { + const start = new Date('2023-01-01T00:00:00Z'); + const end = new Date('2023-03-01T00:00:00Z'); + const result = parseMonthly(start, end, sampleCommits); + + expect(result).toEqual([ + { + month: 'January 2023', + start, + end, + committers: { + Marco: 2, + }, + }, + { + month: 'February 2023', + start, + end, + committers: { + George: 1, + Marco: 1, + }, + }, + ]); + }); + + it('should return an empty array if there are no commits', () => { + const result = parseMonthly( + new Date('2023-01-01T00:00:00Z'), + new Date('2023-03-01T00:00:00Z'), + [] + ); + expect(result).toEqual([]); + }); + + it('should handle date ranges with no commits in certain months', () => { + const commits = [ + { + date: new Date('2023-03-10T12:00:00Z'), + committer: 'Greg', + commitHash: 'mno345', + }, + ]; + const start = new Date('2023-01-01T00:00:00Z'); + const end = new Date('2023-04-01T00:00:00Z'); + const result = parseMonthly(start, end, commits); + + expect(result).toEqual([ + { + month: 'March 2023', + start, + end, + committers: { + Greg: 1, + }, + }, + ]); + }); + + it('should handle an empty date range', () => { + const result = parseMonthly( + new Date('2023-01-01T00:00:00Z'), + new Date('2023-01-01T00:00:00Z'), + sampleCommits + ); + expect(result).toEqual([]); + }); +}); diff --git a/libs/report/committers/src/lib/committers.ts b/libs/report/committers/src/lib/committers.ts index 71f00d8..5f6462d 100644 --- a/libs/report/committers/src/lib/committers.ts +++ b/libs/report/committers/src/lib/committers.ts @@ -6,6 +6,11 @@ import { parseGitLogEntries } from './parse-git-log-entries'; import { getCommitterCounts } from './get-committer-counts'; import { ArgumentsCamelCase, CommandBuilder, CommandModule } from 'yargs'; import { CommitterCount } from './types'; +import { + outputMonthlyCommitters, + outputMonthlyCommittersJson, + parseMonthly, +} from './parse-monthly'; interface Options { beforeDate: string; @@ -13,6 +18,7 @@ interface Options { exclude: string[]; json: boolean; directory: string; + monthly: boolean; } export const reportCommittersCommand: CommandModule = { @@ -50,6 +56,13 @@ export const reportCommittersCommand: CommandModule = { required: false, string: true, }, + monthly: { + alias: 'm', + boolean: true, + describe: 'Break down by calendar month, rather than by committer.', + required: false, + default: false, + }, } as CommandBuilder, handler: run, }; @@ -80,6 +93,17 @@ async function run(args: ArgumentsCamelCase): Promise { return; } const entries = parseGitLogEntries(rawEntries); + + if (args.monthly) { + const monthly = parseMonthly(afterDate, beforeDate, entries); + if (args.json) { + outputMonthlyCommittersJson(monthly); + return; + } + outputMonthlyCommitters(monthly); + return; + } + const committerCounts = getCommitterCounts(entries); if (args.json) { outputCommittersJson(committerCounts); diff --git a/libs/report/committers/src/lib/parse-monthly.ts b/libs/report/committers/src/lib/parse-monthly.ts index a6de2ae..23e59af 100644 --- a/libs/report/committers/src/lib/parse-monthly.ts +++ b/libs/report/committers/src/lib/parse-monthly.ts @@ -1,24 +1,13 @@ -import { - eachMonthOfInterval, - format, - isWithinInterval, - max, - min, -} from 'date-fns'; -import { MonthlyData } from './types'; +import { eachMonthOfInterval, format, isWithinInterval } from 'date-fns'; +import { Commit, MonthlyData } from './types'; -export function parseMonthly( - startDate: Date, - endDate: Date, - entries: { commitHash: string; committer: string; date: string }[] -) { +export function parseMonthly(startDate: Date, endDate: Date, entries: Commit[]) { const monthly: MonthlyData[] = []; - const dates = [startDate, endDate]; - const ival = { - start: min(dates), - end: max(dates), - }; - const range = eachMonthOfInterval(ival); + const range = eachMonthOfInterval({ + start: startDate, + end: endDate, + }); + for (const idxr in range) { const idx = parseInt(idxr); if (idx + 1 >= range.length) { @@ -26,19 +15,15 @@ export function parseMonthly( } const [start, end] = [range[idx], range[idx + 1]]; const month: MonthlyData = { - name: format(start, 'LLLL yyyy'), - start, - end, + month: format(start, 'LLLL yyyy'), + start: startDate, + end: endDate, committers: {}, }; for (const rec of entries) { - if (isWithinInterval(new Date(rec.date), { start, end })) { - month.committers[rec.committer] = month.committers[rec.committer] || []; - month.committers[rec.committer].push({ - hash: rec.commitHash, - date: rec.date, - }); + if (isWithinInterval(rec.date, { start, end })) { + month.committers[rec.committer] = (month.committers[rec.committer] || 0) + 1; } } @@ -48,3 +33,20 @@ export function parseMonthly( } return monthly; } + +function monthlyDataHumanReadable(data: MonthlyData) { + return { + ...data, + start: format(data.start, 'yyyy-MM-dd'), + end: format(data.end, 'yyyy-MM-dd'), + }; +} + +export function outputMonthlyCommitters(commits: MonthlyData[]) { + const mapped = commits.map((c) => monthlyDataHumanReadable(c)); + console.table(mapped); +} + +export function outputMonthlyCommittersJson(commits: MonthlyData[]) { + console.log(JSON.stringify(commits, null, 2)); +} diff --git a/libs/report/committers/src/lib/types.ts b/libs/report/committers/src/lib/types.ts index ac69cd5..18ab597 100644 --- a/libs/report/committers/src/lib/types.ts +++ b/libs/report/committers/src/lib/types.ts @@ -9,10 +9,10 @@ export type CommitterCount = { }; export type MonthlyData = { - name: string; + month: string; start: Date; end: Date; - committers: Record; + committers: Record; }; export type SortedCommitterInfo = {