import {promises as fsp} from 'fs'; import {join} from 'path'; import {getIssues, getCommits, getMergeRequests} from './download'; export interface Result { count: number; } export interface MonthlyResult extends Result { month: number; year: number; } export interface Statistics { contributions: { authors: number; commits: number; }; issues: { closed: number; opened: number; }; lines: { added: number; removed: number; }; mergeRequests: { closed: number; opened: number; }; month: number; year: number; } async function entry(): Promise { const dataDirectory: string = join(__dirname, '../pages/data/'); await fsp.mkdir(dataDirectory, {recursive: true}); // Get the GitLab data from a local file or download them from the API. const commits: any[] = await getCommits(); const issues: any[] = await getIssues(); const mergeRequests: any[] = await getMergeRequests(); // Get the amount of commits made for each month. const commitsMade: MonthlyResult[] = await findData( commits, () => false, (result: MonthlyResult, commit: any) => result.year === new Date(commit.created_at).getFullYear() && result.month === new Date(commit.created_at).getMonth() + 1, (commit: any): MonthlyResult => ({ count: 1, year: new Date(commit.created_at).getFullYear(), month: new Date(commit.created_at).getMonth() + 1 }), () => 1 ); // Get the amount of lines added for each month. const linesAdded: MonthlyResult[] = await findData( commits, () => false, (result: MonthlyResult, commit: any) => result.year === new Date(commit.created_at).getFullYear() && result.month === new Date(commit.created_at).getMonth() + 1, (commit: any): MonthlyResult => ({ count: commit.stats.additions, year: new Date(commit.created_at).getFullYear(), month: new Date(commit.created_at).getMonth() + 1 }), (commit: any): number => commit.stats.additions ); // Get the amount of lines removed for each month. const linesRemoved: MonthlyResult[] = await findData( commits, () => false, (result: MonthlyResult, commit: any) => result.year === new Date(commit.created_at).getFullYear() && result.month === new Date(commit.created_at).getMonth() + 1, (commit: any): MonthlyResult => ({ count: commit.stats.deletions, year: new Date(commit.created_at).getFullYear(), month: new Date(commit.created_at).getMonth() + 1 }), (commit: any): number => commit.stats.deletions ); // Get the amount of issues opened for each month. const issuesOpened: MonthlyResult[] = await findData( issues, () => false, (result: MonthlyResult, issue: any) => result.year === new Date(issue.created_at).getFullYear() && result.month === new Date(issue.created_at).getMonth() + 1, (issue: any) => ({ count: 1, month: new Date(issue.created_at).getMonth() + 1, year: new Date(issue.created_at).getFullYear() }), () => 1 ); // Get the amount of issues closed for each month. const issuesClosed: MonthlyResult[] = await findData( issues, (issue: any) => issue.closed_at === null, (result: MonthlyResult, issue: any) => result.year === new Date(issue.closed_at).getFullYear() && result.month === new Date(issue.closed_at).getMonth() + 1, (issue: any) => ({ count: 1, month: new Date(issue.closed_at).getMonth() + 1, year: new Date(issue.closed_at).getFullYear() }), () => 1 ); // Get the amount of merge requests opened for each month. const mergeRequestsOpened: MonthlyResult[] = await findData( mergeRequests, () => false, (result: MonthlyResult, mergeRequest: any) => result.year === new Date(mergeRequest.created_at).getFullYear() && result.month === new Date(mergeRequest.created_at).getMonth() + 1, (mergeRequest: any) => ({ count: 1, month: new Date(mergeRequest.created_at).getMonth() + 1, year: new Date(mergeRequest.created_at).getFullYear() }), () => 1 ); // Get the amount of merge requests closed/merged for each month. const mergeRequestsClosed: MonthlyResult[] = await findData( mergeRequests, (mergeRequest: any) => mergeRequest.closed_at === null && mergeRequest.merged_at === null, (result: MonthlyResult, mergeRequest: any) => result.year === new Date( mergeRequest.closed_at === null ? mergeRequest.merged_at : mergeRequest.closed_at ).getFullYear() && result.month === new Date( mergeRequest.closed_at === null ? mergeRequest.merged_at : mergeRequest.closed_at ).getMonth() + 1, (mergeRequest: any) => ({ count: 1, month: new Date( mergeRequest.closed_at === null ? mergeRequest.merged_at : mergeRequest.closed_at ).getMonth() + 1, year: new Date( mergeRequest.closed_at === null ? mergeRequest.merged_at : mergeRequest.closed_at ).getFullYear() }), () => 1 ); // Create empty statistics for months that may not have any data. const statistics: Statistics[] = []; // Start at 2018 and end in the current year. for (let year = 2018; year < new Date().getFullYear() + 1; year++) { for (let month = 1; month < 13; month++) { // Tildes started in April of 2018, so don't create anything unless we're in or // after that month. if (year === 2018 && month < 4) { continue; } // Once we reach the current year and the next month, stop creating more data. if ( year === new Date().getFullYear() && month === new Date().getMonth() + 2 ) { break; } statistics.push({ contributions: { authors: 0, commits: 0 }, issues: { closed: 0, opened: 0 }, lines: { added: 0, removed: 0 }, mergeRequests: { closed: 0, opened: 0 }, month, year }); } } for (const statistic of statistics) { // Create a `Set()` that will hold all our unique author usernames. const uniqueContributors: Set = new Set(); for (const commit of commits) { if ( statistic.year === new Date(commit.created_at).getFullYear() && statistic.month === new Date(commit.created_at).getMonth() + 1 ) { uniqueContributors.add(commit.author_name); } } // Add all the data to the statistic. statistic.contributions.authors = uniqueContributors.size; statistic.issues.opened = await getResultCount( issuesOpened, statistic.year, statistic.month ); statistic.contributions.commits = await getResultCount( commitsMade, statistic.year, statistic.month ); statistic.issues.closed = await getResultCount( issuesClosed, statistic.year, statistic.month ); statistic.lines.added = await getResultCount( linesAdded, statistic.year, statistic.month ); statistic.lines.removed = await getResultCount( linesRemoved, statistic.year, statistic.month ); statistic.mergeRequests.closed = await getResultCount( mergeRequestsClosed, statistic.year, statistic.month ); statistic.mergeRequests.opened = await getResultCount( mergeRequestsOpened, statistic.year, statistic.month ); } // Sort the statistics ascending by year and month. statistics.sort((a, b) => a.year - b.year || a.month - b.month); // Write the statistics to file. await fsp.writeFile( join(dataDirectory, 'statistics.json'), JSON.stringify(statistics, null, 2) ); } // A generic function that takes in data and a set of functions to count something from // that data. export async function findData( data: any[], ignoreFn: (point: any) => boolean, findFn: (result: T, point: any) => boolean, undefinedFn: (point: any) => T, incrementFn: (point: any) => number ): Promise { const monthlies: T[] = []; for (const point of data) { // If the ignore function returns true we want to skip the current datapoint. if (ignoreFn(point)) { continue; } // Find the monthly that applies to this datapoint. const monthly: T | undefined = monthlies.find(result => findFn(result, point) ); // If it doesn't exist yet we want to create it. // Otherwise we want to increment the count. if (monthly === undefined) { monthlies.push(undefinedFn(point)); } else { monthly.count += incrementFn(point); } } return monthlies; } // A function that returns the count of a MonthlyResult in the specified year and month. async function getResultCount( results: MonthlyResult[], year: number, month: number ): Promise { const result: MonthlyResult | undefined = results.find( val => val.year === year && val.month === month ); return result === undefined ? 0 : result.count; } if (require.main === module) { entry(); }