Skip to content

Commit b7f6451

Browse files
authored
Improve labels used in reported issues (#1175)
2 parents cc4c2b2 + 1bf553d commit b7f6451

File tree

7 files changed

+447
-123
lines changed

7 files changed

+447
-123
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 [major]
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+
### Changed
10+
11+
- **Breaking:** Update issue labels to improve clarity; existing labels will be automatically updated on the first run
12+
513
## 6.1.0 - 2025-07-21
614

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

src/reporter/github/index.js

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export default class GitHub {
5151
async initialize() {
5252
this.MANAGED_LABELS = Object.values(LABELS);
5353
try {
54-
const existingLabels = await this.getRepositoryLabels();
54+
let existingLabels = await this.getRepositoryLabels();
5555
const labelsToRemove = existingLabels.filter(label => label.description && label.description.includes(DEPRECATED_MANAGED_BY_OTA_MARKER));
5656

5757
if (labelsToRemove.length) {
@@ -62,8 +62,26 @@ export default class GitHub {
6262
}
6363
}
6464

65-
const updatedExistingLabels = labelsToRemove.length ? await this.getRepositoryLabels() : existingLabels; // Refresh labels after deletion, only if needed
66-
const existingLabelsNames = updatedExistingLabels.map(label => label.name);
65+
if (labelsToRemove.length) {
66+
existingLabels = await this.getRepositoryLabels();
67+
}
68+
69+
const managedLabelsNames = this.MANAGED_LABELS.map(label => label.name);
70+
const obsoleteManagedLabels = existingLabels.filter(label => label.description?.includes(MANAGED_BY_OTA_MARKER) && !managedLabelsNames.includes(label.name));
71+
72+
if (obsoleteManagedLabels.length) {
73+
logger.info(`Removing obsolete managed labels: ${obsoleteManagedLabels.map(label => `"${label.name}"`).join(', ')}`);
74+
75+
for (const label of obsoleteManagedLabels) {
76+
await this.deleteLabel(label.name);
77+
}
78+
}
79+
80+
if (obsoleteManagedLabels.length) {
81+
existingLabels = await this.getRepositoryLabels();
82+
}
83+
84+
const existingLabelsNames = existingLabels.map(label => label.name);
6785
const missingLabels = this.MANAGED_LABELS.filter(label => !existingLabelsNames.includes(label.name));
6886

6987
if (missingLabels.length) {
@@ -77,6 +95,30 @@ export default class GitHub {
7795
});
7896
}
7997
}
98+
99+
const labelsToUpdate = this.MANAGED_LABELS.filter(label => {
100+
const existingLabel = existingLabels.find(existingLabel => existingLabel.name === label.name);
101+
102+
if (!existingLabel) {
103+
return false;
104+
}
105+
106+
const expectedDescription = `${label.description} ${MANAGED_BY_OTA_MARKER}`;
107+
108+
return existingLabel.description !== expectedDescription || existingLabel.color !== label.color;
109+
});
110+
111+
if (labelsToUpdate.length) {
112+
logger.info(`Updating labels with changed descriptions: ${labelsToUpdate.map(label => `"${label.name}"`).join(', ')}`);
113+
114+
for (const label of labelsToUpdate) {
115+
await this.updateLabel({
116+
name: label.name,
117+
color: label.color,
118+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
119+
});
120+
}
121+
}
80122
} catch (error) {
81123
logger.error(`Failed to handle repository labels: ${error.message}`);
82124
}
@@ -134,6 +176,15 @@ export default class GitHub {
134176
});
135177
}
136178

179+
async updateLabel({ name, color, description }) {
180+
await this.octokit.request('PATCH /repos/{owner}/{repo}/labels/{name}', {
181+
...this.commonParams,
182+
name,
183+
color,
184+
description,
185+
});
186+
}
187+
137188
async getIssue(title) {
138189
return (await this.issues).get(title);
139190
}

src/reporter/github/index.test.js

Lines changed: 116 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect } from 'chai';
22
import nock from 'nock';
33

4-
import { LABELS } from '../labels.js';
4+
import { LABELS, MANAGED_BY_OTA_MARKER } from '../labels.js';
55

66
import GitHub from './index.js';
77

@@ -10,8 +10,8 @@ describe('GitHub', function () {
1010

1111
let MANAGED_LABELS;
1212
let github;
13-
const EXISTING_OPEN_ISSUE = { number: 1, title: 'Opened issue', description: 'Issue description', state: GitHub.ISSUE_STATE_OPEN, labels: [{ name: 'page access restriction' }, { name: 'server error' }] };
14-
const EXISTING_CLOSED_ISSUE = { number: 2, title: 'Closed issue', description: 'Issue description', state: GitHub.ISSUE_STATE_CLOSED, labels: [{ name: 'empty content' }] };
13+
const EXISTING_OPEN_ISSUE = { number: 1, title: 'Opened issue', description: 'Issue description', state: GitHub.ISSUE_STATE_OPEN, labels: [{ name: LABELS.HTTP_403.name }] };
14+
const EXISTING_CLOSED_ISSUE = { number: 2, title: 'Closed issue', description: 'Issue description', state: GitHub.ISSUE_STATE_CLOSED, labels: [{ name: LABELS.EMPTY_CONTENT.name }] };
1515

1616
before(async () => {
1717
MANAGED_LABELS = Object.values(LABELS);
@@ -24,31 +24,119 @@ describe('GitHub', function () {
2424
});
2525

2626
describe('#initialize', () => {
27-
const scopes = [];
27+
context('when some labels are missing', () => {
28+
const scopes = [];
2829

29-
before(async () => {
30-
const existingLabels = MANAGED_LABELS.slice(0, -2);
30+
before(async () => {
31+
const existingLabels = MANAGED_LABELS.slice(0, -2).map(label => ({
32+
...label,
33+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
34+
}));
3135

32-
nock('https://api.github.com')
33-
.get('/repos/owner/repo/labels')
34-
.query(true)
35-
.reply(200, existingLabels);
36+
nock('https://api.github.com')
37+
.get('/repos/owner/repo/labels')
38+
.query(true)
39+
.reply(200, existingLabels);
40+
41+
const missingLabels = MANAGED_LABELS.slice(-2);
3642

37-
const missingLabels = MANAGED_LABELS.slice(-2);
43+
for (const label of missingLabels) {
44+
scopes.push(nock('https://api.github.com')
45+
.post('/repos/owner/repo/labels', body => body.name === label.name)
46+
.reply(200, label));
47+
}
3848

39-
for (const label of missingLabels) {
40-
scopes.push(nock('https://api.github.com')
41-
.post('/repos/owner/repo/labels', body => body.name === label.name)
42-
.reply(200, label));
43-
}
49+
await github.initialize();
50+
});
51+
52+
after(nock.cleanAll);
4453

45-
await github.initialize();
54+
it('should create missing labels', () => {
55+
scopes.forEach(scope => expect(scope.isDone()).to.be.true);
56+
});
4657
});
4758

48-
after(nock.cleanAll);
59+
context('when some labels are obsolete', () => {
60+
const deleteScopes = [];
61+
62+
before(async () => {
63+
const existingLabels = [
64+
...MANAGED_LABELS.map(label => ({
65+
...label,
66+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
67+
})),
68+
// Add an obsolete label that should be removed
69+
{
70+
name: 'obsolete label',
71+
color: 'FF0000',
72+
description: `This label is no longer used ${MANAGED_BY_OTA_MARKER}`,
73+
},
74+
];
75+
76+
nock('https://api.github.com')
77+
.get('/repos/owner/repo/labels')
78+
.query(true)
79+
.reply(200, existingLabels);
80+
81+
// Mock the delete call for the obsolete label
82+
deleteScopes.push(nock('https://api.github.com')
83+
.delete('/repos/owner/repo/labels/obsolete%20label')
84+
.reply(200));
85+
86+
// Mock the second getRepositoryLabels call after deletion
87+
nock('https://api.github.com')
88+
.get('/repos/owner/repo/labels')
89+
.query(true)
90+
.reply(200, MANAGED_LABELS.map(label => ({
91+
...label,
92+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
93+
})));
94+
95+
await github.initialize();
96+
});
97+
98+
after(nock.cleanAll);
99+
100+
it('should remove obsolete managed labels', () => {
101+
deleteScopes.forEach(scope => expect(scope.isDone()).to.be.true);
102+
});
103+
});
104+
105+
context('when some labels have changed descriptions', () => {
106+
const updateScopes = [];
49107

50-
it('should create missing labels', () => {
51-
scopes.forEach(scope => expect(scope.isDone()).to.be.true);
108+
before(async () => {
109+
const originalTestLabels = MANAGED_LABELS.slice(-2);
110+
const testLabels = originalTestLabels.map(label => ({
111+
...label,
112+
description: `${label.description} - obsolete description`,
113+
}));
114+
115+
nock('https://api.github.com')
116+
.persist()
117+
.get('/repos/owner/repo/labels')
118+
.query(true)
119+
.reply(200, [ ...MANAGED_LABELS.slice(0, -2), ...testLabels ].map(label => ({
120+
...label,
121+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
122+
})));
123+
124+
for (const label of originalTestLabels) {
125+
updateScopes.push(nock('https://api.github.com')
126+
.patch(`/repos/owner/repo/labels/${encodeURIComponent(label.name)}`, body =>
127+
body.description === `${label.description} ${MANAGED_BY_OTA_MARKER}`)
128+
.reply(200, label));
129+
}
130+
await github.initialize();
131+
});
132+
133+
after(() => {
134+
nock.cleanAll();
135+
});
136+
137+
it('should update labels with changed descriptions', () => {
138+
updateScopes.forEach(scope => expect(scope.isDone()).to.be.true);
139+
});
52140
});
53141
});
54142

@@ -280,7 +368,7 @@ describe('GitHub', function () {
280368
const ISSUE_TO_CREATE = {
281369
title: 'New Issue',
282370
description: 'Description of the new issue',
283-
labels: ['empty response'],
371+
labels: [LABELS.EMPTY_RESPONSE.name],
284372
};
285373

286374
before(async () => {
@@ -311,14 +399,14 @@ describe('GitHub', function () {
311399

312400
before(async () => {
313401
updateIssueScope = nock('https://api.github.com')
314-
.patch(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: ['page access restriction'] })
402+
.patch(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [LABELS.HTTP_403.name] })
315403
.reply(200);
316404

317405
addCommentScope = nock('https://api.github.com')
318406
.post(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}/comments`, { body: EXISTING_CLOSED_ISSUE.description })
319407
.reply(200);
320408

321-
await github.createOrUpdateIssue({ title: EXISTING_CLOSED_ISSUE.title, description: EXISTING_CLOSED_ISSUE.description, labels: ['page access restriction'] });
409+
await github.createOrUpdateIssue({ title: EXISTING_CLOSED_ISSUE.title, description: EXISTING_CLOSED_ISSUE.description, labels: [LABELS.HTTP_403.name] });
322410
});
323411

324412
after(() => {
@@ -342,14 +430,14 @@ describe('GitHub', function () {
342430

343431
before(async () => {
344432
updateIssueScope = nock('https://api.github.com')
345-
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: ['empty content'] })
433+
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [LABELS.EMPTY_CONTENT.name] })
346434
.reply(200);
347435

348436
addCommentScope = nock('https://api.github.com')
349437
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`, { body: EXISTING_OPEN_ISSUE.description })
350438
.reply(200);
351439

352-
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: ['empty content'] });
440+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [LABELS.EMPTY_CONTENT.name] });
353441
});
354442

355443
after(() => {
@@ -379,7 +467,7 @@ describe('GitHub', function () {
379467
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`)
380468
.reply(200);
381469

382-
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [ 'page access restriction', 'server error' ] });
470+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [LABELS.HTTP_403.name] });
383471
});
384472

385473
after(() => {
@@ -403,14 +491,14 @@ describe('GitHub', function () {
403491

404492
before(async () => {
405493
updateIssueScope = nock('https://api.github.com')
406-
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [ 'page access restriction', 'empty content' ] })
494+
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [ LABELS.HTTP_403.name, LABELS.NEEDS_INTERVENTION.name ] })
407495
.reply(200);
408496

409497
addCommentScope = nock('https://api.github.com')
410498
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`, { body: EXISTING_OPEN_ISSUE.description })
411499
.reply(200);
412500

413-
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [ 'page access restriction', 'empty content' ] });
501+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [ LABELS.HTTP_403.name, LABELS.NEEDS_INTERVENTION.name ] });
414502
});
415503

416504
after(() => {

0 commit comments

Comments
 (0)