@jrpool/kilotest 58.2.4 → 59.0.2
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/api/getReport.test.ts +1 -1
- package/api/listIssues.test.ts +32 -0
- package/api/listIssues.ts +38 -8
- package/api/listPages.test.ts +92 -0
- package/api/listPages.ts +93 -0
- package/api/orderNewTest.test.ts +2 -2
- package/api/orderNewTest.ts +4 -4
- package/api/orderRetest.test.ts +1 -1
- package/api/requestNewTest.test.ts +1 -1
- package/api/requestNewTest.ts +6 -6
- package/api/requestRetest.test.ts +3 -3
- package/api/requestRetest.ts +2 -2
- package/api/routes.test.ts +1 -1
- package/api/routes.ts +5 -5
- package/api/schemas.ts +24 -7
- package/api/util.ts +26 -45
- package/apiIntegration.test.ts +5 -5
- package/docs/AI-TOOLS.md +1 -1
- package/docs/SERVICE.md +16 -5
- package/index.test.ts +31 -19
- package/index.ts +25 -13
- package/lhm.plugin.json +1433 -141
- package/linkValidation.test.ts +2 -2
- package/llms-full.txt +9 -9
- package/llms.txt +4 -4
- package/mcp.test.ts +9 -9
- package/mcp.ts +14 -14
- package/openapi.yaml +84 -30
- package/package.json +1 -1
- package/routing.test.ts +5 -5
- package/server.json +1 -1
- package/sitemap.xml +1 -1
- package/smokeTest.ts +2 -2
- package/style.css +8 -8
- package/test/fixtures/buildFixtures.ts +3 -3
- package/test/fixtures/db/reports/260101T0000-mix.json +2 -2
- package/test/fixtures/db/reports/260202T0000-new.json +2 -2
- package/util.test.ts +31 -22
- package/util.ts +16 -5
- package/web/enqueueForm/index.html +1 -1
- package/web/hideReportForm/hideReportForm.test.ts +5 -0
- package/web/hideReportForm/index.ts +9 -6
- package/web/home/home.test.ts +19 -0
- package/{index.html → web/home/index.html} +2 -2
- package/web/home/index.ts +20 -0
- package/web/listDiagnoses/listDiagnoses.test.ts +1 -1
- package/web/listIssues/index.html +9 -10
- package/web/listIssues/index.ts +45 -3
- package/web/listIssues/listIssues.test.ts +82 -6
- package/web/{listReports → listPages}/index.html +1 -2
- package/web/listPages/index.ts +99 -0
- package/web/listPages/listPages.test.ts +196 -0
- package/web/listViolators/listViolators.test.ts +1 -1
- package/web/metrics/metrics.test.ts +2 -2
- package/web/requestNewTest/requestNewTest.test.ts +1 -1
- package/web/requestRetest/requestRetest.test.ts +1 -2
- package/web/requestRetestForm/index.ts +4 -2
- package/web/requestRetestForm/requestRetestForm.test.ts +12 -1
- package/web/unhideReportForm/index.ts +9 -6
- package/web/unhideReportForm/unhideReportForm.test.ts +8 -5
- package/web/util.test.ts +87 -23
- package/web/util.ts +27 -31
- package/api/listReports.test.ts +0 -109
- package/api/listReports.ts +0 -87
- package/web/listReports/index.ts +0 -182
- package/web/listReports/listReports.test.ts +0 -300
package/web/util.ts
CHANGED
|
@@ -12,8 +12,7 @@ import {
|
|
|
12
12
|
alphaCompare,
|
|
13
13
|
getAgoDays,
|
|
14
14
|
getDateString,
|
|
15
|
-
|
|
16
|
-
getReportExtracts,
|
|
15
|
+
getReportEndTime,
|
|
17
16
|
getTestActInstances,
|
|
18
17
|
getTestActs,
|
|
19
18
|
htmlSafe,
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
} from '../util.ts';
|
|
22
21
|
import {issues as issueSpecs} from 'testaro-issues';
|
|
23
22
|
import type {Catalog} from 'testaro';
|
|
24
|
-
import type {
|
|
23
|
+
import type {UsableReport} from '../util.ts';
|
|
25
24
|
/* c8 ignore stop */
|
|
26
25
|
import wcagMap from '../wcagMap.json' with {type: 'json'};
|
|
27
26
|
|
|
@@ -40,6 +39,7 @@ export type PageDataStrings = {
|
|
|
40
39
|
url: string;
|
|
41
40
|
urlLink: string;
|
|
42
41
|
testInfo: string;
|
|
42
|
+
fullReportLink: string;
|
|
43
43
|
error?: never;
|
|
44
44
|
};
|
|
45
45
|
// Counts summarizing the results of an available report.
|
|
@@ -56,8 +56,6 @@ export type ReportData = {
|
|
|
56
56
|
description: string;
|
|
57
57
|
url: string;
|
|
58
58
|
jobName: unknown;
|
|
59
|
-
creationDate: Date | null;
|
|
60
|
-
daysAgo: number | null;
|
|
61
59
|
issueCount: number;
|
|
62
60
|
engineNames: string[];
|
|
63
61
|
engineCount: number;
|
|
@@ -95,9 +93,9 @@ export const checkCommentLength = (
|
|
|
95
93
|
const fragmentEncode = (string: string) => {
|
|
96
94
|
return encodeURIComponent(string).replace(/-/g, '%2D');
|
|
97
95
|
};
|
|
98
|
-
// Returns a string describing the time in days since a time stamp.
|
|
99
|
-
export const getAgoString = (
|
|
100
|
-
const agoDays = getAgoDays(
|
|
96
|
+
// Returns a string describing the time in days since a time stamp or date.
|
|
97
|
+
export const getAgoString = (timeArg: string | Date): string => {
|
|
98
|
+
const agoDays = getAgoDays(timeArg);
|
|
101
99
|
if (agoDays === null) {
|
|
102
100
|
return 'an unknown number of days';
|
|
103
101
|
}
|
|
@@ -111,8 +109,20 @@ const getTimeString = (timeStamp: string) => {
|
|
|
111
109
|
// Return the time string if valid, or null if not.
|
|
112
110
|
return (!isNaN(Date.parse(`2000-01-01T${timeString}Z`))) ? timeString : null;
|
|
113
111
|
};
|
|
114
|
-
// Returns a date-and-time string.
|
|
115
|
-
export const getDateTimeString = (
|
|
112
|
+
// Returns a date-and-time string from a time stamp or date.
|
|
113
|
+
export const getDateTimeString = (timeArg: string | Date): string => {
|
|
114
|
+
let timeStamp: string;
|
|
115
|
+
// If the argument is a date:
|
|
116
|
+
if (timeArg instanceof Date) {
|
|
117
|
+
// Convert it to a time stamp, or to an invalid one if the date is invalid.
|
|
118
|
+
timeStamp = isNaN(timeArg.getTime())
|
|
119
|
+
? ''
|
|
120
|
+
: timeArg.toISOString().slice(2, 16).replace(/[-:]/g, '');
|
|
121
|
+
}
|
|
122
|
+
// Otherwise, i.e. if it is a time stamp:
|
|
123
|
+
else {
|
|
124
|
+
timeStamp = timeArg;
|
|
125
|
+
}
|
|
116
126
|
const dateString = getDateString(timeStamp) || 'an unknown date';
|
|
117
127
|
const timeString = getTimeString(timeStamp) || 'an unknown time';
|
|
118
128
|
const dateTimeString = `${dateString} at ${timeString}`;
|
|
@@ -178,15 +188,11 @@ export const getEngineIDs = (report: any) => {
|
|
|
178
188
|
};
|
|
179
189
|
// Returns basics about an available report.
|
|
180
190
|
export const getReportData = (report: UsableReport): ReportData => {
|
|
181
|
-
// Identify the report by the time stamp at the start of its job name.
|
|
182
|
-
const timeStamp = report.id.slice(0, 11);
|
|
183
191
|
// Initialize the data.
|
|
184
192
|
const data = {
|
|
185
193
|
description: report.target.what,
|
|
186
194
|
url: report.target.url,
|
|
187
195
|
jobName: report.id,
|
|
188
|
-
creationDate: getDateTime(timeStamp),
|
|
189
|
-
daysAgo: getAgoDays(timeStamp),
|
|
190
196
|
issueCount: 0,
|
|
191
197
|
engineNames: [] as string[],
|
|
192
198
|
engineCount: 0,
|
|
@@ -245,9 +251,8 @@ export const getReportData = (report: UsableReport): ReportData => {
|
|
|
245
251
|
// Returns page data from an available report.
|
|
246
252
|
export const getPageData = (report: UsableReport): PageData => {
|
|
247
253
|
const {what: description, url} = report.target;
|
|
248
|
-
// Get the elapsed time in days since the report was completed
|
|
249
|
-
|
|
250
|
-
const daysAgo = getAgoDays(new Date(`20${report.jobData.endTime}Z`));
|
|
254
|
+
// Get the elapsed time in days since the report was completed.
|
|
255
|
+
const daysAgo = getAgoDays(getReportEndTime(report));
|
|
251
256
|
// Return the data.
|
|
252
257
|
return {
|
|
253
258
|
description,
|
|
@@ -258,14 +263,15 @@ export const getPageData = (report: UsableReport): PageData => {
|
|
|
258
263
|
// Gets HTML strings for page data from a report.
|
|
259
264
|
export const getPageDataStrings = (report: UsableReport): PageDataStrings => {
|
|
260
265
|
const {daysAgo, url, description} = getPageData(report);
|
|
261
|
-
// Get a description of the time
|
|
262
|
-
const when = getDateTimeString(report
|
|
266
|
+
// Get a description of the completion time of the report.
|
|
267
|
+
const when = getDateTimeString(getReportEndTime(report));
|
|
263
268
|
// Return the HTML strings.
|
|
264
269
|
return {
|
|
265
270
|
description,
|
|
266
271
|
url,
|
|
267
272
|
urlLink: `<a href="${url}">${url}</a>`,
|
|
268
|
-
testInfo: `Tested ${daysAgo === 1 ? '1 day' : `${daysAgo} days`} ago on ${when} by job <code>${report.id}</code
|
|
273
|
+
testInfo: `Tested ${daysAgo === 1 ? '1 day' : `${daysAgo} days`} ago on ${when} by job <code>${report.id}</code>`,
|
|
274
|
+
fullReportLink: `<a href="/fullReport.json/${report.id.replace('-', '/')}">Download the full technical report</a> (JSON)`
|
|
269
275
|
};
|
|
270
276
|
};
|
|
271
277
|
// Returns the lines of a list of facts about the page of an available report.
|
|
@@ -291,6 +297,7 @@ export const getPageFactsLines = (
|
|
|
291
297
|
`${margin} <li>Issues: ${summary.issueCount}</li>`,
|
|
292
298
|
`${margin} </ul>`,
|
|
293
299
|
`${margin} </li>`,
|
|
300
|
+
`${margin} <li>${strings.fullReportLink}</li>`,
|
|
294
301
|
`${margin}</ul>`
|
|
295
302
|
];
|
|
296
303
|
// Returns the lines of a list of facts about an issue in an available report.
|
|
@@ -372,14 +379,3 @@ export const getViolatorFactsLines = (
|
|
|
372
379
|
lines.push(`${margin}</ul>`);
|
|
373
380
|
return lines;
|
|
374
381
|
};
|
|
375
|
-
// Gets the descriptions of multi-report pages, from extracts already obtained, if provided.
|
|
376
|
-
export const getMultiReportWhats = async (
|
|
377
|
-
reportExtracts?: ReportExtract[]
|
|
378
|
-
): Promise<string[]> => {
|
|
379
|
-
reportExtracts ??= await getReportExtracts();
|
|
380
|
-
const sortedDescriptions = reportExtracts.map(extract => extract.description).sort();
|
|
381
|
-
const multiReportDescriptions = sortedDescriptions.filter(
|
|
382
|
-
(description, index) => description !== sortedDescriptions[index - 1] && description === sortedDescriptions[index + 1]
|
|
383
|
-
);
|
|
384
|
-
return multiReportDescriptions;
|
|
385
|
-
};
|
package/api/listReports.test.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
listReports.test.ts
|
|
3
|
-
Tests for api/listReports.ts using the fixture corpus.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
import {test, before, after} from 'node:test';
|
|
9
|
-
import assert from 'node:assert/strict';
|
|
10
|
-
import {response} from './listReports.ts';
|
|
11
|
-
|
|
12
|
-
// CONSTANTS
|
|
13
|
-
|
|
14
|
-
// Fixture report identifiers in the reports/ directory (excluding the hidden one).
|
|
15
|
-
const fixtureIds = [
|
|
16
|
-
['260101T0000', 'mix'],
|
|
17
|
-
['260101T0001', 'ct'],
|
|
18
|
-
['260101T0002', 'no'],
|
|
19
|
-
['260101T0005', 'emp'],
|
|
20
|
-
['260101T0006', 'prv'],
|
|
21
|
-
['260101T0008', 'mul'],
|
|
22
|
-
['260101T0009', 'brd'],
|
|
23
|
-
['260202T0000', 'new']
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
// SETUP AND TEARDOWN
|
|
27
|
-
|
|
28
|
-
const savedDBDir = process.env.DB_DIR;
|
|
29
|
-
|
|
30
|
-
before(async () => {
|
|
31
|
-
process.env.DB_DIR = (await import('../test/dbFixture.ts')).fixtureDBDir;
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
after(() => {
|
|
35
|
-
if (savedDBDir !== undefined) {
|
|
36
|
-
process.env.DB_DIR = savedDBDir;
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
delete process.env.DB_DIR;
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// TESTS
|
|
44
|
-
|
|
45
|
-
test('listReports returns basics about all 8 non-hidden reports', async () => {
|
|
46
|
-
const body = await response();
|
|
47
|
-
const reportsBasics = body['response content']['basics about all available reports'];
|
|
48
|
-
assert.equal(reportsBasics.length, 8);
|
|
49
|
-
const ids = reportsBasics.map((b: any) => b.identifier).sort();
|
|
50
|
-
const expectedIds = fixtureIds.map(([ts, jid]) => `${ts}-${jid}`).sort();
|
|
51
|
-
assert.deepEqual(ids, expectedIds);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('listReports does not include the hidden report', async () => {
|
|
55
|
-
const body = await response();
|
|
56
|
-
const reportsBasics = body['response content']['basics about all available reports'];
|
|
57
|
-
const ids = reportsBasics.map((b: any) => b.identifier);
|
|
58
|
-
assert.ok(!ids.includes('260101T0007-hid'), 'hidden report must not appear in listReports');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('listReports sorts reports by page description and then by completion time', async () => {
|
|
62
|
-
const body = await response();
|
|
63
|
-
const reportsBasics = body['response content']['basics about all available reports'];
|
|
64
|
-
const descriptions = reportsBasics.map((b: any) => b['tested web page'].description);
|
|
65
|
-
// All CantTell Page, Empty Results Page, Mixed Outcomes Page, Mixed Outcomes Page, No Outcomes Page, Prevented Page.
|
|
66
|
-
assert.deepEqual(
|
|
67
|
-
descriptions,
|
|
68
|
-
[
|
|
69
|
-
'All CantTell Page',
|
|
70
|
-
'Branch Coverage Page',
|
|
71
|
-
'Empty Results Page',
|
|
72
|
-
'Mixed Outcomes Page',
|
|
73
|
-
'Mixed Outcomes Page',
|
|
74
|
-
'Multi Violator Page',
|
|
75
|
-
'No Outcomes Page',
|
|
76
|
-
'Prevented Page'
|
|
77
|
-
]
|
|
78
|
-
);
|
|
79
|
-
// The two Mixed Outcomes Page reports should be sorted by completion time (older first).
|
|
80
|
-
const mixedReports = reportsBasics.filter((b: any) => b['tested web page'].description === 'Mixed Outcomes Page');
|
|
81
|
-
assert.equal(mixedReports[0]!.identifier, '260101T0000-mix');
|
|
82
|
-
assert.equal(mixedReports[1]!.identifier, '260202T0000-new');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test('listReports marks the superseded report correctly', async () => {
|
|
86
|
-
const body = await response();
|
|
87
|
-
const reportsBasics = body['response content']['basics about all available reports'];
|
|
88
|
-
const mix = reportsBasics.find((b: any) => b.identifier === '260101T0000-mix');
|
|
89
|
-
const newer = reportsBasics.find((b: any) => b.identifier === '260202T0000-new');
|
|
90
|
-
assert.ok(mix);
|
|
91
|
-
assert.ok(newer);
|
|
92
|
-
assert.equal(mix['whether a later report about the same page exists'], true);
|
|
93
|
-
assert.equal(newer['whether a later report about the same page exists'], false);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('listReports includes request-test instructions', async () => {
|
|
97
|
-
const body = await response();
|
|
98
|
-
const content = body['response content'];
|
|
99
|
-
assert.ok(content['how to request that a page with no report be tested']);
|
|
100
|
-
assert.ok(content['how a web user can request that the page be tested']);
|
|
101
|
-
assert.equal(content['how to request that a page with no report be tested'].method, 'POST');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('listReports includes tool name and metadata', async () => {
|
|
105
|
-
const body = await response();
|
|
106
|
-
assert.equal(body['tool name'], 'listReports');
|
|
107
|
-
assert.ok(body['response metadata'].identifier);
|
|
108
|
-
assert.ok(body['response metadata']['date and time']);
|
|
109
|
-
});
|
package/api/listReports.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
listReports.ts
|
|
3
|
-
Returns basics about all the available reports.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
import {z} from 'zod';
|
|
9
|
-
import {getReportBasics, getResponseMetadata, getThisHost, getToolsFacts} from './util.ts';
|
|
10
|
-
import {getReportExtracts} from '../util.ts';
|
|
11
|
-
import {listReportsResponseSchema} from './schemas.ts';
|
|
12
|
-
|
|
13
|
-
// TYPES
|
|
14
|
-
|
|
15
|
-
// The response content defined by the response schema.
|
|
16
|
-
type ResponseContent = z.infer<typeof listReportsResponseSchema>['response content'];
|
|
17
|
-
|
|
18
|
-
// FUNCTIONS
|
|
19
|
-
|
|
20
|
-
// Returns the response body.
|
|
21
|
-
export const response = async () => {
|
|
22
|
-
const thisHost = getThisHost();
|
|
23
|
-
// Initialize an array of basics about the reports.
|
|
24
|
-
const reportsBasics: ResponseContent['basics about all available reports'] = [];
|
|
25
|
-
// Get extracts of all available reports.
|
|
26
|
-
const reportExtracts = await getReportExtracts();
|
|
27
|
-
// For each report:
|
|
28
|
-
for (const extract of reportExtracts) {
|
|
29
|
-
const {jobID, timeStamp} = extract;
|
|
30
|
-
// Get the basics about it.
|
|
31
|
-
const reportBasics = await getReportBasics(timeStamp, jobID, extract);
|
|
32
|
-
// Add the basics, with instructions for getting details, to the array.
|
|
33
|
-
reportsBasics.push({
|
|
34
|
-
...reportBasics,
|
|
35
|
-
'how to get details about the report': {
|
|
36
|
-
method: 'GET',
|
|
37
|
-
URL: `${thisHost}/api/listIssues/${timeStamp}/${jobID}`
|
|
38
|
-
},
|
|
39
|
-
'web users can get details about the report at': `${thisHost}/listIssues.html/${timeStamp}/${jobID}`
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
// Sort the array by page description and secondarily by completion recency.
|
|
43
|
-
reportsBasics.sort((a, b) => {
|
|
44
|
-
if (a['tested web page'].description !== b['tested web page'].description) {
|
|
45
|
-
return a['tested web page']
|
|
46
|
-
.description
|
|
47
|
-
.localeCompare(b['tested web page'].description, 'en', {sensitivity: 'base'});
|
|
48
|
-
}
|
|
49
|
-
return a['completion date and time'].localeCompare(b['completion date and time']);
|
|
50
|
-
});
|
|
51
|
-
// Create the response content.
|
|
52
|
-
const responseContent: ResponseContent = {
|
|
53
|
-
'basics about all available reports': reportsBasics,
|
|
54
|
-
'how to request that a page with no report be tested': {
|
|
55
|
-
method: 'POST',
|
|
56
|
-
URL: `${thisHost}/api/requestNewTest`,
|
|
57
|
-
'request body': {
|
|
58
|
-
description: '10- to 100-character description of the page conforming to the naming convention used in this list of reports',
|
|
59
|
-
URL: '12- to 300-character URL of the page, including the https:// scheme and any query',
|
|
60
|
-
reason: '20- to 100-character reason why the page should be tested'
|
|
61
|
-
},
|
|
62
|
-
'how to check whether the request has been fulfilled': 'use this listReports tool to determine whether a report about the page has become available (typical wait time: 1 hour to 1 day)'
|
|
63
|
-
},
|
|
64
|
-
'how a web user can request that the page be tested': {
|
|
65
|
-
URL: `${thisHost}/requestNewTestForm.html`
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
// Create a response body.
|
|
69
|
-
const body = {
|
|
70
|
-
'tool collection': getToolsFacts(),
|
|
71
|
-
'tool name': 'listReports',
|
|
72
|
-
'this request': {
|
|
73
|
-
description: 'Provide basics about all available reports.',
|
|
74
|
-
method: 'GET',
|
|
75
|
-
URL: `${thisHost}/api/listReports`,
|
|
76
|
-
'closest ancestor request': null
|
|
77
|
-
},
|
|
78
|
-
'URLs of similar requests for web users': {
|
|
79
|
-
'this request': `${thisHost}/listReports.html`,
|
|
80
|
-
'closest ancestor request': null
|
|
81
|
-
},
|
|
82
|
-
'response metadata': getResponseMetadata(),
|
|
83
|
-
'response content': responseContent
|
|
84
|
-
};
|
|
85
|
-
// Return it.
|
|
86
|
-
return body;
|
|
87
|
-
};
|
package/web/listReports/index.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.ts
|
|
3
|
-
Lists all available reports.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
getJobNames,
|
|
10
|
-
getObject,
|
|
11
|
-
getReport,
|
|
12
|
-
getReportExtracts,
|
|
13
|
-
getTestRequests,
|
|
14
|
-
isReportError,
|
|
15
|
-
jobsPath,
|
|
16
|
-
objectSort,
|
|
17
|
-
populateTemplate
|
|
18
|
-
} from '../../util.ts';
|
|
19
|
-
import {
|
|
20
|
-
getMultiReportWhats,
|
|
21
|
-
getPageDataStrings,
|
|
22
|
-
getPageFactsLines,
|
|
23
|
-
getReportData
|
|
24
|
-
} from '../util.ts';
|
|
25
|
-
import path from 'node:path';
|
|
26
|
-
|
|
27
|
-
// FUNCTIONS
|
|
28
|
-
|
|
29
|
-
// Adds parameters to a query for the answer page.
|
|
30
|
-
const populateQuery = async (query: Record<string, any>) => {
|
|
31
|
-
const margin = ' '.repeat(8);
|
|
32
|
-
// Initialize the classes of lines.
|
|
33
|
-
const lines: {requests: string[], queue: string[], claimed: string[], tested: string[]} = {
|
|
34
|
-
requests: [],
|
|
35
|
-
queue: [],
|
|
36
|
-
claimed: [],
|
|
37
|
-
tested: []
|
|
38
|
-
};
|
|
39
|
-
// Get the test requests.
|
|
40
|
-
const testRequests = await getTestRequests();
|
|
41
|
-
// For each requested URL:
|
|
42
|
-
Object.keys(testRequests).forEach(url => {
|
|
43
|
-
// For each of its requests:
|
|
44
|
-
testRequests[url]!.forEach((req: any) => {
|
|
45
|
-
const {description, reason} = req;
|
|
46
|
-
// Add a line.
|
|
47
|
-
lines.requests.push(`${margin}<li><code>${url}</code> (${description}): ${reason}</li>`);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
// Sort the lines in alphabetical order by URL and secondarily by proposed name.
|
|
51
|
-
lines.requests.sort();
|
|
52
|
-
// Add the lines to the query.
|
|
53
|
-
query.requests = lines.requests.join('\n');
|
|
54
|
-
// Add a no-requests message, if applicable, to the query.
|
|
55
|
-
query.noRequests = lines.requests.length
|
|
56
|
-
? 'Kilotest managers can <a href="enqueueForm.html">approve or reject a request</a>.'
|
|
57
|
-
: 'No requests await approval now.';
|
|
58
|
-
// Get the file names of all queued and claimed jobs.
|
|
59
|
-
const jobFileNames = await getJobNames();
|
|
60
|
-
// Initialize sets of the page descriptions and URLs of jobs in both categories.
|
|
61
|
-
const jobsData = {
|
|
62
|
-
queue: {
|
|
63
|
-
descriptions: new Set(),
|
|
64
|
-
urls: new Set()
|
|
65
|
-
},
|
|
66
|
-
claimed: {
|
|
67
|
-
descriptions: new Set(),
|
|
68
|
-
urls: new Set()
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
// For each job category:
|
|
72
|
-
for (const category of ['queue', 'claimed'] as const) {
|
|
73
|
-
// For each job in the category:
|
|
74
|
-
for (const fileName of jobFileNames[category]) {
|
|
75
|
-
// Get the job.
|
|
76
|
-
const job = await getObject(path.join(jobsPath(), category, fileName)) as {target: {url: string, what: string}};
|
|
77
|
-
// Get the description and URL of its page.
|
|
78
|
-
const {target} = job;
|
|
79
|
-
const {what, url} = target;
|
|
80
|
-
// Ensure they are in the sets of properties of the category.
|
|
81
|
-
jobsData[category].descriptions.add(what);
|
|
82
|
-
jobsData[category].urls.add(url);
|
|
83
|
-
// Add a line.
|
|
84
|
-
lines[category].push(`${margin}<li><code>${job.target.url}</code> (${job.target.what})</li>`);
|
|
85
|
-
}
|
|
86
|
-
// Add the lines to the query.
|
|
87
|
-
query[category] = lines[category].join('\n');
|
|
88
|
-
}
|
|
89
|
-
// Add a no-queued message, if applicable, to the query.
|
|
90
|
-
query.noQueued = lines.queue.length ? '' : 'No pages are queued for testing.';
|
|
91
|
-
// Add a no-claimed message, if applicable, to the query.
|
|
92
|
-
query.noClaimed = lines.claimed.length ? '' : 'No pages are being tested now.';
|
|
93
|
-
// Get extracts of all available reports.
|
|
94
|
-
const reportExtracts = await getReportExtracts();
|
|
95
|
-
const reportCount = reportExtracts.length;
|
|
96
|
-
query.which = reportCount ? 'the following' : 'no';
|
|
97
|
-
query.some = (reportCount || jobFileNames.queue.length || jobFileNames.claimed.length)
|
|
98
|
-
? 'another'
|
|
99
|
-
: 'a';
|
|
100
|
-
const multiReportWhats = await getMultiReportWhats(reportExtracts);
|
|
101
|
-
// Sort them primarily by page description and secondarily by completion time.
|
|
102
|
-
let sortedExtracts = objectSort(reportExtracts, 'reportTime', 'alpha');
|
|
103
|
-
sortedExtracts = objectSort(sortedExtracts, 'description', 'alpha');
|
|
104
|
-
// For each report:
|
|
105
|
-
for (const extract of sortedExtracts) {
|
|
106
|
-
const {jobID, timeStamp, url, description, superseded} = extract;
|
|
107
|
-
// Get the report.
|
|
108
|
-
const report = await getReport(timeStamp, jobID);
|
|
109
|
-
// If this failed:
|
|
110
|
-
if (isReportError(report)) {
|
|
111
|
-
console.error(report.error);
|
|
112
|
-
// Populate the query with the reason.
|
|
113
|
-
query.error = report.error;
|
|
114
|
-
// Stop populating the query.
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
// Otherwise, i.e. if it succeeded, get data about the report.
|
|
118
|
-
const reportData = getReportData(report);
|
|
119
|
-
const pageDataStrings = getPageDataStrings(report);
|
|
120
|
-
const {issueCount} = reportData;
|
|
121
|
-
const {testInfo} = pageDataStrings;
|
|
122
|
-
// Add lines about the report.
|
|
123
|
-
lines.tested.push(`${margin}<details>`);
|
|
124
|
-
const testText = multiReportWhats.includes(description) ? ` (${testInfo.toLowerCase()})` : '';
|
|
125
|
-
lines.tested.push(`${margin} <summary>${description}${testText}</summary>`);
|
|
126
|
-
// Add the page facts to the lines.
|
|
127
|
-
lines.tested.push(...getPageFactsLines(pageDataStrings, reportData, `${margin} `));
|
|
128
|
-
lines.tested.push(`${margin} <ul class="nav">`);
|
|
129
|
-
// If any issues were reported:
|
|
130
|
-
if (issueCount) {
|
|
131
|
-
// Add a link to issue details to the lines.
|
|
132
|
-
const href = `href="listIssues.html/${timeStamp}/${jobID}"`;
|
|
133
|
-
const label = `aria-label="issue details for the ${description} page"`;
|
|
134
|
-
const link = `<a ${href} ${label}>Issue details</a>`;
|
|
135
|
-
lines.tested.push(`${margin} <li>${link}</li>`);
|
|
136
|
-
}
|
|
137
|
-
let retestString: string = '';
|
|
138
|
-
// If a page with the same description or URL is being tested:
|
|
139
|
-
if (jobsData.claimed.descriptions.has(description) || jobsData.claimed.urls.has(url)) {
|
|
140
|
-
// Report this.
|
|
141
|
-
retestString = 'Currently being retested';
|
|
142
|
-
}
|
|
143
|
-
// Otherwise, if such a page is queued:
|
|
144
|
-
else if (jobsData.queue.descriptions.has(description) || jobsData.queue.urls.has(url)) {
|
|
145
|
-
retestString = 'Currently in the queue for retesting';
|
|
146
|
-
}
|
|
147
|
-
// Otherwise, if no such page is being tested or queued and the report is not superseded:
|
|
148
|
-
else if (!superseded) {
|
|
149
|
-
// Make the page available for retesting.
|
|
150
|
-
const href = `/requestRetestForm.html/${timeStamp}/${jobID}`;
|
|
151
|
-
const retestContent = 'Should Kilotest retest the page?';
|
|
152
|
-
retestString = `<a href="${href}">${retestContent}</a>`;
|
|
153
|
-
}
|
|
154
|
-
if (retestString) {
|
|
155
|
-
lines.tested.push(`${margin} <li>${retestString}</li>`);
|
|
156
|
-
}
|
|
157
|
-
lines.tested.push(`${margin} </ul>`);
|
|
158
|
-
lines.tested.push(`${margin}</details>`);
|
|
159
|
-
}
|
|
160
|
-
query.testedPages = lines.tested.join('\n');
|
|
161
|
-
};
|
|
162
|
-
// Returns a page answering the targets question.
|
|
163
|
-
export const answer = async () => {
|
|
164
|
-
const query: Record<string, any> = {};
|
|
165
|
-
// Create a query to replace placeholders.
|
|
166
|
-
await populateQuery(query);
|
|
167
|
-
// If the query reports an error:
|
|
168
|
-
if (query.error) {
|
|
169
|
-
// Return it.
|
|
170
|
-
return {
|
|
171
|
-
status: 'error',
|
|
172
|
-
message: query.error
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
// Otherwise, i.e. if it does not report an error, get the template.
|
|
176
|
-
const answerPage = await populateTemplate(import.meta.dirname, query);
|
|
177
|
-
// Return the populated page.
|
|
178
|
-
return {
|
|
179
|
-
status: 'ok',
|
|
180
|
-
answerPage
|
|
181
|
-
};
|
|
182
|
-
};
|