testilo 26.0.0 → 28.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -1
- package/call.js +49 -5
- package/package.json +1 -1
- package/procs/compare/tcp40/index.js +3 -4
- package/procs/difgest/tfp40/index.js +3 -3
- package/procs/digest/tdp40/index.js +4 -2
- package/procs/track/ttp40/index.html +83 -0
- package/procs/track/ttp40/index.js +70 -0
- package/procs/util.js +5 -1
- package/reports/tracking/style.css +115 -0
- package/summarize.js +2 -2
- package/track.js +23 -0
package/README.md
CHANGED
|
@@ -768,7 +768,43 @@ node call summarize divisions 2411
|
|
|
768
768
|
|
|
769
769
|
When a user invokes `summarize` in this example, the `call` module:
|
|
770
770
|
- gets all the reports in the `scored` subdirectory of the `REPORTDIR` directory, or (if the third argument is present) all those whose file names begin with `2411`.
|
|
771
|
-
- writes the summary as
|
|
771
|
+
- writes the summary, with `divisions` as its description, in JSON format to the `summarized` subdirectory of the `REPORTDIR` directory.
|
|
772
|
+
|
|
773
|
+
### Track
|
|
774
|
+
|
|
775
|
+
The `track` module of Testilo selects, organizes, and presents data from summaries to show changes over time in total scores. The module produces a web page, showing changes in a table and (in the future) also in a line graph.
|
|
776
|
+
|
|
777
|
+
#### Invocation
|
|
778
|
+
|
|
779
|
+
##### By a module
|
|
780
|
+
|
|
781
|
+
A module can invoke `track()` in this way:
|
|
782
|
+
|
|
783
|
+
```javaScript
|
|
784
|
+
const {track} = require('testilo/track');
|
|
785
|
+
const trackerDir = `${process.env.FUNCTIONDIR}/track/ttp99a`;
|
|
786
|
+
const {tracker} = require(`${trackerDir}/index`);
|
|
787
|
+
const summary = …;
|
|
788
|
+
const [reportID, trackReport] = track(tracker, summary);
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
The `track()` function returns an ID and an HTML tracking report that shows data for all of the results in the summary. The invoking module can further dispose of the report as needed.
|
|
792
|
+
|
|
793
|
+
##### By a user
|
|
794
|
+
|
|
795
|
+
A user can invoke `track()` in this way:
|
|
796
|
+
|
|
797
|
+
```javaScript
|
|
798
|
+
node call track ttp99a 241016T2045-Uf-0 4 'ABC Foundation'
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
When a user invokes `track()` in this example, the `call` module:
|
|
802
|
+
- gets the summary from the `241016T2045-Uf-0.json` file in the `summarized` subdirectory of the `REPORTDIR` directory.
|
|
803
|
+
- selects the summarized data for all audits with the `order` value of `'4'` and the `target.what` value of `'ABC Foundation'`. If the third or fourth argument to `call()` is `null` (or omitted), then `call()` does not select audits by `order` or by `target.what`, respectively.
|
|
804
|
+
- uses tracker `ttp99a` to create a tracking report.
|
|
805
|
+
- writes the tracking report to the `tracking` subdirectory of the `REPORTDIR` directory.
|
|
806
|
+
|
|
807
|
+
The tracking reports created by `track()` are HTML files, and they expect a `style.css` file to exist in their directory. The `reports/tracking/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where tracking reports are written.
|
|
772
808
|
|
|
773
809
|
## Origin
|
|
774
810
|
|
package/call.js
CHANGED
|
@@ -36,6 +36,8 @@ const {difgest} = require('./difgest');
|
|
|
36
36
|
const {compare} = require('./compare');
|
|
37
37
|
// Function to summarize reports.
|
|
38
38
|
const {summarize} = require('./summarize');
|
|
39
|
+
// Function to track audits.
|
|
40
|
+
const {track} = require('./track');
|
|
39
41
|
|
|
40
42
|
// ########## CONSTANTS
|
|
41
43
|
|
|
@@ -153,8 +155,7 @@ const callDigest = async (digesterID, selector = '') => {
|
|
|
153
155
|
// If any exist:
|
|
154
156
|
if (reports.length) {
|
|
155
157
|
// Get the digester.
|
|
156
|
-
const
|
|
157
|
-
const {digester} = require(`${digesterDir}/index`);
|
|
158
|
+
const {digester} = require(`${functionDir}/digest/${digesterID}/index`);
|
|
158
159
|
// Digest the reports.
|
|
159
160
|
const digestedReports = await digest(digester, reports);
|
|
160
161
|
const digestedReportDir = `${reportDir}/digested`;
|
|
@@ -172,7 +173,7 @@ const callDigest = async (digesterID, selector = '') => {
|
|
|
172
173
|
console.log('ERROR: No scored reports to be digested');
|
|
173
174
|
}
|
|
174
175
|
};
|
|
175
|
-
// Fulfills a
|
|
176
|
+
// Fulfills a difgesting request.
|
|
176
177
|
const callDifgest = async (difgesterID, reportAID, reportBID) => {
|
|
177
178
|
// Get the scored reports to be difgested.
|
|
178
179
|
const reportAArray = await getReports('scored', reportAID);
|
|
@@ -240,7 +241,7 @@ const callCredit = async (tallyID, selector = '') => {
|
|
|
240
241
|
console.log('ERROR: No scored reports to be tallied');
|
|
241
242
|
}
|
|
242
243
|
};
|
|
243
|
-
// Fulfills a
|
|
244
|
+
// Fulfills a summarization request.
|
|
244
245
|
const callSummarize = async (what, selector = '') => {
|
|
245
246
|
// Get the scored reports to be summarized.
|
|
246
247
|
const reports = await getReports('scored', selector);
|
|
@@ -255,7 +256,7 @@ const callSummarize = async (what, selector = '') => {
|
|
|
255
256
|
// Save the summary.
|
|
256
257
|
const summaryDir = `${reportDir}/summarized`;
|
|
257
258
|
await fs.mkdir(summaryDir, {recursive: true});
|
|
258
|
-
const filePath = `${summaryDir}/${summary.
|
|
259
|
+
const filePath = `${summaryDir}/${summary.id}.json`;
|
|
259
260
|
await fs.writeFile(filePath, `${JSON.stringify(summary, null, 2)}\n`);
|
|
260
261
|
console.log(`Reports summarized and summary saved as ${filePath}`);
|
|
261
262
|
}
|
|
@@ -265,6 +266,43 @@ const callSummarize = async (what, selector = '') => {
|
|
|
265
266
|
console.log('ERROR: No scored reports to be summarized');
|
|
266
267
|
}
|
|
267
268
|
};
|
|
269
|
+
// Fulfills a tracking request.
|
|
270
|
+
const callTrack = async (trackerID, summaryID, orderID, targetWhat) => {
|
|
271
|
+
// Get the summary.
|
|
272
|
+
try {
|
|
273
|
+
const summaryJSON = await fs.readFile(`${reportDir}/summarized/${summaryID}.json`, 'utf8');
|
|
274
|
+
const summary = JSON.parse(summaryJSON);
|
|
275
|
+
// Remove unwanted audits from it.
|
|
276
|
+
summary.data = summary.data.filter(audit => {
|
|
277
|
+
if (orderID && audit.order !== orderID) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
if (targetWhat && audit.target && audit.target.what !== targetWhat) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
return true;
|
|
284
|
+
});
|
|
285
|
+
// If any audits remain:
|
|
286
|
+
if (summary.data.length) {
|
|
287
|
+
// Get the tracker.
|
|
288
|
+
const {tracker} = require(`${functionDir}/track/${trackerID}/index`);
|
|
289
|
+
// Track the audits.
|
|
290
|
+
const [reportID, trackingReport] = await track(tracker, summary);
|
|
291
|
+
// Save the tracking report.
|
|
292
|
+
await fs.mkdir(`${reportDir}/tracking`, {recursive: true});
|
|
293
|
+
const reportPath = `${reportDir}/tracking/${reportID}.html`;
|
|
294
|
+
await fs.writeFile(reportPath, trackingReport);
|
|
295
|
+
console.log(`Tracking report saved in ${reportPath}`);
|
|
296
|
+
}
|
|
297
|
+
// Otherwise, i.e. if no audits remain:
|
|
298
|
+
else {
|
|
299
|
+
console.log('ERROR: No audits match the request');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch(error) {
|
|
303
|
+
console.log(`ERROR: Tracking request invalid (${error.message})`);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
268
306
|
|
|
269
307
|
// ########## OPERATION
|
|
270
308
|
|
|
@@ -329,6 +367,12 @@ else if (fn === 'summarize' && fnArgs.length > 0 && fnArgs.length < 3) {
|
|
|
329
367
|
console.log('Execution completed');
|
|
330
368
|
});
|
|
331
369
|
}
|
|
370
|
+
else if (fn === 'track' && fnArgs.length > 1 && fnArgs.length < 5) {
|
|
371
|
+
callTrack(... fnArgs)
|
|
372
|
+
.then(() => {
|
|
373
|
+
console.log('Execution completed');
|
|
374
|
+
});
|
|
375
|
+
}
|
|
332
376
|
else {
|
|
333
377
|
console.log('ERROR: Invalid statement');
|
|
334
378
|
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
// Module to access files.
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
|
-
const {getBarCell} = require('../../util');
|
|
10
|
+
const {getBarCell, getNowDate, getNowDateSlash} = require('../../util');
|
|
11
11
|
|
|
12
12
|
// CONSTANTS
|
|
13
13
|
|
|
@@ -63,9 +63,8 @@ const populateQuery = async (scoredReports, query) => {
|
|
|
63
63
|
const data = await getData(scoredReports);
|
|
64
64
|
query.pageCount = data.pageCount;
|
|
65
65
|
query.tableBody = await getTableBody(data.bodyData);
|
|
66
|
-
|
|
67
|
-
query.
|
|
68
|
-
query.dateSlash = query.dateISO.replace(/-/g, '/');
|
|
66
|
+
query.dateISO = getNowDate();
|
|
67
|
+
query.dateSlash = getNowDateSlash();
|
|
69
68
|
};
|
|
70
69
|
// Returns a comparative report.
|
|
71
70
|
exports.comparer = async scoredReports => {
|
|
@@ -9,7 +9,7 @@ const {issues} = require('../../score/tic40');
|
|
|
9
9
|
// Module to process files.
|
|
10
10
|
const fs = require('fs/promises');
|
|
11
11
|
// Utility module.
|
|
12
|
-
const {getBarCell} = require('../../util');
|
|
12
|
+
const {getBarCell, getNowDate, getNowDateSlash} = require('../../util');
|
|
13
13
|
|
|
14
14
|
// CONSTANTS
|
|
15
15
|
|
|
@@ -46,8 +46,8 @@ const getIssueScoreRow = (summary, wcag, scoreA, scoreB, aSuperiorityMax, bSuper
|
|
|
46
46
|
const populateQuery = (reportA, reportB, query) => {
|
|
47
47
|
// General parameters.
|
|
48
48
|
query.fp = id;
|
|
49
|
-
query.dateISO =
|
|
50
|
-
query.dateSlash =
|
|
49
|
+
query.dateISO = getNowDate();
|
|
50
|
+
query.dateSlash = getNowDateSlash();
|
|
51
51
|
// For each report:
|
|
52
52
|
const issueIDs = new Set();
|
|
53
53
|
[reportA, reportB].forEach((report, index) => {
|
|
@@ -8,6 +8,8 @@ require('dotenv').config();
|
|
|
8
8
|
const {issues} = require('../../score/tic40');
|
|
9
9
|
// Module to process files.
|
|
10
10
|
const fs = require('fs/promises');
|
|
11
|
+
// Utility module.
|
|
12
|
+
const {getNowDate, getNowDateSlash} = require('../../util');
|
|
11
13
|
|
|
12
14
|
// CONSTANTS
|
|
13
15
|
|
|
@@ -34,8 +36,8 @@ const populateQuery = (report, query) => {
|
|
|
34
36
|
query.sp = scoreProcID;
|
|
35
37
|
query.dp = digesterID;
|
|
36
38
|
// Add the job data to the query.
|
|
37
|
-
query.dateISO =
|
|
38
|
-
query.dateSlash =
|
|
39
|
+
query.dateISO = getNowDate();
|
|
40
|
+
query.dateSlash = getNowDateSlash();
|
|
39
41
|
query.org = target.what;
|
|
40
42
|
query.url = target.which;
|
|
41
43
|
query.requester = requester;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!DOCTYPE HTML>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="author" content="Testilo">
|
|
7
|
+
<meta name="creator" content="Testilo">
|
|
8
|
+
<meta name="publisher" name="Testilo">
|
|
9
|
+
<meta name="description" content="report of accessibility testing of web pages">
|
|
10
|
+
<meta name="keywords" content="accessibility a11y web testing">
|
|
11
|
+
<title>Accessibility tracking report</title>
|
|
12
|
+
<link rel="icon" href="favicon.ico">
|
|
13
|
+
<link rel="stylesheet" href="style.css">
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<main>
|
|
17
|
+
<header>
|
|
18
|
+
<h1>Accessibility tracking report</h1>
|
|
19
|
+
</header>
|
|
20
|
+
<h2>Introduction</h2>
|
|
21
|
+
<p>This is tracking report <code>__id__</code>.</p>
|
|
22
|
+
<p>It tracks accessibility scores over time. A perfect score is 0. The tracking was performed by Testilo procedure __tp__.</p>
|
|
23
|
+
<p>The results are presented first as a graph, and then as a table.</p>
|
|
24
|
+
<h2>Results as a graph</h2>
|
|
25
|
+
<figure id="graph">
|
|
26
|
+
<figcaption>Accessibility scores</figcaption>
|
|
27
|
+
</figure>
|
|
28
|
+
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
|
29
|
+
<script src="https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6"></script>
|
|
30
|
+
<script type="module">
|
|
31
|
+
const summaryJSON = '__summaryJSON__';
|
|
32
|
+
const summary = JSON.parse(summaryJSON);
|
|
33
|
+
const graphData = [];
|
|
34
|
+
summary.data.forEach(result => {
|
|
35
|
+
graphData.push({
|
|
36
|
+
target: result.target.what,
|
|
37
|
+
time: new Date(`20${result.endTime}Z`),
|
|
38
|
+
score: result.score
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
const svg = Plot.plot({
|
|
42
|
+
style: 'overflow: visible;',
|
|
43
|
+
y: {grid: true},
|
|
44
|
+
marks: [
|
|
45
|
+
Plot.ruleY([0]),
|
|
46
|
+
Plot.lineY(graphData, {
|
|
47
|
+
x: 'time',
|
|
48
|
+
y: 'score',
|
|
49
|
+
stroke: 'target'
|
|
50
|
+
}),
|
|
51
|
+
Plot.text(graphData, Plot.selectLast({
|
|
52
|
+
x: 'time',
|
|
53
|
+
y: 'score',
|
|
54
|
+
z: 'target',
|
|
55
|
+
text: 'target',
|
|
56
|
+
textAnchor: 'start',
|
|
57
|
+
dx: 3
|
|
58
|
+
}))
|
|
59
|
+
]
|
|
60
|
+
});
|
|
61
|
+
document.getElementById('graph').insertAdjacentElement('beforeend', svg);
|
|
62
|
+
</script>
|
|
63
|
+
<h2>Results as a table</h2>
|
|
64
|
+
<table class="allBorder secondCellRight thirdCellRight">
|
|
65
|
+
<caption>Accessibility scores</caption>
|
|
66
|
+
<thead>
|
|
67
|
+
<tr>
|
|
68
|
+
<th>Date and time</th>
|
|
69
|
+
<th>Score</th>
|
|
70
|
+
<th>Order</th>
|
|
71
|
+
<th>Target</th>
|
|
72
|
+
</tr>
|
|
73
|
+
</thead>
|
|
74
|
+
<tbody>
|
|
75
|
+
__scoreRows__
|
|
76
|
+
</tbody>
|
|
77
|
+
</table>
|
|
78
|
+
<footer>
|
|
79
|
+
<p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
|
|
80
|
+
</footer>
|
|
81
|
+
</main>
|
|
82
|
+
</body>
|
|
83
|
+
</html>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// index: tracker for tracking procedure ttp40.
|
|
2
|
+
|
|
3
|
+
// IMPORTS
|
|
4
|
+
|
|
5
|
+
// Module to keep secrets.
|
|
6
|
+
require('dotenv').config();
|
|
7
|
+
// Module to process files.
|
|
8
|
+
const fs = require('fs/promises');
|
|
9
|
+
// Utility module.
|
|
10
|
+
const {alphaNumOf, getNowDate, getNowDateSlash} = require('../../util');
|
|
11
|
+
|
|
12
|
+
// CONSTANTS
|
|
13
|
+
|
|
14
|
+
// Tracker ID.
|
|
15
|
+
const trackerID = 'ttp40';
|
|
16
|
+
// Newline with indentations.
|
|
17
|
+
const innerJoiner = '\n ';
|
|
18
|
+
// Digest URL.
|
|
19
|
+
const digestURL = process.env.DIGEST_URL;
|
|
20
|
+
|
|
21
|
+
// FUNCTIONS
|
|
22
|
+
|
|
23
|
+
// Adds parameters to a query for a tracking report.
|
|
24
|
+
const populateQuery = async (id, summary, query) => {
|
|
25
|
+
// General parameters.
|
|
26
|
+
query.id = id;
|
|
27
|
+
query.tp = trackerID;
|
|
28
|
+
query.dateISO = getNowDate();
|
|
29
|
+
query.dateSlash = getNowDateSlash();
|
|
30
|
+
// JSON of pruned summary.
|
|
31
|
+
summary.data.forEach(result => {
|
|
32
|
+
delete result.id;
|
|
33
|
+
delete result.target.id;
|
|
34
|
+
delete result.target.which;
|
|
35
|
+
});
|
|
36
|
+
query.summaryJSON = JSON.stringify(summary);
|
|
37
|
+
// For each score:
|
|
38
|
+
const rows = [];
|
|
39
|
+
const results = summary.data;
|
|
40
|
+
const targetWhats = Array.from(new Set(results.map(result => result.target.what))).sort();
|
|
41
|
+
summary.data.forEach(result => {
|
|
42
|
+
// Create an HTML table row for it.
|
|
43
|
+
const timeCell = `<td>${result.endTime}</td>`;
|
|
44
|
+
const digestLinkDestination = digestURL.replace('__id__', result.id);
|
|
45
|
+
const scoreCell = `<td><a href=${digestLinkDestination}>${result.score}</a></td>`;
|
|
46
|
+
const orderCell = `<td class="center">${result.order}</td>`;
|
|
47
|
+
const targetID = alphaNumOf(targetWhats.indexOf(result.target.what));
|
|
48
|
+
const targetLink = `<a href="${result.target.which}">${result.target.what}</a>`;
|
|
49
|
+
const targetCell = `<td>${targetID}: ${targetLink}</td>`;
|
|
50
|
+
const row = `<tr>${[timeCell, scoreCell, orderCell, targetCell].join('')}</tr>`;
|
|
51
|
+
// Add the row to the array of rows.
|
|
52
|
+
rows.push(row);
|
|
53
|
+
});
|
|
54
|
+
// Add the rows to the query.
|
|
55
|
+
query.scoreRows = rows.join(innerJoiner);
|
|
56
|
+
};
|
|
57
|
+
// Returns a tracking report.
|
|
58
|
+
exports.tracker = async (id, summary) => {
|
|
59
|
+
// Create a query to replace placeholders.
|
|
60
|
+
const query = {};
|
|
61
|
+
await populateQuery(id, summary, query);
|
|
62
|
+
// Get the template.
|
|
63
|
+
let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
|
|
64
|
+
// Replace its placeholders.
|
|
65
|
+
Object.keys(query).forEach(param => {
|
|
66
|
+
template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
67
|
+
});
|
|
68
|
+
// Return the tracking report.
|
|
69
|
+
return template;
|
|
70
|
+
};
|
package/procs/util.js
CHANGED
|
@@ -18,7 +18,11 @@ const alphaNumChars = (() => {
|
|
|
18
18
|
// Returns a string representing a date and time.
|
|
19
19
|
const getTimeString = date => date.toISOString().slice(0, 19);
|
|
20
20
|
// Returns a string representing the date and time.
|
|
21
|
-
exports.getNowString = () => getTimeString(new Date());
|
|
21
|
+
const getNowString = exports.getNowString = () => getTimeString(new Date());
|
|
22
|
+
// Returns a string representing an ISO date.
|
|
23
|
+
const getNowDate = exports.getNowDate = () => getNowString().slice(0, 10);
|
|
24
|
+
// Returns a string representing a date with slashes.
|
|
25
|
+
exports.getNowDateSlash = () => getNowDate().replace(/-/g, '/');
|
|
22
26
|
// Returns a time stamp representing a date and time.
|
|
23
27
|
const getTimeStamp
|
|
24
28
|
= exports.getTimeStamp
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
*:focus {
|
|
2
|
+
outline: solid 0.2rem #00f;
|
|
3
|
+
outline-offset: 0.2rem;
|
|
4
|
+
}
|
|
5
|
+
body {
|
|
6
|
+
margin: 2rem;
|
|
7
|
+
font-size: large;
|
|
8
|
+
}
|
|
9
|
+
.bold, .componentID {
|
|
10
|
+
font-weight: 700;
|
|
11
|
+
}
|
|
12
|
+
button {
|
|
13
|
+
min-height: 44px;
|
|
14
|
+
min-width: 44px;
|
|
15
|
+
padding: 0.5rem;
|
|
16
|
+
border: solid 0.2rem #03a;
|
|
17
|
+
border-radius: 1rem;
|
|
18
|
+
font-size: inherit;
|
|
19
|
+
font-weight: 700;
|
|
20
|
+
color: #fff;
|
|
21
|
+
background-color: #03a;
|
|
22
|
+
}
|
|
23
|
+
button:hover {
|
|
24
|
+
color: #000;
|
|
25
|
+
background-color: #9df;
|
|
26
|
+
}
|
|
27
|
+
caption {
|
|
28
|
+
margin: 1rem 0;
|
|
29
|
+
font-weight: 700;
|
|
30
|
+
font-style: italic;
|
|
31
|
+
}
|
|
32
|
+
code {
|
|
33
|
+
font-family: sans-serif;
|
|
34
|
+
color: #832703;
|
|
35
|
+
}
|
|
36
|
+
.error {
|
|
37
|
+
font-weight: 700;
|
|
38
|
+
color: #c00;
|
|
39
|
+
}
|
|
40
|
+
fieldset {
|
|
41
|
+
width: max-content;
|
|
42
|
+
margin-top: 1rem;
|
|
43
|
+
}
|
|
44
|
+
.firstCellRight td:nth-child(1) {
|
|
45
|
+
text-align: right;
|
|
46
|
+
}
|
|
47
|
+
form {
|
|
48
|
+
margin-top: 1rem;
|
|
49
|
+
}
|
|
50
|
+
h1, h2, h3, h4, h5, h6 {
|
|
51
|
+
margin-bottom: 0;
|
|
52
|
+
}
|
|
53
|
+
h2 {
|
|
54
|
+
font-size: 1.8rem;
|
|
55
|
+
margin: 1rem auto 0.5rem auto;
|
|
56
|
+
color: #006666;
|
|
57
|
+
}
|
|
58
|
+
h3 {
|
|
59
|
+
font-size: 1.6rem;
|
|
60
|
+
}
|
|
61
|
+
h3.bars {
|
|
62
|
+
border-top: solid black 0.1rem;
|
|
63
|
+
padding-top: 0.3rem;
|
|
64
|
+
}
|
|
65
|
+
h4 {
|
|
66
|
+
font-size: 1.4rem;
|
|
67
|
+
}
|
|
68
|
+
h5 {
|
|
69
|
+
font-size: 1.2rem;
|
|
70
|
+
}
|
|
71
|
+
h6 {
|
|
72
|
+
font-size: 1rem;
|
|
73
|
+
}
|
|
74
|
+
input {
|
|
75
|
+
margin: 0.5rem 0;
|
|
76
|
+
font-size: inherit;
|
|
77
|
+
}
|
|
78
|
+
legend {
|
|
79
|
+
font-weight: 700;
|
|
80
|
+
}
|
|
81
|
+
pre {
|
|
82
|
+
white-space: break-spaces;
|
|
83
|
+
}
|
|
84
|
+
.secondCellRight td:nth-child(2) {
|
|
85
|
+
text-align: right;
|
|
86
|
+
}
|
|
87
|
+
section:not(.wide) {
|
|
88
|
+
max-width: 36rem;
|
|
89
|
+
}
|
|
90
|
+
#synopsis > p {
|
|
91
|
+
margin: 0;
|
|
92
|
+
padding: 0;
|
|
93
|
+
}
|
|
94
|
+
table {
|
|
95
|
+
border-collapse: collapse;
|
|
96
|
+
}
|
|
97
|
+
table.allBorder th {
|
|
98
|
+
padding-bottom: 0;
|
|
99
|
+
}
|
|
100
|
+
table.allBorder th, table.allBorder td, tbody td {
|
|
101
|
+
border: 1px gray solid;
|
|
102
|
+
}
|
|
103
|
+
tbody th {
|
|
104
|
+
padding-right: 1rem;
|
|
105
|
+
text-align: right;
|
|
106
|
+
}
|
|
107
|
+
td, th {
|
|
108
|
+
padding: 0 0.5rem;
|
|
109
|
+
}
|
|
110
|
+
thead th {
|
|
111
|
+
padding-bottom: 1rem;
|
|
112
|
+
}
|
|
113
|
+
.thirdCellRight td:nth-child(3) {
|
|
114
|
+
text-align: right;
|
|
115
|
+
}
|
package/summarize.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Module to keep secrets.
|
|
9
9
|
require('dotenv').config();
|
|
10
10
|
// Module to perform common operations.
|
|
11
|
-
const {
|
|
11
|
+
const {getFileID} = require('./procs/util');
|
|
12
12
|
|
|
13
13
|
// ########## FUNCTIONS
|
|
14
14
|
|
|
@@ -27,8 +27,8 @@ exports.summarize = (what, reports) => {
|
|
|
27
27
|
};
|
|
28
28
|
});
|
|
29
29
|
const summary = {
|
|
30
|
+
id: getFileID(2),
|
|
30
31
|
what,
|
|
31
|
-
timeStamp: getNowStamp(),
|
|
32
32
|
data
|
|
33
33
|
};
|
|
34
34
|
return summary;
|
package/track.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
track.js
|
|
3
|
+
Creates tracking reports from report summaries.
|
|
4
|
+
Arguments:
|
|
5
|
+
0. Tracking function.
|
|
6
|
+
1. Summary.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ########## IMPORTS
|
|
10
|
+
|
|
11
|
+
// Module to perform common operations.
|
|
12
|
+
const {getFileID} = require('./procs/util');
|
|
13
|
+
|
|
14
|
+
// ########## FUNCTIONS
|
|
15
|
+
|
|
16
|
+
// Creates and returns a tracking report from a summary.
|
|
17
|
+
exports.track = async (tracker, summary) => {
|
|
18
|
+
// Use the tracker to create a tracking report.
|
|
19
|
+
const id = getFileID(2);
|
|
20
|
+
const trackingReport = await tracker(id, summary);
|
|
21
|
+
console.log(`Tracking report ${id} created`);
|
|
22
|
+
return [id, trackingReport];
|
|
23
|
+
};
|