322 lines
9.1 KiB
TypeScript
322 lines
9.1 KiB
TypeScript
|
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<void> {
|
||
|
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<string> = 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<T extends Result>(
|
||
|
data: any[],
|
||
|
ignoreFn: (point: any) => boolean,
|
||
|
findFn: (result: T, point: any) => boolean,
|
||
|
undefinedFn: (point: any) => T,
|
||
|
incrementFn: (point: any) => number
|
||
|
): Promise<T[]> {
|
||
|
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<number> {
|
||
|
const result: MonthlyResult | undefined = results.find(
|
||
|
val => val.year === year && val.month === month
|
||
|
);
|
||
|
return result === undefined ? 0 : result.count;
|
||
|
}
|
||
|
|
||
|
if (require.main === module) {
|
||
|
entry();
|
||
|
}
|