@0xbigboss/gh-pulse-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache.cjs +312 -0
- package/dist/cache.d.cts +58 -0
- package/dist/cache.d.ts +58 -0
- package/dist/cache.js +303 -0
- package/dist/config.cjs +182 -0
- package/dist/config.d.cts +248 -0
- package/dist/config.d.ts +248 -0
- package/dist/config.js +172 -0
- package/dist/filters.cjs +16 -0
- package/dist/filters.d.cts +3 -0
- package/dist/filters.d.ts +3 -0
- package/dist/filters.js +12 -0
- package/dist/github.cjs +240 -0
- package/dist/github.d.cts +46 -0
- package/dist/github.d.ts +46 -0
- package/dist/github.js +235 -0
- package/dist/index.cjs +28 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/reports/context.cjs +8 -0
- package/dist/reports/context.d.cts +7 -0
- package/dist/reports/context.d.ts +7 -0
- package/dist/reports/context.js +5 -0
- package/dist/reports/exec.cjs +160 -0
- package/dist/reports/exec.d.cts +6 -0
- package/dist/reports/exec.d.ts +6 -0
- package/dist/reports/exec.js +157 -0
- package/dist/reports/index.cjs +21 -0
- package/dist/reports/index.d.cts +5 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/meta.cjs +15 -0
- package/dist/reports/meta.d.cts +12 -0
- package/dist/reports/meta.d.ts +12 -0
- package/dist/reports/meta.js +12 -0
- package/dist/reports/personal.cjs +90 -0
- package/dist/reports/personal.d.cts +8 -0
- package/dist/reports/personal.d.ts +8 -0
- package/dist/reports/personal.js +87 -0
- package/dist/reports/team.cjs +127 -0
- package/dist/reports/team.d.cts +6 -0
- package/dist/reports/team.d.ts +6 -0
- package/dist/reports/team.js +124 -0
- package/dist/reports/types.cjs +2 -0
- package/dist/reports/types.d.cts +144 -0
- package/dist/reports/types.d.ts +144 -0
- package/dist/reports/types.js +1 -0
- package/dist/reports/utils.cjs +71 -0
- package/dist/reports/utils.d.cts +6 -0
- package/dist/reports/utils.d.ts +6 -0
- package/dist/reports/utils.js +65 -0
- package/dist/repos.cjs +102 -0
- package/dist/repos.d.cts +12 -0
- package/dist/repos.d.ts +12 -0
- package/dist/repos.js +96 -0
- package/dist/sync.cjs +360 -0
- package/dist/sync.d.cts +24 -0
- package/dist/sync.d.ts +24 -0
- package/dist/sync.js +357 -0
- package/dist/team.cjs +45 -0
- package/dist/team.d.cts +10 -0
- package/dist/team.d.ts +10 -0
- package/dist/team.js +42 -0
- package/dist/time.cjs +153 -0
- package/dist/time.d.cts +13 -0
- package/dist/time.d.ts +13 -0
- package/dist/time.js +145 -0
- package/dist/types.cjs +2 -0
- package/dist/types.d.cts +133 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.js +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildExecReport = buildExecReport;
|
|
4
|
+
const utils_1 = require("./utils.cjs");
|
|
5
|
+
const context_1 = require("./context.cjs");
|
|
6
|
+
const filters_1 = require("../filters.cjs");
|
|
7
|
+
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
8
|
+
function buildExecReport(cache, options) {
|
|
9
|
+
const { pullRequests, commits } = (0, context_1.loadReportData)(cache, options.repos);
|
|
10
|
+
const now = options.meta.generated_at;
|
|
11
|
+
const exclude = options.excludeAuthors;
|
|
12
|
+
const teamMembers = options.teamMembers;
|
|
13
|
+
const prLookup = buildPullRequestLookup(pullRequests);
|
|
14
|
+
const events = cache
|
|
15
|
+
.getEvents({ since: options.timeRange.start, until: options.timeRange.end })
|
|
16
|
+
.filter((event) => options.repos.includes(event.repo));
|
|
17
|
+
const mergedEvents = events.filter((event) => event.type === 'pr_merged' && prAuthorAllowed(prLookup, event, exclude, options.excludeBots));
|
|
18
|
+
const mergedPrs = mergedEvents
|
|
19
|
+
.map((event) => prLookup.get(`${event.repo}#${event.pr_number}`))
|
|
20
|
+
.filter((pr) => Boolean(pr))
|
|
21
|
+
.filter((pr) => options.includeDrafts || !pr.draft);
|
|
22
|
+
const trendBuckets = buildWeeklyBuckets(options.timeRange.start, options.timeRange.end);
|
|
23
|
+
const velocityTrends = trendBuckets.map((bucket) => buildTrendPoint(cache, mergedEvents, prLookup, bucket));
|
|
24
|
+
const cycleTimeTrends = velocityTrends;
|
|
25
|
+
const repoHealth = buildRepoHealth(pullRequests, commits, options.repos, options.thresholds, now);
|
|
26
|
+
const highlights = buildHighlights(mergedPrs, options.thresholds.large_pr_lines, now);
|
|
27
|
+
const teamBreakdown = buildTeamBreakdown(teamMembers, pullRequests, commits, events, exclude, options.excludeBots, options.timeRange.start, options.timeRange.end);
|
|
28
|
+
return {
|
|
29
|
+
type: 'exec',
|
|
30
|
+
meta: options.meta,
|
|
31
|
+
velocity_trends: velocityTrends,
|
|
32
|
+
cycle_time_trends: cycleTimeTrends,
|
|
33
|
+
repo_health: repoHealth,
|
|
34
|
+
highlights,
|
|
35
|
+
team_breakdown: teamBreakdown,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function buildWeeklyBuckets(start, end) {
|
|
39
|
+
const buckets = [];
|
|
40
|
+
let cursor = start;
|
|
41
|
+
while (cursor < end) {
|
|
42
|
+
const bucketEnd = Math.min(cursor + WEEK_MS, end);
|
|
43
|
+
buckets.push({ start: cursor, end: bucketEnd });
|
|
44
|
+
cursor = bucketEnd;
|
|
45
|
+
}
|
|
46
|
+
return buckets;
|
|
47
|
+
}
|
|
48
|
+
function buildTrendPoint(cache, mergedEvents, prLookup, bucket) {
|
|
49
|
+
const bucketEvents = mergedEvents.filter((event) => event.type === 'pr_merged' &&
|
|
50
|
+
event.merged_at >= bucket.start &&
|
|
51
|
+
event.merged_at <= bucket.end);
|
|
52
|
+
const cycleTimes = bucketEvents
|
|
53
|
+
.map((event) => prLookup.get(`${event.repo}#${event.pr_number}`))
|
|
54
|
+
.filter((pr) => Boolean(pr))
|
|
55
|
+
.map((pr) => (0, utils_1.computeCycleTime)(pr, cache.getEventsForPr(pr.repo, pr.number)))
|
|
56
|
+
.map((metrics) => metrics?.total_time)
|
|
57
|
+
.filter((value) => typeof value === 'number' && value > 0);
|
|
58
|
+
return {
|
|
59
|
+
bucket_start: bucket.start,
|
|
60
|
+
bucket_end: bucket.end,
|
|
61
|
+
prs_merged: bucketEvents.length,
|
|
62
|
+
cycle_time_p50: (0, utils_1.percentile)(cycleTimes, 50),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function buildRepoHealth(pullRequests, commits, repos, thresholds, now) {
|
|
66
|
+
return repos.map((repo) => {
|
|
67
|
+
const prActivity = pullRequests.filter((pr) => pr.repo === repo).map((pr) => pr.updated_at);
|
|
68
|
+
const commitActivity = commits
|
|
69
|
+
.filter((commit) => commit.repo === repo)
|
|
70
|
+
.map((commit) => commit.committed_at);
|
|
71
|
+
const latest = maxTimestamp([...prActivity, ...commitActivity]);
|
|
72
|
+
const ageDays = latest ? Math.floor((now - latest) / (24 * 60 * 60 * 1000)) : null;
|
|
73
|
+
let status = 'dormant';
|
|
74
|
+
if (ageDays === null) {
|
|
75
|
+
status = 'dormant';
|
|
76
|
+
}
|
|
77
|
+
else if (ageDays <= thresholds.stale_days) {
|
|
78
|
+
status = 'active';
|
|
79
|
+
}
|
|
80
|
+
else if (ageDays <= thresholds.stuck_days) {
|
|
81
|
+
status = 'stale';
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
status = 'dormant';
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
repo,
|
|
88
|
+
status,
|
|
89
|
+
last_activity_at: latest,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function buildHighlights(mergedPrs, largePrThreshold, now) {
|
|
94
|
+
return mergedPrs
|
|
95
|
+
.map((pr) => {
|
|
96
|
+
const reasons = [];
|
|
97
|
+
if (pr.additions + pr.deletions >= largePrThreshold) {
|
|
98
|
+
reasons.push(`large change (${pr.additions + pr.deletions} lines)`);
|
|
99
|
+
}
|
|
100
|
+
const title = pr.title.toLowerCase();
|
|
101
|
+
if (title.startsWith('feat:') || title.startsWith('fix:')) {
|
|
102
|
+
reasons.push('conventional commit title');
|
|
103
|
+
}
|
|
104
|
+
if (pr.labels.length > 0) {
|
|
105
|
+
reasons.push(`labels: ${pr.labels.slice(0, 3).join(', ')}`);
|
|
106
|
+
}
|
|
107
|
+
if (reasons.length === 0) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
pr: (0, utils_1.buildPullRequestSummary)(pr, now),
|
|
112
|
+
reasons,
|
|
113
|
+
};
|
|
114
|
+
})
|
|
115
|
+
.filter((entry) => Boolean(entry));
|
|
116
|
+
}
|
|
117
|
+
function buildTeamBreakdown(teamMembers, pullRequests, commits, events, exclude, excludeBots, since, until) {
|
|
118
|
+
const reviewEvents = events.filter((event) => event.type === 'review_submitted');
|
|
119
|
+
return [...teamMembers]
|
|
120
|
+
.filter((member) => !(0, filters_1.isExcludedAuthor)(member, exclude, excludeBots))
|
|
121
|
+
.map((member) => {
|
|
122
|
+
const authored = pullRequests.filter((pr) => pr.author === member &&
|
|
123
|
+
pr.created_at >= since &&
|
|
124
|
+
pr.created_at <= until &&
|
|
125
|
+
!(0, filters_1.isExcludedAuthor)(pr.author, exclude, excludeBots));
|
|
126
|
+
const reviewed = reviewEvents.filter((event) => event.type === 'review_submitted' &&
|
|
127
|
+
event.reviewer === member &&
|
|
128
|
+
!(0, filters_1.isExcludedAuthor)(event.reviewer, exclude, excludeBots));
|
|
129
|
+
const memberCommits = commits.filter((commit) => commit.author === member &&
|
|
130
|
+
commit.committed_at >= since &&
|
|
131
|
+
commit.committed_at <= until &&
|
|
132
|
+
!(0, filters_1.isExcludedAuthor)(commit.author, exclude, excludeBots));
|
|
133
|
+
const linesChanged = memberCommits.reduce((total, commit) => total + commit.additions + commit.deletions, 0);
|
|
134
|
+
return {
|
|
135
|
+
user: member,
|
|
136
|
+
prs_authored: authored.length,
|
|
137
|
+
prs_reviewed: reviewed.length,
|
|
138
|
+
commits: memberCommits.length,
|
|
139
|
+
lines_changed: linesChanged,
|
|
140
|
+
};
|
|
141
|
+
})
|
|
142
|
+
.toSorted((a, b) => a.user.localeCompare(b.user));
|
|
143
|
+
}
|
|
144
|
+
function prAuthorAllowed(lookup, event, exclude, excludeBots) {
|
|
145
|
+
const pr = lookup.get(`${event.repo}#${event.pr_number}`);
|
|
146
|
+
if (!pr) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
return !(0, filters_1.isExcludedAuthor)(pr.author, exclude, excludeBots);
|
|
150
|
+
}
|
|
151
|
+
function maxTimestamp(values) {
|
|
152
|
+
const filtered = values.filter((value) => typeof value === 'number');
|
|
153
|
+
if (filtered.length === 0) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return Math.max(...filtered);
|
|
157
|
+
}
|
|
158
|
+
function buildPullRequestLookup(pullRequests) {
|
|
159
|
+
return new Map(pullRequests.map((pr) => [`${pr.repo}#${pr.number}`, pr]));
|
|
160
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Cache } from "../cache.cjs";
|
|
2
|
+
import type { ExecReport, ReportInputs } from "./types.cjs";
|
|
3
|
+
export interface ExecReportOptions extends ReportInputs {
|
|
4
|
+
meta: ExecReport['meta'];
|
|
5
|
+
}
|
|
6
|
+
export declare function buildExecReport(cache: Cache, options: ExecReportOptions): ExecReport;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Cache } from "../cache.js";
|
|
2
|
+
import type { ExecReport, ReportInputs } from "./types.js";
|
|
3
|
+
export interface ExecReportOptions extends ReportInputs {
|
|
4
|
+
meta: ExecReport['meta'];
|
|
5
|
+
}
|
|
6
|
+
export declare function buildExecReport(cache: Cache, options: ExecReportOptions): ExecReport;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { buildPullRequestSummary, computeCycleTime, percentile } from "./utils.js";
|
|
2
|
+
import { loadReportData } from "./context.js";
|
|
3
|
+
import { isExcludedAuthor } from "../filters.js";
|
|
4
|
+
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
5
|
+
export function buildExecReport(cache, options) {
|
|
6
|
+
const { pullRequests, commits } = loadReportData(cache, options.repos);
|
|
7
|
+
const now = options.meta.generated_at;
|
|
8
|
+
const exclude = options.excludeAuthors;
|
|
9
|
+
const teamMembers = options.teamMembers;
|
|
10
|
+
const prLookup = buildPullRequestLookup(pullRequests);
|
|
11
|
+
const events = cache
|
|
12
|
+
.getEvents({ since: options.timeRange.start, until: options.timeRange.end })
|
|
13
|
+
.filter((event) => options.repos.includes(event.repo));
|
|
14
|
+
const mergedEvents = events.filter((event) => event.type === 'pr_merged' && prAuthorAllowed(prLookup, event, exclude, options.excludeBots));
|
|
15
|
+
const mergedPrs = mergedEvents
|
|
16
|
+
.map((event) => prLookup.get(`${event.repo}#${event.pr_number}`))
|
|
17
|
+
.filter((pr) => Boolean(pr))
|
|
18
|
+
.filter((pr) => options.includeDrafts || !pr.draft);
|
|
19
|
+
const trendBuckets = buildWeeklyBuckets(options.timeRange.start, options.timeRange.end);
|
|
20
|
+
const velocityTrends = trendBuckets.map((bucket) => buildTrendPoint(cache, mergedEvents, prLookup, bucket));
|
|
21
|
+
const cycleTimeTrends = velocityTrends;
|
|
22
|
+
const repoHealth = buildRepoHealth(pullRequests, commits, options.repos, options.thresholds, now);
|
|
23
|
+
const highlights = buildHighlights(mergedPrs, options.thresholds.large_pr_lines, now);
|
|
24
|
+
const teamBreakdown = buildTeamBreakdown(teamMembers, pullRequests, commits, events, exclude, options.excludeBots, options.timeRange.start, options.timeRange.end);
|
|
25
|
+
return {
|
|
26
|
+
type: 'exec',
|
|
27
|
+
meta: options.meta,
|
|
28
|
+
velocity_trends: velocityTrends,
|
|
29
|
+
cycle_time_trends: cycleTimeTrends,
|
|
30
|
+
repo_health: repoHealth,
|
|
31
|
+
highlights,
|
|
32
|
+
team_breakdown: teamBreakdown,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function buildWeeklyBuckets(start, end) {
|
|
36
|
+
const buckets = [];
|
|
37
|
+
let cursor = start;
|
|
38
|
+
while (cursor < end) {
|
|
39
|
+
const bucketEnd = Math.min(cursor + WEEK_MS, end);
|
|
40
|
+
buckets.push({ start: cursor, end: bucketEnd });
|
|
41
|
+
cursor = bucketEnd;
|
|
42
|
+
}
|
|
43
|
+
return buckets;
|
|
44
|
+
}
|
|
45
|
+
function buildTrendPoint(cache, mergedEvents, prLookup, bucket) {
|
|
46
|
+
const bucketEvents = mergedEvents.filter((event) => event.type === 'pr_merged' &&
|
|
47
|
+
event.merged_at >= bucket.start &&
|
|
48
|
+
event.merged_at <= bucket.end);
|
|
49
|
+
const cycleTimes = bucketEvents
|
|
50
|
+
.map((event) => prLookup.get(`${event.repo}#${event.pr_number}`))
|
|
51
|
+
.filter((pr) => Boolean(pr))
|
|
52
|
+
.map((pr) => computeCycleTime(pr, cache.getEventsForPr(pr.repo, pr.number)))
|
|
53
|
+
.map((metrics) => metrics?.total_time)
|
|
54
|
+
.filter((value) => typeof value === 'number' && value > 0);
|
|
55
|
+
return {
|
|
56
|
+
bucket_start: bucket.start,
|
|
57
|
+
bucket_end: bucket.end,
|
|
58
|
+
prs_merged: bucketEvents.length,
|
|
59
|
+
cycle_time_p50: percentile(cycleTimes, 50),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function buildRepoHealth(pullRequests, commits, repos, thresholds, now) {
|
|
63
|
+
return repos.map((repo) => {
|
|
64
|
+
const prActivity = pullRequests.filter((pr) => pr.repo === repo).map((pr) => pr.updated_at);
|
|
65
|
+
const commitActivity = commits
|
|
66
|
+
.filter((commit) => commit.repo === repo)
|
|
67
|
+
.map((commit) => commit.committed_at);
|
|
68
|
+
const latest = maxTimestamp([...prActivity, ...commitActivity]);
|
|
69
|
+
const ageDays = latest ? Math.floor((now - latest) / (24 * 60 * 60 * 1000)) : null;
|
|
70
|
+
let status = 'dormant';
|
|
71
|
+
if (ageDays === null) {
|
|
72
|
+
status = 'dormant';
|
|
73
|
+
}
|
|
74
|
+
else if (ageDays <= thresholds.stale_days) {
|
|
75
|
+
status = 'active';
|
|
76
|
+
}
|
|
77
|
+
else if (ageDays <= thresholds.stuck_days) {
|
|
78
|
+
status = 'stale';
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
status = 'dormant';
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
repo,
|
|
85
|
+
status,
|
|
86
|
+
last_activity_at: latest,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function buildHighlights(mergedPrs, largePrThreshold, now) {
|
|
91
|
+
return mergedPrs
|
|
92
|
+
.map((pr) => {
|
|
93
|
+
const reasons = [];
|
|
94
|
+
if (pr.additions + pr.deletions >= largePrThreshold) {
|
|
95
|
+
reasons.push(`large change (${pr.additions + pr.deletions} lines)`);
|
|
96
|
+
}
|
|
97
|
+
const title = pr.title.toLowerCase();
|
|
98
|
+
if (title.startsWith('feat:') || title.startsWith('fix:')) {
|
|
99
|
+
reasons.push('conventional commit title');
|
|
100
|
+
}
|
|
101
|
+
if (pr.labels.length > 0) {
|
|
102
|
+
reasons.push(`labels: ${pr.labels.slice(0, 3).join(', ')}`);
|
|
103
|
+
}
|
|
104
|
+
if (reasons.length === 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
pr: buildPullRequestSummary(pr, now),
|
|
109
|
+
reasons,
|
|
110
|
+
};
|
|
111
|
+
})
|
|
112
|
+
.filter((entry) => Boolean(entry));
|
|
113
|
+
}
|
|
114
|
+
function buildTeamBreakdown(teamMembers, pullRequests, commits, events, exclude, excludeBots, since, until) {
|
|
115
|
+
const reviewEvents = events.filter((event) => event.type === 'review_submitted');
|
|
116
|
+
return [...teamMembers]
|
|
117
|
+
.filter((member) => !isExcludedAuthor(member, exclude, excludeBots))
|
|
118
|
+
.map((member) => {
|
|
119
|
+
const authored = pullRequests.filter((pr) => pr.author === member &&
|
|
120
|
+
pr.created_at >= since &&
|
|
121
|
+
pr.created_at <= until &&
|
|
122
|
+
!isExcludedAuthor(pr.author, exclude, excludeBots));
|
|
123
|
+
const reviewed = reviewEvents.filter((event) => event.type === 'review_submitted' &&
|
|
124
|
+
event.reviewer === member &&
|
|
125
|
+
!isExcludedAuthor(event.reviewer, exclude, excludeBots));
|
|
126
|
+
const memberCommits = commits.filter((commit) => commit.author === member &&
|
|
127
|
+
commit.committed_at >= since &&
|
|
128
|
+
commit.committed_at <= until &&
|
|
129
|
+
!isExcludedAuthor(commit.author, exclude, excludeBots));
|
|
130
|
+
const linesChanged = memberCommits.reduce((total, commit) => total + commit.additions + commit.deletions, 0);
|
|
131
|
+
return {
|
|
132
|
+
user: member,
|
|
133
|
+
prs_authored: authored.length,
|
|
134
|
+
prs_reviewed: reviewed.length,
|
|
135
|
+
commits: memberCommits.length,
|
|
136
|
+
lines_changed: linesChanged,
|
|
137
|
+
};
|
|
138
|
+
})
|
|
139
|
+
.toSorted((a, b) => a.user.localeCompare(b.user));
|
|
140
|
+
}
|
|
141
|
+
function prAuthorAllowed(lookup, event, exclude, excludeBots) {
|
|
142
|
+
const pr = lookup.get(`${event.repo}#${event.pr_number}`);
|
|
143
|
+
if (!pr) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
return !isExcludedAuthor(pr.author, exclude, excludeBots);
|
|
147
|
+
}
|
|
148
|
+
function maxTimestamp(values) {
|
|
149
|
+
const filtered = values.filter((value) => typeof value === 'number');
|
|
150
|
+
if (filtered.length === 0) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return Math.max(...filtered);
|
|
154
|
+
}
|
|
155
|
+
function buildPullRequestLookup(pullRequests) {
|
|
156
|
+
return new Map(pullRequests.map((pr) => [`${pr.repo}#${pr.number}`, pr]));
|
|
157
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types.cjs"), exports);
|
|
18
|
+
__exportStar(require("./personal.cjs"), exports);
|
|
19
|
+
__exportStar(require("./team.cjs"), exports);
|
|
20
|
+
__exportStar(require("./exec.cjs"), exports);
|
|
21
|
+
__exportStar(require("./meta.cjs"), exports);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildReportMeta = buildReportMeta;
|
|
4
|
+
function buildReportMeta(options) {
|
|
5
|
+
const generatedAt = options.now ? options.now() : Date.now();
|
|
6
|
+
return {
|
|
7
|
+
audience: options.audience,
|
|
8
|
+
period_label: options.periodLabel,
|
|
9
|
+
start: options.start,
|
|
10
|
+
end: options.end,
|
|
11
|
+
generated_at: generatedAt,
|
|
12
|
+
repos: options.repos,
|
|
13
|
+
data_freshness: options.cache.getFreshness(options.repos),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Cache } from "../cache.cjs";
|
|
2
|
+
import type { RepoFullName, Timestamp } from "../types.cjs";
|
|
3
|
+
import type { ReportAudience, ReportMeta } from "./types.cjs";
|
|
4
|
+
export declare function buildReportMeta(options: {
|
|
5
|
+
audience: ReportAudience;
|
|
6
|
+
repos: RepoFullName[];
|
|
7
|
+
periodLabel: string;
|
|
8
|
+
start: Timestamp;
|
|
9
|
+
end: Timestamp;
|
|
10
|
+
cache: Cache;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
}): ReportMeta;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Cache } from "../cache.js";
|
|
2
|
+
import type { RepoFullName, Timestamp } from "../types.js";
|
|
3
|
+
import type { ReportAudience, ReportMeta } from "./types.js";
|
|
4
|
+
export declare function buildReportMeta(options: {
|
|
5
|
+
audience: ReportAudience;
|
|
6
|
+
repos: RepoFullName[];
|
|
7
|
+
periodLabel: string;
|
|
8
|
+
start: Timestamp;
|
|
9
|
+
end: Timestamp;
|
|
10
|
+
cache: Cache;
|
|
11
|
+
now?: () => number;
|
|
12
|
+
}): ReportMeta;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function buildReportMeta(options) {
|
|
2
|
+
const generatedAt = options.now ? options.now() : Date.now();
|
|
3
|
+
return {
|
|
4
|
+
audience: options.audience,
|
|
5
|
+
period_label: options.periodLabel,
|
|
6
|
+
start: options.start,
|
|
7
|
+
end: options.end,
|
|
8
|
+
generated_at: generatedAt,
|
|
9
|
+
repos: options.repos,
|
|
10
|
+
data_freshness: options.cache.getFreshness(options.repos),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildPersonalReport = buildPersonalReport;
|
|
4
|
+
const utils_1 = require("./utils.cjs");
|
|
5
|
+
const context_1 = require("./context.cjs");
|
|
6
|
+
const filters_1 = require("../filters.cjs");
|
|
7
|
+
function buildPersonalReport(cache, options) {
|
|
8
|
+
const { pullRequests, commits } = (0, context_1.loadReportData)(cache, options.repos);
|
|
9
|
+
const now = options.meta.generated_at;
|
|
10
|
+
const exclude = options.excludeAuthors;
|
|
11
|
+
const prLookup = buildPullRequestLookup(pullRequests);
|
|
12
|
+
const events = cache
|
|
13
|
+
.getEvents({ since: options.timeRange.start, until: options.timeRange.end })
|
|
14
|
+
.filter((event) => options.repos.includes(event.repo));
|
|
15
|
+
const authoredPrs = pullRequests.filter((pr) => pr.author === options.user && !(0, filters_1.isExcludedAuthor)(pr.author, exclude, options.excludeBots));
|
|
16
|
+
const authoredSummaries = authoredPrs
|
|
17
|
+
.filter((pr) => pr.created_at >= options.timeRange.start && pr.created_at <= options.timeRange.end)
|
|
18
|
+
.map((pr) => (0, utils_1.buildPullRequestSummary)(pr, now));
|
|
19
|
+
const openWork = authoredPrs
|
|
20
|
+
.filter((pr) => pr.state === 'open')
|
|
21
|
+
.filter((pr) => options.includeDrafts || !pr.draft)
|
|
22
|
+
.map((pr) => (0, utils_1.buildPullRequestSummary)(pr, now));
|
|
23
|
+
const requested = pullRequests
|
|
24
|
+
.filter((pr) => pr.state === 'open')
|
|
25
|
+
.filter((pr) => options.includeDrafts || !pr.draft)
|
|
26
|
+
.filter((pr) => pr.requested_reviewers.includes(options.user))
|
|
27
|
+
.filter((pr) => !(0, filters_1.isExcludedAuthor)(pr.author, exclude, options.excludeBots))
|
|
28
|
+
.map((pr) => (0, utils_1.buildPullRequestSummary)(pr, now));
|
|
29
|
+
const teamMembers = new Set(options.teamMembers);
|
|
30
|
+
const teamPrs = pullRequests
|
|
31
|
+
.filter((pr) => pr.state === 'open')
|
|
32
|
+
.filter((pr) => options.includeDrafts || !pr.draft)
|
|
33
|
+
.filter((pr) => pr.author !== options.user)
|
|
34
|
+
.filter((pr) => teamMembers.has(pr.author))
|
|
35
|
+
.filter((pr) => !(0, filters_1.isExcludedAuthor)(pr.author, exclude, options.excludeBots))
|
|
36
|
+
.map((pr) => (0, utils_1.buildPullRequestSummary)(pr, now));
|
|
37
|
+
const commitsForUser = commits
|
|
38
|
+
.filter((commit) => commit.author === options.user)
|
|
39
|
+
.filter((commit) => commit.committed_at >= options.timeRange.start)
|
|
40
|
+
.filter((commit) => commit.committed_at <= options.timeRange.end)
|
|
41
|
+
.filter((commit) => !(0, filters_1.isExcludedAuthor)(commit.author, exclude, options.excludeBots));
|
|
42
|
+
const commitSummaries = commitsForUser.map((commit) => ({
|
|
43
|
+
repo: commit.repo,
|
|
44
|
+
sha: commit.sha,
|
|
45
|
+
author: commit.author,
|
|
46
|
+
committed_at: commit.committed_at,
|
|
47
|
+
message: commit.message,
|
|
48
|
+
additions: commit.additions,
|
|
49
|
+
deletions: commit.deletions,
|
|
50
|
+
files_changed: commit.files_changed,
|
|
51
|
+
}));
|
|
52
|
+
const summary = buildPersonalSummary(events, prLookup, options.user, commitsForUser);
|
|
53
|
+
return {
|
|
54
|
+
type: options.meta.audience === 'individual' ? 'individual' : 'personal',
|
|
55
|
+
user: options.user,
|
|
56
|
+
meta: options.meta,
|
|
57
|
+
summary,
|
|
58
|
+
review_debt: {
|
|
59
|
+
requested,
|
|
60
|
+
team_prs: teamPrs,
|
|
61
|
+
},
|
|
62
|
+
open_work: {
|
|
63
|
+
open_prs: openWork,
|
|
64
|
+
},
|
|
65
|
+
authored_prs: authoredSummaries,
|
|
66
|
+
commits_detail: commitSummaries,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function buildPersonalSummary(events, prLookup, user, commits) {
|
|
70
|
+
const opened = events.filter((event) => event.type === 'pr_opened' && event.author === user);
|
|
71
|
+
const merged = events.filter((event) => event.type === 'pr_merged' && prMatchesAuthor(prLookup, event.repo, event.pr_number, user));
|
|
72
|
+
const closed = events.filter((event) => event.type === 'pr_closed' && prMatchesAuthor(prLookup, event.repo, event.pr_number, user));
|
|
73
|
+
const reposTouched = new Set(commits.map((commit) => commit.repo));
|
|
74
|
+
const linesChanged = commits.reduce((total, commit) => total + commit.additions + commit.deletions, 0);
|
|
75
|
+
return {
|
|
76
|
+
prs_opened: opened.length,
|
|
77
|
+
prs_merged: merged.length,
|
|
78
|
+
prs_closed: closed.length,
|
|
79
|
+
commits: commits.length,
|
|
80
|
+
repos_touched: reposTouched.size,
|
|
81
|
+
lines_changed: linesChanged,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function prMatchesAuthor(lookup, repo, number, user) {
|
|
85
|
+
const pr = lookup.get(`${repo}#${number}`);
|
|
86
|
+
return pr ? pr.author === user : false;
|
|
87
|
+
}
|
|
88
|
+
function buildPullRequestLookup(pullRequests) {
|
|
89
|
+
return new Map(pullRequests.map((pr) => [`${pr.repo}#${pr.number}`, pr]));
|
|
90
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Cache } from "../cache.cjs";
|
|
2
|
+
import type { GitHubUsername } from "../types.cjs";
|
|
3
|
+
import type { ReportInputs, PersonalReport } from "./types.cjs";
|
|
4
|
+
export interface PersonalReportOptions extends ReportInputs {
|
|
5
|
+
user: GitHubUsername;
|
|
6
|
+
meta: PersonalReport['meta'];
|
|
7
|
+
}
|
|
8
|
+
export declare function buildPersonalReport(cache: Cache, options: PersonalReportOptions): PersonalReport;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Cache } from "../cache.js";
|
|
2
|
+
import type { GitHubUsername } from "../types.js";
|
|
3
|
+
import type { ReportInputs, PersonalReport } from "./types.js";
|
|
4
|
+
export interface PersonalReportOptions extends ReportInputs {
|
|
5
|
+
user: GitHubUsername;
|
|
6
|
+
meta: PersonalReport['meta'];
|
|
7
|
+
}
|
|
8
|
+
export declare function buildPersonalReport(cache: Cache, options: PersonalReportOptions): PersonalReport;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { buildPullRequestSummary } from "./utils.js";
|
|
2
|
+
import { loadReportData } from "./context.js";
|
|
3
|
+
import { isExcludedAuthor } from "../filters.js";
|
|
4
|
+
export function buildPersonalReport(cache, options) {
|
|
5
|
+
const { pullRequests, commits } = loadReportData(cache, options.repos);
|
|
6
|
+
const now = options.meta.generated_at;
|
|
7
|
+
const exclude = options.excludeAuthors;
|
|
8
|
+
const prLookup = buildPullRequestLookup(pullRequests);
|
|
9
|
+
const events = cache
|
|
10
|
+
.getEvents({ since: options.timeRange.start, until: options.timeRange.end })
|
|
11
|
+
.filter((event) => options.repos.includes(event.repo));
|
|
12
|
+
const authoredPrs = pullRequests.filter((pr) => pr.author === options.user && !isExcludedAuthor(pr.author, exclude, options.excludeBots));
|
|
13
|
+
const authoredSummaries = authoredPrs
|
|
14
|
+
.filter((pr) => pr.created_at >= options.timeRange.start && pr.created_at <= options.timeRange.end)
|
|
15
|
+
.map((pr) => buildPullRequestSummary(pr, now));
|
|
16
|
+
const openWork = authoredPrs
|
|
17
|
+
.filter((pr) => pr.state === 'open')
|
|
18
|
+
.filter((pr) => options.includeDrafts || !pr.draft)
|
|
19
|
+
.map((pr) => buildPullRequestSummary(pr, now));
|
|
20
|
+
const requested = pullRequests
|
|
21
|
+
.filter((pr) => pr.state === 'open')
|
|
22
|
+
.filter((pr) => options.includeDrafts || !pr.draft)
|
|
23
|
+
.filter((pr) => pr.requested_reviewers.includes(options.user))
|
|
24
|
+
.filter((pr) => !isExcludedAuthor(pr.author, exclude, options.excludeBots))
|
|
25
|
+
.map((pr) => buildPullRequestSummary(pr, now));
|
|
26
|
+
const teamMembers = new Set(options.teamMembers);
|
|
27
|
+
const teamPrs = pullRequests
|
|
28
|
+
.filter((pr) => pr.state === 'open')
|
|
29
|
+
.filter((pr) => options.includeDrafts || !pr.draft)
|
|
30
|
+
.filter((pr) => pr.author !== options.user)
|
|
31
|
+
.filter((pr) => teamMembers.has(pr.author))
|
|
32
|
+
.filter((pr) => !isExcludedAuthor(pr.author, exclude, options.excludeBots))
|
|
33
|
+
.map((pr) => buildPullRequestSummary(pr, now));
|
|
34
|
+
const commitsForUser = commits
|
|
35
|
+
.filter((commit) => commit.author === options.user)
|
|
36
|
+
.filter((commit) => commit.committed_at >= options.timeRange.start)
|
|
37
|
+
.filter((commit) => commit.committed_at <= options.timeRange.end)
|
|
38
|
+
.filter((commit) => !isExcludedAuthor(commit.author, exclude, options.excludeBots));
|
|
39
|
+
const commitSummaries = commitsForUser.map((commit) => ({
|
|
40
|
+
repo: commit.repo,
|
|
41
|
+
sha: commit.sha,
|
|
42
|
+
author: commit.author,
|
|
43
|
+
committed_at: commit.committed_at,
|
|
44
|
+
message: commit.message,
|
|
45
|
+
additions: commit.additions,
|
|
46
|
+
deletions: commit.deletions,
|
|
47
|
+
files_changed: commit.files_changed,
|
|
48
|
+
}));
|
|
49
|
+
const summary = buildPersonalSummary(events, prLookup, options.user, commitsForUser);
|
|
50
|
+
return {
|
|
51
|
+
type: options.meta.audience === 'individual' ? 'individual' : 'personal',
|
|
52
|
+
user: options.user,
|
|
53
|
+
meta: options.meta,
|
|
54
|
+
summary,
|
|
55
|
+
review_debt: {
|
|
56
|
+
requested,
|
|
57
|
+
team_prs: teamPrs,
|
|
58
|
+
},
|
|
59
|
+
open_work: {
|
|
60
|
+
open_prs: openWork,
|
|
61
|
+
},
|
|
62
|
+
authored_prs: authoredSummaries,
|
|
63
|
+
commits_detail: commitSummaries,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function buildPersonalSummary(events, prLookup, user, commits) {
|
|
67
|
+
const opened = events.filter((event) => event.type === 'pr_opened' && event.author === user);
|
|
68
|
+
const merged = events.filter((event) => event.type === 'pr_merged' && prMatchesAuthor(prLookup, event.repo, event.pr_number, user));
|
|
69
|
+
const closed = events.filter((event) => event.type === 'pr_closed' && prMatchesAuthor(prLookup, event.repo, event.pr_number, user));
|
|
70
|
+
const reposTouched = new Set(commits.map((commit) => commit.repo));
|
|
71
|
+
const linesChanged = commits.reduce((total, commit) => total + commit.additions + commit.deletions, 0);
|
|
72
|
+
return {
|
|
73
|
+
prs_opened: opened.length,
|
|
74
|
+
prs_merged: merged.length,
|
|
75
|
+
prs_closed: closed.length,
|
|
76
|
+
commits: commits.length,
|
|
77
|
+
repos_touched: reposTouched.size,
|
|
78
|
+
lines_changed: linesChanged,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function prMatchesAuthor(lookup, repo, number, user) {
|
|
82
|
+
const pr = lookup.get(`${repo}#${number}`);
|
|
83
|
+
return pr ? pr.author === user : false;
|
|
84
|
+
}
|
|
85
|
+
function buildPullRequestLookup(pullRequests) {
|
|
86
|
+
return new Map(pullRequests.map((pr) => [`${pr.repo}#${pr.number}`, pr]));
|
|
87
|
+
}
|