Skip to content

Commit b120a3d

Browse files
authored
Reduce GitHub API calls in issues reporter (#1113)
2 parents dd19f93 + 06e8664 commit b120a3d

File tree

4 files changed

+208
-153
lines changed

4 files changed

+208
-153
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All changes that impact users of this module are documented in this file, in the [Common Changelog](https://common-changelog.org) format with some additional specifications defined in the CONTRIBUTING file. This codebase adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44

5+
## Unreleased [patch]
6+
7+
> Development of this release was supported by the [French Ministry for Foreign Affairs](https://www.diplomatie.gouv.fr/fr/politique-etrangere-de-la-france/diplomatie-numerique/) through its ministerial [State Startups incubator](https://beta.gouv.fr/startups/open-terms-archive.html) under the aegis of the Ambassador for Digital Affairs.
8+
9+
### Fixed
10+
11+
- Reduce GitHub API calls in issues reporter
12+
513
## 2.3.2 - 2024-10-23
614

715
_Full changeset and discussions: [#1112](https://github.com/OpenTermsArchive/engine/pull/1112)._

src/reporter/github.js

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ export default class GitHub {
2828
const [ owner, repo ] = repository.split('/');
2929

3030
this.commonParams = { owner, repo };
31+
32+
this.issuesCache = new Map();
33+
this._issuesPromise = null;
34+
}
35+
36+
get issues() {
37+
if (!this._issuesPromise) {
38+
logger.info('Loading issues from GitHub…');
39+
this._issuesPromise = this.loadAllIssues();
40+
}
41+
42+
return this._issuesPromise;
43+
}
44+
45+
clearCache() {
46+
this.issuesCache.clear();
47+
this._issuesPromise = null;
48+
logger.info('Issues cache cleared');
3149
}
3250

3351
async initialize() {
@@ -53,6 +71,33 @@ export default class GitHub {
5371
}
5472
}
5573

74+
async loadAllIssues() {
75+
try {
76+
const issues = await this.octokit.paginate('GET /repos/{owner}/{repo}/issues', {
77+
...this.commonParams,
78+
state: GitHub.ISSUE_STATE_ALL,
79+
per_page: 100,
80+
});
81+
82+
const onlyIssues = issues.filter(issue => !issue.pull_request); // Filter out pull requests since GitHub treats them as a special type of issue
83+
84+
onlyIssues.forEach(issue => {
85+
const cachedIssue = this.issuesCache.get(issue.title);
86+
87+
if (!cachedIssue || new Date(issue.created_at) < new Date(cachedIssue.created_at)) { // Only work on the oldest issue if there are duplicates, in order to consolidate the longest history possible
88+
this.issuesCache.set(issue.title, issue);
89+
}
90+
});
91+
92+
logger.info(`Cached ${onlyIssues.length} issues from the GitHub repository`);
93+
94+
return this.issuesCache;
95+
} catch (error) {
96+
logger.error(`Failed to load issues: ${error.message}`);
97+
throw error;
98+
}
99+
}
100+
56101
async getRepositoryLabels() {
57102
const { data: labels } = await this.octokit.request('GET /repos/{owner}/{repo}/labels', { ...this.commonParams });
58103

@@ -68,6 +113,10 @@ export default class GitHub {
68113
});
69114
}
70115

116+
async getIssue(title) {
117+
return (await this.issues).get(title);
118+
}
119+
71120
async createIssue({ title, description: body, labels }) {
72121
const { data: issue } = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
73122
...this.commonParams,
@@ -76,6 +125,8 @@ export default class GitHub {
76125
labels,
77126
});
78127

128+
this.issuesCache.set(issue.title, issue);
129+
79130
return issue;
80131
}
81132

@@ -87,19 +138,9 @@ export default class GitHub {
87138
labels,
88139
});
89140

90-
return updatedIssue;
91-
}
92-
93-
async getIssue({ title, ...searchParams }) {
94-
const issues = await this.octokit.paginate('GET /repos/{owner}/{repo}/issues', {
95-
...this.commonParams,
96-
per_page: 100,
97-
...searchParams,
98-
}, response => response.data);
99-
100-
const [issue] = issues.filter(item => item.title === title); // Since only one is expected, use the first one
141+
this.issuesCache.set(updatedIssue.title, updatedIssue);
101142

102-
return issue;
143+
return updatedIssue;
103144
}
104145

105146
async addCommentToIssue({ issue, comment: body }) {
@@ -114,25 +155,25 @@ export default class GitHub {
114155

115156
async closeIssueWithCommentIfExists({ title, comment }) {
116157
try {
117-
const openedIssue = await this.getIssue({ title, state: GitHub.ISSUE_STATE_OPEN });
158+
const issue = await this.getIssue(title);
118159

119-
if (!openedIssue) {
160+
if (!issue || issue.state == GitHub.ISSUE_STATE_CLOSED) {
120161
return;
121162
}
122163

123-
await this.addCommentToIssue({ issue: openedIssue, comment });
124-
logger.info(`Added comment to issue #${openedIssue.number}: ${openedIssue.html_url}`);
164+
await this.addCommentToIssue({ issue, comment });
165+
166+
const updatedIssue = await this.updateIssue(issue, { state: GitHub.ISSUE_STATE_CLOSED });
125167

126-
await this.updateIssue(openedIssue, { state: GitHub.ISSUE_STATE_CLOSED });
127-
logger.info(`Closed issue #${openedIssue.number}: ${openedIssue.html_url}`);
168+
logger.info(`Closed issue with comment #${updatedIssue.number}: ${updatedIssue.html_url}`);
128169
} catch (error) {
129-
logger.error(`Failed to update issue "${title}": ${error.message}`);
170+
logger.error(`Failed to close issue with comment "${title}": ${error.stack}`);
130171
}
131172
}
132173

133174
async createOrUpdateIssue({ title, description, label }) {
134175
try {
135-
const issue = await this.getIssue({ title, state: GitHub.ISSUE_STATE_ALL });
176+
const issue = await this.getIssue(title);
136177

137178
if (!issue) {
138179
const createdIssue = await this.createIssue({ title, description, labels: [label] });
@@ -148,16 +189,13 @@ export default class GitHub {
148189
return;
149190
}
150191

151-
await this.updateIssue(issue, {
152-
state: GitHub.ISSUE_STATE_OPEN,
153-
labels: [ label, ...labelsNotManagedToKeep ],
154-
});
155-
logger.info(`Updated issue #${issue.number}: ${issue.html_url}`);
192+
const updatedIssue = await this.updateIssue(issue, { state: GitHub.ISSUE_STATE_OPEN, labels: [ label, ...labelsNotManagedToKeep ] });
193+
156194
await this.addCommentToIssue({ issue, comment: description });
157195

158-
logger.info(`Added comment to issue #${issue.number}: ${issue.html_url}`);
196+
logger.info(`Updated issue with comment #${updatedIssue.number}: ${updatedIssue.html_url}`);
159197
} catch (error) {
160-
logger.error(`Failed to update issue "${title}": ${error.message}`);
198+
logger.error(`Failed to update issue "${title}": ${error.stack}`);
161199
}
162200
}
163201
}

0 commit comments

Comments
 (0)