@opentermsarchive/engine 6.1.0 → 7.1.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/.eslintrc.yaml +1 -0
- package/package.json +1 -1
- package/scripts/history/migrate-services.js +2 -2
- package/scripts/import/loadCommits.js +1 -1
- package/scripts/reporter/duplicate/index.js +2 -2
- package/scripts/rewrite/rewrite-snapshots.js +0 -1
- package/scripts/rewrite/rewrite-versions.js +0 -1
- package/src/archivist/extract/index.js +0 -3
- package/src/archivist/fetcher/index.js +1 -1
- package/src/archivist/index.js +1 -1
- package/src/archivist/recorder/repositories/git/index.test.js +0 -2
- package/src/archivist/recorder/repositories/mongo/index.js +0 -2
- package/src/archivist/recorder/version.js +1 -1
- package/src/archivist/services/index.js +4 -4
- package/src/archivist/services/index.test.js +3 -3
- package/src/reporter/github/index.js +56 -5
- package/src/reporter/github/index.test.js +116 -28
- package/src/reporter/gitlab/index.js +82 -5
- package/src/reporter/gitlab/index.test.js +134 -34
- package/src/reporter/index.js +4 -4
- package/src/reporter/labels.js +51 -51
package/.eslintrc.yaml
CHANGED
package/package.json
CHANGED
|
@@ -140,7 +140,7 @@ async function rewriteSnapshots(repository, records, idsMapping, logger) {
|
|
|
140
140
|
let i = 1;
|
|
141
141
|
|
|
142
142
|
for (const record of records) {
|
|
143
|
-
const { id: recordId } = await repository.save(record);
|
|
143
|
+
const { id: recordId } = await repository.save(record);
|
|
144
144
|
|
|
145
145
|
idsMapping[record.id] = recordId; // Saves the mapping between the old ID and the new one.
|
|
146
146
|
|
|
@@ -166,7 +166,7 @@ async function rewriteVersions(repository, records, idsMapping, logger) {
|
|
|
166
166
|
|
|
167
167
|
record.snapshotId = newSnapshotId;
|
|
168
168
|
|
|
169
|
-
const { id: recordId } = await repository.save(record);
|
|
169
|
+
const { id: recordId } = await repository.save(record);
|
|
170
170
|
|
|
171
171
|
if (recordId) {
|
|
172
172
|
logger.info({ message: `Migrated version with new ID: ${recordId}`, serviceId: record.serviceId, type: record.termsType, id: record.id, current: i++, total: records.length });
|
|
@@ -42,7 +42,7 @@ let client;
|
|
|
42
42
|
let counter = 1;
|
|
43
43
|
|
|
44
44
|
for (const commit of filteredCommits.reverse()) { // reverse array to insert most recent commits first
|
|
45
|
-
await collection.updateOne({ hash: commit.hash }, { $set: { ...commit } }, { upsert: true });
|
|
45
|
+
await collection.updateOne({ hash: commit.hash }, { $set: { ...commit } }, { upsert: true });
|
|
46
46
|
|
|
47
47
|
if (counter % 1000 == 0) {
|
|
48
48
|
logger.info({ message: ' ', current: counter, total: totalCommitToLoad });
|
|
@@ -48,14 +48,14 @@ async function removeDuplicateIssues() {
|
|
|
48
48
|
continue;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
await octokit.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', {
|
|
51
|
+
await octokit.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', {
|
|
52
52
|
owner,
|
|
53
53
|
repo,
|
|
54
54
|
issue_number: issue.number,
|
|
55
55
|
state: 'closed',
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', {
|
|
58
|
+
await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', {
|
|
59
59
|
owner,
|
|
60
60
|
repo,
|
|
61
61
|
issue_number: issue.number,
|
|
@@ -64,7 +64,6 @@ let recorder;
|
|
|
64
64
|
const filteredCommits = commits.filter(({ message }) =>
|
|
65
65
|
message.match(/^(Start tracking|Update)/));
|
|
66
66
|
|
|
67
|
-
/* eslint-disable no-await-in-loop */
|
|
68
67
|
/* eslint-disable no-continue */
|
|
69
68
|
for (const commit of filteredCommits) {
|
|
70
69
|
console.log(Date.now(), commit.hash, commit.date, commit.message);
|
|
@@ -74,7 +74,6 @@ let recorder;
|
|
|
74
74
|
const filteredCommits = commits.filter(({ message }) =>
|
|
75
75
|
message.match(/^(Start tracking|Update)/));
|
|
76
76
|
|
|
77
|
-
/* eslint-disable no-await-in-loop */
|
|
78
77
|
/* eslint-disable no-continue */
|
|
79
78
|
for (const commit of filteredCommits) {
|
|
80
79
|
console.log(Date.now(), commit.hash, commit.date, commit.message);
|
|
@@ -59,15 +59,12 @@ export async function extractFromHTML(sourceDocument) {
|
|
|
59
59
|
|
|
60
60
|
for (const filterFunction of serviceSpecificFilters) {
|
|
61
61
|
try {
|
|
62
|
-
/* eslint-disable no-await-in-loop */
|
|
63
|
-
// We want this to be made in series
|
|
64
62
|
await filterFunction(webPageDOM, {
|
|
65
63
|
fetch: location,
|
|
66
64
|
select: contentSelectors,
|
|
67
65
|
remove: insignificantContentSelectors,
|
|
68
66
|
filter: serviceSpecificFilters.map(filter => filter.name),
|
|
69
67
|
});
|
|
70
|
-
/* eslint-enable no-await-in-loop */
|
|
71
68
|
} catch (error) {
|
|
72
69
|
throw new Error(`The filter function "${filterFunction.name}" failed: ${error}`);
|
|
73
70
|
}
|
package/src/archivist/index.js
CHANGED
|
@@ -187,7 +187,7 @@ export default class Archivist extends events.EventEmitter {
|
|
|
187
187
|
const { location: url, executeClientScripts, cssSelectors } = sourceDocument;
|
|
188
188
|
|
|
189
189
|
try {
|
|
190
|
-
const { mimeType, content, fetcher } = await this.fetch({ url, executeClientScripts, cssSelectors });
|
|
190
|
+
const { mimeType, content, fetcher } = await this.fetch({ url, executeClientScripts, cssSelectors });
|
|
191
191
|
|
|
192
192
|
sourceDocument.content = content;
|
|
193
193
|
sourceDocument.mimeType = mimeType;
|
|
@@ -1382,7 +1382,6 @@ describe('GitRepository', () => {
|
|
|
1382
1382
|
|
|
1383
1383
|
await subject.initialize();
|
|
1384
1384
|
|
|
1385
|
-
/* eslint-disable no-await-in-loop */
|
|
1386
1385
|
for (const commit of Object.values(commits)) {
|
|
1387
1386
|
const { path: relativeFilePath, date, content, message } = commit;
|
|
1388
1387
|
const filePath = path.join(RECORDER_PATH, relativeFilePath);
|
|
@@ -1396,7 +1395,6 @@ describe('GitRepository', () => {
|
|
|
1396
1395
|
expectedIds.push(sha);
|
|
1397
1396
|
expectedDates.push(date);
|
|
1398
1397
|
}
|
|
1399
|
-
/* eslint-enable no-await-in-loop */
|
|
1400
1398
|
});
|
|
1401
1399
|
|
|
1402
1400
|
after(() => subject.removeAll());
|
|
@@ -96,13 +96,11 @@ export default class MongoRepository extends RepositoryInterface {
|
|
|
96
96
|
async* iterate() {
|
|
97
97
|
const cursor = this.collection.find().sort({ fetchDate: 1 });
|
|
98
98
|
|
|
99
|
-
/* eslint-disable no-await-in-loop */
|
|
100
99
|
while (await cursor.hasNext()) {
|
|
101
100
|
const mongoDocument = await cursor.next();
|
|
102
101
|
|
|
103
102
|
yield this.#toDomain(mongoDocument);
|
|
104
103
|
}
|
|
105
|
-
/* eslint-enable no-await-in-loop */
|
|
106
104
|
}
|
|
107
105
|
|
|
108
106
|
removeAll() {
|
|
@@ -5,7 +5,7 @@ import Record from './record.js';
|
|
|
5
5
|
export default class Version extends Record {
|
|
6
6
|
static REQUIRED_PARAMS = Object.freeze([ ...Record.REQUIRED_PARAMS, 'snapshotIds' ]);
|
|
7
7
|
|
|
8
|
-
static SOURCE_DOCUMENTS_SEPARATOR = '\n\n';
|
|
8
|
+
static SOURCE_DOCUMENTS_SEPARATOR = '\n\n- - -\n\n'; // Separator used to delimit source documents when concatenating them. The "- - -" produces a horizontal ruler in Markdown
|
|
9
9
|
|
|
10
10
|
constructor(params) {
|
|
11
11
|
super(params);
|
|
@@ -50,7 +50,7 @@ async function loadServiceFilters(serviceId, filterNames) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const filterFilePath = `${serviceId}.filters.js`;
|
|
53
|
-
const serviceFilters = await import(pathToFileURL(path.join(declarationsPath, filterFilePath)));
|
|
53
|
+
const serviceFilters = await import(pathToFileURL(path.join(declarationsPath, filterFilePath)));
|
|
54
54
|
|
|
55
55
|
return filterNames.map(filterName => serviceFilters[filterName]);
|
|
56
56
|
}
|
|
@@ -74,7 +74,7 @@ async function loadServiceDocument(service, termsType, termsTypeDeclaration) {
|
|
|
74
74
|
remove: sourceDocumentInsignificantContentSelectors,
|
|
75
75
|
} = sourceDocument;
|
|
76
76
|
|
|
77
|
-
const sourceDocumentFilters = await loadServiceFilters(service.id, sourceDocumentFilterNames);
|
|
77
|
+
const sourceDocumentFilters = await loadServiceFilters(service.id, sourceDocumentFilterNames);
|
|
78
78
|
|
|
79
79
|
sourceDocuments.push(new SourceDocument({
|
|
80
80
|
location: sourceDocumentLocation || location,
|
|
@@ -101,7 +101,7 @@ export async function loadWithHistory(servicesIds = []) {
|
|
|
101
101
|
const services = await load(servicesIds);
|
|
102
102
|
|
|
103
103
|
for (const serviceId of Object.keys(services)) {
|
|
104
|
-
const { declarations, filters } = await loadServiceHistoryFiles(serviceId);
|
|
104
|
+
const { declarations, filters } = await loadServiceHistoryFiles(serviceId);
|
|
105
105
|
|
|
106
106
|
for (const termsType of Object.keys(declarations)) {
|
|
107
107
|
const termsTypeDeclarationEntries = declarations[termsType];
|
|
@@ -145,7 +145,7 @@ export async function loadWithHistory(servicesIds = []) {
|
|
|
145
145
|
remove: sourceDocumentInsignificantContentSelectors,
|
|
146
146
|
} = sourceDocument;
|
|
147
147
|
|
|
148
|
-
const sourceDocumentFilters = await loadServiceFilters(serviceId, sourceDocumentFilterNames);
|
|
148
|
+
const sourceDocumentFilters = await loadServiceFilters(serviceId, sourceDocumentFilterNames);
|
|
149
149
|
|
|
150
150
|
sourceDocuments.push(new SourceDocument({
|
|
151
151
|
location: sourceDocumentLocation || declarationForThisDate.fetch,
|
|
@@ -78,7 +78,7 @@ describe('Services', () => {
|
|
|
78
78
|
|
|
79
79
|
for (let indexFilter = 0; indexFilter < expectedFilters.length; indexFilter++) {
|
|
80
80
|
it(`has the proper "${expectedFilters[indexFilter].name}" filter function`, async () => {
|
|
81
|
-
expect(await actualFilters[indexFilter]()).equal(await expectedFilters[indexFilter]());
|
|
81
|
+
expect(await actualFilters[indexFilter]()).equal(await expectedFilters[indexFilter]());
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
} else {
|
|
@@ -220,7 +220,7 @@ describe('Services', () => {
|
|
|
220
220
|
|
|
221
221
|
for (let indexFilter = 0; indexFilter < expectedFiltersForThisDate.length; indexFilter++) {
|
|
222
222
|
it(`has the proper "${expectedFiltersForThisDate[indexFilter].name}" filter function`, async () => {
|
|
223
|
-
expect(await actualFiltersForThisDate[indexFilter]()).equal(await expectedFiltersForThisDate[indexFilter]());
|
|
223
|
+
expect(await actualFiltersForThisDate[indexFilter]()).equal(await expectedFiltersForThisDate[indexFilter]());
|
|
224
224
|
});
|
|
225
225
|
}
|
|
226
226
|
} else {
|
|
@@ -246,7 +246,7 @@ describe('Services', () => {
|
|
|
246
246
|
indexFilter++
|
|
247
247
|
) {
|
|
248
248
|
it(`has the proper "${expectedFilters[indexFilter].name}" filter function`, async () => {
|
|
249
|
-
expect(await actualFilters[indexFilter]()).equal(await expectedFilters[indexFilter]());
|
|
249
|
+
expect(await actualFilters[indexFilter]()).equal(await expectedFilters[indexFilter]());
|
|
250
250
|
});
|
|
251
251
|
}
|
|
252
252
|
} else {
|
|
@@ -51,26 +51,68 @@ export default class GitHub {
|
|
|
51
51
|
async initialize() {
|
|
52
52
|
this.MANAGED_LABELS = Object.values(LABELS);
|
|
53
53
|
try {
|
|
54
|
-
|
|
54
|
+
let existingLabels = await this.getRepositoryLabels();
|
|
55
55
|
const labelsToRemove = existingLabels.filter(label => label.description && label.description.includes(DEPRECATED_MANAGED_BY_OTA_MARKER));
|
|
56
56
|
|
|
57
57
|
if (labelsToRemove.length) {
|
|
58
58
|
logger.info(`Removing labels with deprecated markers: ${labelsToRemove.map(label => `"${label.name}"`).join(', ')}`);
|
|
59
59
|
|
|
60
60
|
for (const label of labelsToRemove) {
|
|
61
|
-
await this.deleteLabel(label.name);
|
|
61
|
+
await this.deleteLabel(label.name);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
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);
|
|
67
85
|
const missingLabels = this.MANAGED_LABELS.filter(label => !existingLabelsNames.includes(label.name));
|
|
68
86
|
|
|
69
87
|
if (missingLabels.length) {
|
|
70
88
|
logger.info(`Following required labels are not present on the repository: ${missingLabels.map(label => `"${label.name}"`).join(', ')}. Creating them…`);
|
|
71
89
|
|
|
72
90
|
for (const label of missingLabels) {
|
|
73
|
-
await this.createLabel({
|
|
91
|
+
await this.createLabel({
|
|
92
|
+
name: label.name,
|
|
93
|
+
color: label.color,
|
|
94
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
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({
|
|
74
116
|
name: label.name,
|
|
75
117
|
color: label.color,
|
|
76
118
|
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
@@ -134,6 +176,15 @@ export default class GitHub {
|
|
|
134
176
|
});
|
|
135
177
|
}
|
|
136
178
|
|
|
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
|
+
|
|
137
188
|
async getIssue(title) {
|
|
138
189
|
return (await this.issues).get(title);
|
|
139
190
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { expect } from 'chai';
|
|
2
2
|
import nock from 'nock';
|
|
3
3
|
|
|
4
|
-
import { LABELS } from '../labels.js';
|
|
4
|
+
import { LABELS, MANAGED_BY_OTA_MARKER } from '../labels.js';
|
|
5
5
|
|
|
6
6
|
import GitHub from './index.js';
|
|
7
7
|
|
|
@@ -10,8 +10,8 @@ describe('GitHub', function () {
|
|
|
10
10
|
|
|
11
11
|
let MANAGED_LABELS;
|
|
12
12
|
let github;
|
|
13
|
-
const EXISTING_OPEN_ISSUE = { number: 1, title: 'Opened issue', description: 'Issue description', state: GitHub.ISSUE_STATE_OPEN, labels: [{ name:
|
|
14
|
-
const EXISTING_CLOSED_ISSUE = { number: 2, title: 'Closed issue', description: 'Issue description', state: GitHub.ISSUE_STATE_CLOSED, labels: [{ name:
|
|
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 }] };
|
|
15
15
|
|
|
16
16
|
before(async () => {
|
|
17
17
|
MANAGED_LABELS = Object.values(LABELS);
|
|
@@ -24,31 +24,119 @@ describe('GitHub', function () {
|
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
describe('#initialize', () => {
|
|
27
|
-
|
|
27
|
+
context('when some labels are missing', () => {
|
|
28
|
+
const scopes = [];
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
before(async () => {
|
|
31
|
+
const existingLabels = MANAGED_LABELS.slice(0, -2).map(label => ({
|
|
32
|
+
...label,
|
|
33
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
34
|
+
}));
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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);
|
|
36
42
|
|
|
37
|
-
|
|
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
|
+
}
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
49
|
+
await github.initialize();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
after(nock.cleanAll);
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
it('should create missing labels', () => {
|
|
55
|
+
scopes.forEach(scope => expect(scope.isDone()).to.be.true);
|
|
56
|
+
});
|
|
46
57
|
});
|
|
47
58
|
|
|
48
|
-
|
|
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 = [];
|
|
49
107
|
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
});
|
|
52
140
|
});
|
|
53
141
|
});
|
|
54
142
|
|
|
@@ -280,7 +368,7 @@ describe('GitHub', function () {
|
|
|
280
368
|
const ISSUE_TO_CREATE = {
|
|
281
369
|
title: 'New Issue',
|
|
282
370
|
description: 'Description of the new issue',
|
|
283
|
-
labels: [
|
|
371
|
+
labels: [LABELS.EMPTY_RESPONSE.name],
|
|
284
372
|
};
|
|
285
373
|
|
|
286
374
|
before(async () => {
|
|
@@ -311,14 +399,14 @@ describe('GitHub', function () {
|
|
|
311
399
|
|
|
312
400
|
before(async () => {
|
|
313
401
|
updateIssueScope = nock('https://api.github.com')
|
|
314
|
-
.patch(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [
|
|
402
|
+
.patch(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [LABELS.HTTP_403.name] })
|
|
315
403
|
.reply(200);
|
|
316
404
|
|
|
317
405
|
addCommentScope = nock('https://api.github.com')
|
|
318
406
|
.post(`/repos/owner/repo/issues/${EXISTING_CLOSED_ISSUE.number}/comments`, { body: EXISTING_CLOSED_ISSUE.description })
|
|
319
407
|
.reply(200);
|
|
320
408
|
|
|
321
|
-
await github.createOrUpdateIssue({ title: EXISTING_CLOSED_ISSUE.title, description: EXISTING_CLOSED_ISSUE.description, labels: [
|
|
409
|
+
await github.createOrUpdateIssue({ title: EXISTING_CLOSED_ISSUE.title, description: EXISTING_CLOSED_ISSUE.description, labels: [LABELS.HTTP_403.name] });
|
|
322
410
|
});
|
|
323
411
|
|
|
324
412
|
after(() => {
|
|
@@ -342,14 +430,14 @@ describe('GitHub', function () {
|
|
|
342
430
|
|
|
343
431
|
before(async () => {
|
|
344
432
|
updateIssueScope = nock('https://api.github.com')
|
|
345
|
-
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [
|
|
433
|
+
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [LABELS.EMPTY_CONTENT.name] })
|
|
346
434
|
.reply(200);
|
|
347
435
|
|
|
348
436
|
addCommentScope = nock('https://api.github.com')
|
|
349
437
|
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`, { body: EXISTING_OPEN_ISSUE.description })
|
|
350
438
|
.reply(200);
|
|
351
439
|
|
|
352
|
-
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [
|
|
440
|
+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [LABELS.EMPTY_CONTENT.name] });
|
|
353
441
|
});
|
|
354
442
|
|
|
355
443
|
after(() => {
|
|
@@ -379,7 +467,7 @@ describe('GitHub', function () {
|
|
|
379
467
|
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`)
|
|
380
468
|
.reply(200);
|
|
381
469
|
|
|
382
|
-
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [
|
|
470
|
+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [LABELS.HTTP_403.name] });
|
|
383
471
|
});
|
|
384
472
|
|
|
385
473
|
after(() => {
|
|
@@ -403,14 +491,14 @@ describe('GitHub', function () {
|
|
|
403
491
|
|
|
404
492
|
before(async () => {
|
|
405
493
|
updateIssueScope = nock('https://api.github.com')
|
|
406
|
-
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [
|
|
494
|
+
.patch(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}`, { state: GitHub.ISSUE_STATE_OPEN, labels: [ LABELS.HTTP_403.name, LABELS.NEEDS_INTERVENTION.name ] })
|
|
407
495
|
.reply(200);
|
|
408
496
|
|
|
409
497
|
addCommentScope = nock('https://api.github.com')
|
|
410
498
|
.post(`/repos/owner/repo/issues/${EXISTING_OPEN_ISSUE.number}/comments`, { body: EXISTING_OPEN_ISSUE.description })
|
|
411
499
|
.reply(200);
|
|
412
500
|
|
|
413
|
-
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [
|
|
501
|
+
await github.createOrUpdateIssue({ title: EXISTING_OPEN_ISSUE.title, description: EXISTING_OPEN_ISSUE.description, labels: [ LABELS.HTTP_403.name, LABELS.NEEDS_INTERVENTION.name ] });
|
|
414
502
|
});
|
|
415
503
|
|
|
416
504
|
after(() => {
|
|
@@ -45,26 +45,69 @@ export default class GitLab {
|
|
|
45
45
|
|
|
46
46
|
this.MANAGED_LABELS = Object.values(LABELS);
|
|
47
47
|
try {
|
|
48
|
-
|
|
48
|
+
let existingLabels = await this.getRepositoryLabels();
|
|
49
49
|
const labelsToRemove = existingLabels.filter(label => label.description && label.description.includes(DEPRECATED_MANAGED_BY_OTA_MARKER));
|
|
50
50
|
|
|
51
51
|
if (labelsToRemove.length) {
|
|
52
52
|
logger.info(`Removing labels with deprecated markers: ${labelsToRemove.map(label => `"${label.name}"`).join(', ')}`);
|
|
53
53
|
|
|
54
54
|
for (const label of labelsToRemove) {
|
|
55
|
-
await this.deleteLabel(label.name);
|
|
55
|
+
await this.deleteLabel(label.name);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
if (labelsToRemove.length) {
|
|
60
|
+
existingLabels = await this.getRepositoryLabels();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const managedLabelsNames = this.MANAGED_LABELS.map(label => label.name);
|
|
64
|
+
const obsoleteManagedLabels = existingLabels.filter(label => label.description?.includes(MANAGED_BY_OTA_MARKER) && !managedLabelsNames.includes(label.name));
|
|
65
|
+
|
|
66
|
+
if (obsoleteManagedLabels.length) {
|
|
67
|
+
logger.info(`Removing obsolete managed labels: ${obsoleteManagedLabels.map(label => `"${label.name}"`).join(', ')}`);
|
|
68
|
+
|
|
69
|
+
for (const label of obsoleteManagedLabels) {
|
|
70
|
+
await this.deleteLabel(label.name);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (obsoleteManagedLabels.length) {
|
|
75
|
+
existingLabels = await this.getRepositoryLabels();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const existingLabelsNames = existingLabels.map(label => label.name);
|
|
61
79
|
const missingLabels = this.MANAGED_LABELS.filter(label => !existingLabelsNames.includes(label.name));
|
|
62
80
|
|
|
63
81
|
if (missingLabels.length) {
|
|
64
82
|
logger.info(`Following required labels are not present on the repository: ${missingLabels.map(label => `"${label.name}"`).join(', ')}. Creating them…`);
|
|
65
83
|
|
|
66
84
|
for (const label of missingLabels) {
|
|
67
|
-
await this.createLabel({
|
|
85
|
+
await this.createLabel({
|
|
86
|
+
name: label.name,
|
|
87
|
+
color: `#${label.color}`,
|
|
88
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const labelsToUpdate = this.MANAGED_LABELS.filter(label => {
|
|
94
|
+
const existingLabel = existingLabels.find(existingLabel => existingLabel.name === label.name);
|
|
95
|
+
|
|
96
|
+
if (!existingLabel) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const expectedDescription = `${label.description} ${MANAGED_BY_OTA_MARKER}`;
|
|
101
|
+
const expectedColor = `#${label.color}`;
|
|
102
|
+
|
|
103
|
+
return existingLabel.description !== expectedDescription || existingLabel.color !== expectedColor;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (labelsToUpdate.length) {
|
|
107
|
+
logger.info(`Updating labels with changed descriptions: ${labelsToUpdate.map(label => `"${label.name}"`).join(', ')}`);
|
|
108
|
+
|
|
109
|
+
for (const label of labelsToUpdate) {
|
|
110
|
+
await this.updateLabel({
|
|
68
111
|
name: label.name,
|
|
69
112
|
color: `#${label.color}`,
|
|
70
113
|
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
@@ -155,6 +198,40 @@ export default class GitLab {
|
|
|
155
198
|
}
|
|
156
199
|
}
|
|
157
200
|
|
|
201
|
+
async updateLabel({ name, color, description }) {
|
|
202
|
+
try {
|
|
203
|
+
const label = {
|
|
204
|
+
name,
|
|
205
|
+
color,
|
|
206
|
+
description,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const options = GitLab.baseOptionsHttpReq();
|
|
210
|
+
|
|
211
|
+
options.method = 'PUT';
|
|
212
|
+
options.body = JSON.stringify(label);
|
|
213
|
+
options.headers = {
|
|
214
|
+
'Content-Type': 'application/json',
|
|
215
|
+
...options.headers,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const response = await nodeFetch(
|
|
219
|
+
`${this.apiBaseURL}/projects/${this.projectId}/labels/${encodeURIComponent(name)}`,
|
|
220
|
+
options,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const res = await response.json();
|
|
224
|
+
|
|
225
|
+
if (response.ok) {
|
|
226
|
+
logger.info(`Label updated: ${res.name}, color: ${res.color}`);
|
|
227
|
+
} else {
|
|
228
|
+
logger.error(`updateLabel response: ${JSON.stringify(res)}`);
|
|
229
|
+
}
|
|
230
|
+
} catch (error) {
|
|
231
|
+
logger.error(`Failed to update label: ${error}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
158
235
|
async createIssue({ title, description, labels }) {
|
|
159
236
|
try {
|
|
160
237
|
const issue = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { expect } from 'chai';
|
|
2
2
|
import nock from 'nock';
|
|
3
3
|
|
|
4
|
-
import { LABELS } from '../labels.js';
|
|
4
|
+
import { LABELS, MANAGED_BY_OTA_MARKER } from '../labels.js';
|
|
5
5
|
|
|
6
6
|
import GitLab from './index.js';
|
|
7
7
|
|
|
@@ -18,34 +18,134 @@ describe('GitLab', function () {
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
describe('#initialize', () => {
|
|
21
|
-
|
|
21
|
+
context('when some labels are missing', () => {
|
|
22
|
+
const scopes = [];
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
before(async () => {
|
|
25
|
+
const existingLabels = MANAGED_LABELS.slice(0, -2).map(label => ({
|
|
26
|
+
...label,
|
|
27
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
28
|
+
}));
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
nock(gitlab.apiBaseURL)
|
|
31
|
+
.get(`/projects/${encodeURIComponent('owner/repo')}`)
|
|
32
|
+
.reply(200, { id: PROJECT_ID });
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
nock(gitlab.apiBaseURL)
|
|
35
|
+
.get(`/projects/${PROJECT_ID}/labels?with_counts=true`)
|
|
36
|
+
.reply(200, existingLabels);
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
const missingLabels = MANAGED_LABELS.slice(-2);
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
for (const label of missingLabels) {
|
|
41
|
+
scopes.push(nock(gitlab.apiBaseURL)
|
|
42
|
+
.post(`/projects/${PROJECT_ID}/labels`)
|
|
43
|
+
.reply(200, { name: label.name }));
|
|
44
|
+
}
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
await gitlab.initialize();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
after(nock.cleanAll);
|
|
50
|
+
|
|
51
|
+
it('should create missing labels', () => {
|
|
52
|
+
scopes.forEach(scope => expect(scope.isDone()).to.be.true);
|
|
53
|
+
});
|
|
43
54
|
});
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
context('when some labels are obsolete', () => {
|
|
57
|
+
const deleteScopes = [];
|
|
58
|
+
|
|
59
|
+
before(async () => {
|
|
60
|
+
const existingLabels = [
|
|
61
|
+
...MANAGED_LABELS.map(label => ({
|
|
62
|
+
...label,
|
|
63
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
64
|
+
})),
|
|
65
|
+
// Add an obsolete label that should be removed
|
|
66
|
+
{
|
|
67
|
+
name: 'obsolete label',
|
|
68
|
+
color: '#FF0000',
|
|
69
|
+
description: `This label is no longer used ${MANAGED_BY_OTA_MARKER}`,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
nock(gitlab.apiBaseURL)
|
|
74
|
+
.get(`/projects/${encodeURIComponent('owner/repo')}`)
|
|
75
|
+
.reply(200, { id: PROJECT_ID });
|
|
76
|
+
|
|
77
|
+
nock(gitlab.apiBaseURL)
|
|
78
|
+
.get(`/projects/${PROJECT_ID}/labels?with_counts=true`)
|
|
79
|
+
.reply(200, existingLabels);
|
|
46
80
|
|
|
47
|
-
|
|
48
|
-
|
|
81
|
+
// Mock the delete call for the obsolete label
|
|
82
|
+
deleteScopes.push(nock(gitlab.apiBaseURL)
|
|
83
|
+
.delete(`/projects/${PROJECT_ID}/labels/${encodeURIComponent('obsolete label')}`)
|
|
84
|
+
.reply(200));
|
|
85
|
+
|
|
86
|
+
// Mock the second getRepositoryLabels call after deletion
|
|
87
|
+
nock(gitlab.apiBaseURL)
|
|
88
|
+
.get(`/projects/${PROJECT_ID}/labels?with_counts=true`)
|
|
89
|
+
.reply(200, MANAGED_LABELS.map(label => ({
|
|
90
|
+
...label,
|
|
91
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
92
|
+
})));
|
|
93
|
+
|
|
94
|
+
await gitlab.initialize();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
after(nock.cleanAll);
|
|
98
|
+
|
|
99
|
+
it('should remove obsolete managed labels', () => {
|
|
100
|
+
deleteScopes.forEach(scope => expect(scope.isDone()).to.be.true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
context('when some labels have changed descriptions', () => {
|
|
105
|
+
const updateScopes = [];
|
|
106
|
+
|
|
107
|
+
before(async () => {
|
|
108
|
+
const originalTestLabels = MANAGED_LABELS.slice(-2);
|
|
109
|
+
const testLabels = originalTestLabels.map(label => ({
|
|
110
|
+
...label,
|
|
111
|
+
description: `${label.description} - obsolete description`,
|
|
112
|
+
}));
|
|
113
|
+
|
|
114
|
+
nock(gitlab.apiBaseURL)
|
|
115
|
+
.get(`/projects/${encodeURIComponent('owner/repo')}`)
|
|
116
|
+
.reply(200, { id: PROJECT_ID });
|
|
117
|
+
|
|
118
|
+
nock(gitlab.apiBaseURL)
|
|
119
|
+
.persist()
|
|
120
|
+
.get(`/projects/${PROJECT_ID}/labels?with_counts=true`)
|
|
121
|
+
.reply(200, [
|
|
122
|
+
...MANAGED_LABELS.slice(0, -2).map(label => ({
|
|
123
|
+
...label,
|
|
124
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
125
|
+
})),
|
|
126
|
+
...testLabels.map(label => ({
|
|
127
|
+
...label,
|
|
128
|
+
description: `${label.description} ${MANAGED_BY_OTA_MARKER}`,
|
|
129
|
+
})),
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
for (const label of originalTestLabels) {
|
|
133
|
+
updateScopes.push(nock(gitlab.apiBaseURL)
|
|
134
|
+
.put(`/projects/${PROJECT_ID}/labels/${encodeURIComponent(label.name)}`, body =>
|
|
135
|
+
body.description === `${label.description} ${MANAGED_BY_OTA_MARKER}`)
|
|
136
|
+
.reply(200, { name: label.name, color: `#${label.color}` }));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await gitlab.initialize();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
after(() => {
|
|
143
|
+
nock.cleanAll();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should update labels with changed descriptions', () => {
|
|
147
|
+
updateScopes.forEach(scope => expect(scope.isDone()).to.be.true);
|
|
148
|
+
});
|
|
49
149
|
});
|
|
50
150
|
});
|
|
51
151
|
|
|
@@ -405,7 +505,7 @@ describe('GitLab', function () {
|
|
|
405
505
|
const ISSUE_TO_CREATE = {
|
|
406
506
|
title: 'New Issue',
|
|
407
507
|
description: 'Description of the new issue',
|
|
408
|
-
labels: [
|
|
508
|
+
labels: [LABELS.EMPTY_RESPONSE.name],
|
|
409
509
|
};
|
|
410
510
|
|
|
411
511
|
before(async () => {
|
|
@@ -436,7 +536,7 @@ describe('GitLab', function () {
|
|
|
436
536
|
const ISSUE = {
|
|
437
537
|
title: 'Existing Issue',
|
|
438
538
|
description: 'New comment',
|
|
439
|
-
labels: [
|
|
539
|
+
labels: [LABELS.HTTP_403.name],
|
|
440
540
|
};
|
|
441
541
|
|
|
442
542
|
context('when issue is closed', () => {
|
|
@@ -448,7 +548,7 @@ describe('GitLab', function () {
|
|
|
448
548
|
iid: 123,
|
|
449
549
|
title: ISSUE.title,
|
|
450
550
|
description: ISSUE.description,
|
|
451
|
-
labels: [{ name:
|
|
551
|
+
labels: [{ name: LABELS.EMPTY_CONTENT.name }],
|
|
452
552
|
state: GitLab.ISSUE_STATE_CLOSED,
|
|
453
553
|
};
|
|
454
554
|
|
|
@@ -456,7 +556,7 @@ describe('GitLab', function () {
|
|
|
456
556
|
const responseIssuereopened = { iid: 123 };
|
|
457
557
|
const responseSetLabels = {
|
|
458
558
|
iid: 123,
|
|
459
|
-
labels: [
|
|
559
|
+
labels: [LABELS.HTTP_403.name],
|
|
460
560
|
};
|
|
461
561
|
const responseAddcomment = { iid: 123, id: 23, body: ISSUE.description };
|
|
462
562
|
const { iid } = GITLAB_RESPONSE_FOR_EXISTING_ISSUE;
|
|
@@ -471,7 +571,7 @@ describe('GitLab', function () {
|
|
|
471
571
|
.reply(200, responseIssuereopened);
|
|
472
572
|
|
|
473
573
|
setIssueLabelsScope = nock(gitlab.apiBaseURL)
|
|
474
|
-
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: [
|
|
574
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: [LABELS.HTTP_403.name] })
|
|
475
575
|
.reply(200, responseSetLabels);
|
|
476
576
|
|
|
477
577
|
addCommentScope = nock(gitlab.apiBaseURL)
|
|
@@ -504,7 +604,7 @@ describe('GitLab', function () {
|
|
|
504
604
|
iid: 123,
|
|
505
605
|
title: ISSUE.title,
|
|
506
606
|
description: ISSUE.description,
|
|
507
|
-
labels: [{ name:
|
|
607
|
+
labels: [{ name: LABELS.EMPTY_CONTENT.name }],
|
|
508
608
|
state: GitLab.ISSUE_STATE_OPEN,
|
|
509
609
|
};
|
|
510
610
|
|
|
@@ -512,7 +612,7 @@ describe('GitLab', function () {
|
|
|
512
612
|
const responseIssuereopened = { iid: 123 };
|
|
513
613
|
const responseSetLabels = {
|
|
514
614
|
iid: 123,
|
|
515
|
-
labels: [
|
|
615
|
+
labels: [LABELS.HTTP_403.name],
|
|
516
616
|
};
|
|
517
617
|
const responseAddcomment = { iid: 123, id: 23, body: ISSUE.description };
|
|
518
618
|
const { iid } = GITLAB_RESPONSE_FOR_EXISTING_ISSUE;
|
|
@@ -527,7 +627,7 @@ describe('GitLab', function () {
|
|
|
527
627
|
.reply(200, responseIssuereopened);
|
|
528
628
|
|
|
529
629
|
setIssueLabelsScope = nock(gitlab.apiBaseURL)
|
|
530
|
-
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: [
|
|
630
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: [LABELS.HTTP_403.name] })
|
|
531
631
|
.reply(200, responseSetLabels);
|
|
532
632
|
|
|
533
633
|
addCommentScope = nock(gitlab.apiBaseURL)
|
|
@@ -559,7 +659,7 @@ describe('GitLab', function () {
|
|
|
559
659
|
iid: 123,
|
|
560
660
|
title: ISSUE.title,
|
|
561
661
|
description: ISSUE.description,
|
|
562
|
-
labels: [{ name:
|
|
662
|
+
labels: [{ name: LABELS.HTTP_403.name }],
|
|
563
663
|
state: GitLab.ISSUE_STATE_OPEN,
|
|
564
664
|
};
|
|
565
665
|
|
|
@@ -585,7 +685,7 @@ describe('GitLab', function () {
|
|
|
585
685
|
await gitlab.createOrUpdateIssue({
|
|
586
686
|
title: ISSUE.title,
|
|
587
687
|
description: ISSUE.description,
|
|
588
|
-
labels: [
|
|
688
|
+
labels: [LABELS.HTTP_403.name],
|
|
589
689
|
});
|
|
590
690
|
});
|
|
591
691
|
|
|
@@ -615,7 +715,7 @@ describe('GitLab', function () {
|
|
|
615
715
|
iid: 123,
|
|
616
716
|
title: ISSUE.title,
|
|
617
717
|
description: ISSUE.description,
|
|
618
|
-
labels: [{ name:
|
|
718
|
+
labels: [{ name: LABELS.HTTP_403.name }],
|
|
619
719
|
state: GitLab.ISSUE_STATE_OPEN,
|
|
620
720
|
};
|
|
621
721
|
|
|
@@ -623,7 +723,7 @@ describe('GitLab', function () {
|
|
|
623
723
|
const responseIssuereopened = { iid: 123 };
|
|
624
724
|
const responseSetLabels = {
|
|
625
725
|
iid: 123,
|
|
626
|
-
labels: [
|
|
726
|
+
labels: [ LABELS.HTTP_403.name, LABELS.EMPTY_CONTENT.name ],
|
|
627
727
|
};
|
|
628
728
|
const responseAddcomment = { iid: 123, id: 23, body: ISSUE.description };
|
|
629
729
|
const { iid } = GITLAB_RESPONSE_FOR_EXISTING_ISSUE;
|
|
@@ -638,7 +738,7 @@ describe('GitLab', function () {
|
|
|
638
738
|
.reply(200, responseIssuereopened);
|
|
639
739
|
|
|
640
740
|
setIssueLabelsScope = nock(gitlab.apiBaseURL)
|
|
641
|
-
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: [
|
|
741
|
+
.put(`/projects/${PROJECT_ID}/issues/${iid}`, { labels: [ LABELS.HTTP_403.name, LABELS.EMPTY_CONTENT.name ] })
|
|
642
742
|
.reply(200, responseSetLabels);
|
|
643
743
|
|
|
644
744
|
addCommentScope = nock(gitlab.apiBaseURL)
|
|
@@ -648,7 +748,7 @@ describe('GitLab', function () {
|
|
|
648
748
|
await gitlab.createOrUpdateIssue({
|
|
649
749
|
title: ISSUE.title,
|
|
650
750
|
description: ISSUE.description,
|
|
651
|
-
labels: [
|
|
751
|
+
labels: [ LABELS.HTTP_403.name, LABELS.EMPTY_CONTENT.name ],
|
|
652
752
|
});
|
|
653
753
|
});
|
|
654
754
|
|
package/src/reporter/index.js
CHANGED
|
@@ -10,21 +10,21 @@ const CONTRIBUTION_TOOL_URL = 'https://contribute.opentermsarchive.org/en/servic
|
|
|
10
10
|
const DOC_URL = 'https://docs.opentermsarchive.org';
|
|
11
11
|
|
|
12
12
|
const ERROR_MESSAGE_TO_ISSUE_LABELS_MAP = {
|
|
13
|
-
'has no match': [ LABELS.
|
|
14
|
-
'HTTP code 404': [ LABELS.
|
|
13
|
+
'has no match': [ LABELS.DOCUMENT_STRUCTURE_CHANGE.name, LABELS.NEEDS_INTERVENTION.name ],
|
|
14
|
+
'HTTP code 404': [ LABELS.DOCUMENT_NOT_FOUND.name, LABELS.NEEDS_INTERVENTION.name ],
|
|
15
15
|
'HTTP code 403': [LABELS.HTTP_403.name],
|
|
16
16
|
'HTTP code 429': [LABELS.HTTP_429.name],
|
|
17
17
|
'HTTP code 500': [LABELS.HTTP_500.name],
|
|
18
18
|
'HTTP code 502': [LABELS.HTTP_502.name],
|
|
19
19
|
'HTTP code 503': [LABELS.HTTP_503.name],
|
|
20
|
-
'Timed out after': [LABELS.
|
|
20
|
+
'Timed out after': [LABELS.DOCUMENT_LOAD_TIMEOUT.name],
|
|
21
21
|
EAI_AGAIN: [LABELS.DNS_LOOKUP_FAILURE.name],
|
|
22
22
|
ENOTFOUND: [LABELS.DNS_RESOLUTION_FAILURE.name],
|
|
23
23
|
'Response is empty': [LABELS.EMPTY_RESPONSE.name],
|
|
24
24
|
'unable to verify the first certificate': [LABELS.SSL_INVALID.name],
|
|
25
25
|
'certificate has expired': [LABELS.SSL_EXPIRED.name],
|
|
26
26
|
'maximum redirect reached': [LABELS.TOO_MANY_REDIRECTS.name],
|
|
27
|
-
'not a valid selector': [LABELS.INVALID_SELECTOR.name],
|
|
27
|
+
'not a valid selector': [ LABELS.INVALID_SELECTOR.name, LABELS.NEEDS_INTERVENTION.name ],
|
|
28
28
|
'empty content': [LABELS.EMPTY_CONTENT.name],
|
|
29
29
|
};
|
|
30
30
|
|
package/src/reporter/labels.js
CHANGED
|
@@ -3,90 +3,90 @@ export const DEPRECATED_MANAGED_BY_OTA_MARKER = '[managed by OTA]';
|
|
|
3
3
|
export const MANAGED_BY_OTA_MARKER = '- Auto-managed by OTA engine';
|
|
4
4
|
|
|
5
5
|
export const LABELS = {
|
|
6
|
-
|
|
7
|
-
name: '
|
|
6
|
+
DNS_LOOKUP_FAILURE: {
|
|
7
|
+
name: 'domain lookup failed',
|
|
8
8
|
color: 'FFFFFF',
|
|
9
|
-
description: 'Fetching failed
|
|
9
|
+
description: 'Fetching failed because the request to DNS servers timed out',
|
|
10
10
|
},
|
|
11
|
-
|
|
12
|
-
name: '
|
|
11
|
+
DNS_RESOLUTION_FAILURE: {
|
|
12
|
+
name: 'domain resolution failed',
|
|
13
13
|
color: 'FFFFFF',
|
|
14
|
-
description: 'Fetching failed
|
|
14
|
+
description: 'Fetching failed because the domain name failed to resolve',
|
|
15
15
|
},
|
|
16
|
-
|
|
17
|
-
name: '
|
|
16
|
+
DOCUMENT_LOAD_TIMEOUT: {
|
|
17
|
+
name: 'document load timed out',
|
|
18
18
|
color: 'FFFFFF',
|
|
19
|
-
description: 'Fetching failed with a
|
|
19
|
+
description: 'Fetching failed with a timeout error',
|
|
20
20
|
},
|
|
21
|
-
|
|
22
|
-
name: '
|
|
21
|
+
DOCUMENT_NOT_FOUND: {
|
|
22
|
+
name: 'document not found',
|
|
23
23
|
color: 'FFFFFF',
|
|
24
|
-
description: '
|
|
24
|
+
description: 'Fetch location is outdated',
|
|
25
25
|
},
|
|
26
|
-
|
|
27
|
-
name: '
|
|
26
|
+
DOCUMENT_STRUCTURE_CHANGE: {
|
|
27
|
+
name: 'document structure changed',
|
|
28
28
|
color: 'FFFFFF',
|
|
29
|
-
description: '
|
|
29
|
+
description: 'Selectors do not match any content in the document',
|
|
30
30
|
},
|
|
31
|
-
|
|
32
|
-
name: '
|
|
31
|
+
EMPTY_CONTENT: {
|
|
32
|
+
name: 'document was empty',
|
|
33
33
|
color: 'FFFFFF',
|
|
34
|
-
description: 'Fetching failed because the
|
|
34
|
+
description: 'Fetching failed because the server returns an empty content',
|
|
35
35
|
},
|
|
36
|
-
|
|
37
|
-
name: '
|
|
36
|
+
EMPTY_RESPONSE: {
|
|
37
|
+
name: 'received empty response',
|
|
38
38
|
color: 'FFFFFF',
|
|
39
|
-
description: 'Fetching failed because
|
|
39
|
+
description: 'Fetching failed because server returned an empty response body',
|
|
40
40
|
},
|
|
41
|
-
|
|
42
|
-
name: '
|
|
41
|
+
HTTP_403: {
|
|
42
|
+
name: 'document access forbidden',
|
|
43
43
|
color: 'FFFFFF',
|
|
44
|
-
description: 'Fetching failed
|
|
44
|
+
description: 'Fetching failed with a 403 (forbidden) HTTP code',
|
|
45
45
|
},
|
|
46
|
-
|
|
47
|
-
name: '
|
|
46
|
+
HTTP_429: {
|
|
47
|
+
name: 'access limit exceeded',
|
|
48
48
|
color: 'FFFFFF',
|
|
49
|
-
description: 'Fetching failed
|
|
49
|
+
description: 'Fetching failed with a 429 (too many requests) HTTP code',
|
|
50
50
|
},
|
|
51
|
-
|
|
52
|
-
name: '
|
|
51
|
+
HTTP_500: {
|
|
52
|
+
name: 'server errored',
|
|
53
53
|
color: 'FFFFFF',
|
|
54
|
-
description: 'Fetching failed
|
|
54
|
+
description: 'Fetching failed with a 500 (internal server error) HTTP code',
|
|
55
55
|
},
|
|
56
|
-
|
|
57
|
-
name: '
|
|
56
|
+
HTTP_502: {
|
|
57
|
+
name: 'upstream server errored',
|
|
58
58
|
color: 'FFFFFF',
|
|
59
|
-
description: 'Fetching failed
|
|
59
|
+
description: 'Fetching failed with a 502 (bad gateway) HTTP code',
|
|
60
60
|
},
|
|
61
|
-
|
|
62
|
-
name: '
|
|
61
|
+
HTTP_503: {
|
|
62
|
+
name: 'server was unavailable',
|
|
63
63
|
color: 'FFFFFF',
|
|
64
|
-
description: 'Fetching failed with a
|
|
64
|
+
description: 'Fetching failed with a 503 (service unavailable) HTTP code',
|
|
65
65
|
},
|
|
66
|
-
|
|
67
|
-
name: '
|
|
66
|
+
INVALID_SELECTOR: {
|
|
67
|
+
name: 'invalid selector',
|
|
68
68
|
color: 'FFFFFF',
|
|
69
|
-
description: '
|
|
69
|
+
description: 'Some selectors cannot be understood by the engine',
|
|
70
70
|
},
|
|
71
|
-
|
|
72
|
-
name: '
|
|
71
|
+
SSL_EXPIRED: {
|
|
72
|
+
name: 'certificate expired',
|
|
73
73
|
color: 'FFFFFF',
|
|
74
|
-
description: 'Fetching failed
|
|
74
|
+
description: 'Fetching failed because the domain SSL certificate has expired',
|
|
75
75
|
},
|
|
76
|
-
|
|
77
|
-
name: '
|
|
76
|
+
SSL_INVALID: {
|
|
77
|
+
name: 'certificate was invalid',
|
|
78
78
|
color: 'FFFFFF',
|
|
79
|
-
description: '
|
|
79
|
+
description: 'Fetching failed because the domain SSL certificate failed to verify',
|
|
80
80
|
},
|
|
81
|
-
|
|
82
|
-
name: '
|
|
81
|
+
TOO_MANY_REDIRECTS: {
|
|
82
|
+
name: 'redirected too many times',
|
|
83
83
|
color: 'FFFFFF',
|
|
84
|
-
description: '
|
|
84
|
+
description: 'Fetching failed because of too many redirects',
|
|
85
85
|
},
|
|
86
|
-
|
|
87
|
-
name: '
|
|
86
|
+
UNKNOWN_FAILURE: {
|
|
87
|
+
name: 'failed for an unknown reason',
|
|
88
88
|
color: 'FFFFFF',
|
|
89
|
-
description: '
|
|
89
|
+
description: 'Fetching failed for an undetermined reason that needs investigation',
|
|
90
90
|
},
|
|
91
91
|
NEEDS_INTERVENTION: {
|
|
92
92
|
name: '⚠ needs intervention',
|