testilo 25.1.3 → 27.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 CHANGED
@@ -244,7 +244,7 @@ There are two ways to use the `script` module.
244
244
 
245
245
  ##### By a module
246
246
 
247
- A module can invoke `script` in this way:
247
+ A module can invoke `script()` in this way:
248
248
 
249
249
  ```javaScript
250
250
  const {script} = require('testilo/script');
@@ -293,7 +293,7 @@ There are two ways to use the `merge` module.
293
293
 
294
294
  ##### By a module
295
295
 
296
- A module can invoke `merge` in this way:
296
+ A module can invoke `merge()` in this way:
297
297
 
298
298
  ```javaScript
299
299
  const {merge} = require('testilo/merge');
@@ -426,7 +426,7 @@ Testilo can enhance such a report by:
426
426
  - creating difgests
427
427
  - creating comparisons
428
428
 
429
- ## Scoring
429
+ ### Scoring
430
430
 
431
431
  To add scores to reports, the `score` module of Testilo performs computations on the test results and adds a `score` property to each report.
432
432
 
@@ -436,7 +436,7 @@ The `score()` function of the `score` module takes two arguments:
436
436
 
437
437
  A scoring function defines scoring rules. The Testilo package contains a `procs/score` directory, in which there are modules that export scoring functions. You can use one of those scoring functions, or you can create your own.
438
438
 
439
- ### Scorers
439
+ #### Scorers
440
440
 
441
441
  The built-in scoring functions are named `scorer()` and are exported by files whose names begin with `tsp` (for Testilo scoring proc). Those functions make use of `issues` objects defined in files whose names begin with `tic`. An `issues` object defines an issue classification: a body of data about rules of tools and the tool-agnostic issues that those rules are deemed to belong to.
442
442
 
@@ -482,11 +482,11 @@ The `quality` property is usually 1, but if the test of the rule is known to be
482
482
 
483
483
  Some issue objects (such as `flash` in `tic40.js`) have a `max` property, equal to the maximum possible count of instances. That property allows a scorer to ascribe a greater weight to an instance of that issue.
484
484
 
485
- ### Invocation
485
+ #### Invocation
486
486
 
487
487
  There are two ways to invoke the `score` module.
488
488
 
489
- #### By a module
489
+ ##### By a module
490
490
 
491
491
  A module can invoke `score()` in this way:
492
492
 
@@ -503,7 +503,7 @@ The second argument to `score()` is an array of report objects. They may have be
503
503
 
504
504
  The invoking module can further dispose of the scored reports as needed.
505
505
 
506
- #### By a user
506
+ ##### By a user
507
507
 
508
508
  A user can invoke `score()` in this way:
509
509
 
@@ -519,13 +519,13 @@ When a user invokes `score()` in this example, the `call` module:
519
519
 
520
520
  The optional third argument to `call()` (`75m` in this example) is a report selector. Without the argument, `call()` gets all the reports in the `raw` subdirectory. With the argument, `call()` gets only those reports whose names begin with the argument string.
521
521
 
522
- ### Validation
522
+ #### Validation
523
523
 
524
524
  To test the `score` module, in the project directory you can execute the statement `node validation/score/validate`. If `score` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
525
525
 
526
- ## Report digesting
526
+ ### Report digesting
527
527
 
528
- ### Introduction
528
+ #### Introduction
529
529
 
530
530
  Reports from Testaro are JavaScript objects. When represented as JSON, they are human-readable, but not human-friendly. They are basically designed for machine tractability. This is equally true for reports that have been scored by Testilo. But Testilo can _digest_ a scored report, converting it to a human-oriented HTML document, or _digest_.
531
531
 
@@ -538,11 +538,11 @@ The digesting function populates an HTML digest template. A copy of the template
538
538
 
539
539
  The included templates format placeholders with leading and trailing underscore pairs (such as `__issueCount__`).
540
540
 
541
- ### Invocation
541
+ #### Invocation
542
542
 
543
543
  There are two ways to use the `digest` module.
544
544
 
545
- #### By a module
545
+ ##### By a module
546
546
 
547
547
  A module can invoke `digest()` in this way:
548
548
 
@@ -564,7 +564,7 @@ The third argument is the absolute or relative URL of a directory where the repo
564
564
 
565
565
  The `digest()` function returns an array of digested reports. The invoking module can further dispose of the digested reports as needed.
566
566
 
567
- #### By a user
567
+ ##### By a user
568
568
 
569
569
  A user can invoke `digest()` in this way:
570
570
 
@@ -585,9 +585,9 @@ The optional fourth argument to `call()` (`75m` in this example) is a report sel
585
585
 
586
586
  The digests created by `digest()` are HTML files, and they expect a `style.css` file to exist in their directory. The `reports/digested/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where digested reports are written.
587
587
 
588
- ## Report difgesting
588
+ ### Report difgesting
589
589
 
590
- ### Introduction
590
+ #### Introduction
591
591
 
592
592
  A _difgest_ is a digest that compares two reports. They can be reports of different targets, or reports of the same target from two different times or under two different conditions.
593
593
 
@@ -601,11 +601,11 @@ The `difgest` module difgests two scored reports. Its `difgest()` function takes
601
601
 
602
602
  The difgest template and module operate like the digest ones.
603
603
 
604
- ### Invocation
604
+ #### Invocation
605
605
 
606
606
  There are two ways to use the `difgest` module.
607
607
 
608
- #### By a module
608
+ ##### By a module
609
609
 
610
610
  A module can invoke `difgest()` in this way:
611
611
 
@@ -625,9 +625,9 @@ The difgest will include links to the two digests, which, in turn, contain links
625
625
 
626
626
  `difgest()` returns a difgest. The invoking module can further dispose of the difgest as needed.
627
627
 
628
- #### By a user
628
+ ##### By a user
629
629
 
630
- A user can invoke `difgest` in this way:
630
+ A user can invoke `difgest()` in this way:
631
631
 
632
632
  ```bash
633
633
  node call difgest tfp99 20141215T1200-x7-3 20141215T1200-x7-4
@@ -642,7 +642,7 @@ Difgests include links to the digests of the two reports. The destinations of th
642
642
 
643
643
  Difgests expect a `style.css` file to exist in their directory, as digests do.
644
644
 
645
- ### Validation
645
+ #### Validation
646
646
 
647
647
  To test the `digest` module, in the project directory you can execute the statement `node validation/digest/validate`. If `digest` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
648
648
 
@@ -656,13 +656,13 @@ The `compare` module compares the scores in a collection of scored reports. Its
656
656
 
657
657
  The comparison function defines the rules for generating an HTML file comparing the scored reports. The Testilo package contains a `procs/compare` directory, in which there are subdirectories containing modules that export comparison functions. You can use one of those functions, or you can create your own.
658
658
 
659
- ### Invocation
659
+ #### Invocation
660
660
 
661
661
  There are two ways to use the `compare` module.
662
662
 
663
- #### By a module
663
+ ##### By a module
664
664
 
665
- A module can invoke `compare` in this way:
665
+ A module can invoke `compare()` in this way:
666
666
 
667
667
  ```javaScript
668
668
  const {compare} = require('testilo/compare');
@@ -674,9 +674,9 @@ compare(comparer, scoredReports)
674
674
 
675
675
  The first argument to `compare()` is a comparison function. In this example, it been obtained from a file in the Testilo package, but it could be custom-made. The second argument to `compare()` is an array of report objects. The `compare()` function returns a comparative report. The invoking module can further dispose of the comparative report as needed.
676
676
 
677
- #### By a user
677
+ ##### By a user
678
678
 
679
- A user can invoke `compare` in this way:
679
+ A user can invoke `compare()` in this way:
680
680
 
681
681
  ```bash
682
682
  node call compare tcp99 legislators
@@ -690,6 +690,10 @@ When a user invokes `compare` in this example, the `call` module:
690
690
 
691
691
  The comparative report created by `compare` is an HTML file, and it expects a `style.css` file to exist in its directory. The `reports/comparative/style.css` file in Testilo is an appropriate stylesheet to be copied into the directory where comparative reports are written.
692
692
 
693
+ #### Validation
694
+
695
+ To test the `compare` module, in the project directory you can execute the statement `node validation/compare/validate`. If `compare` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
696
+
693
697
  ### Tool crediting
694
698
 
695
699
  If you use Testaro to perform all the tests of all the tools on multiple targets and score the reports with a score proc that maps tool rules onto tool-agnostic issues, you may want to tabulate the comparative efficacy of each tool in discovering instances of issues. Testilo can help you do this by producing a _credit report_.
@@ -704,13 +708,13 @@ The credit report contains four sections:
704
708
  - `onlies`: a list of the issues that only the tool reported instances of
705
709
  - `mosts`: a list of the issues for which the instance count of the tool was not surpassed by that of any other tool
706
710
 
707
- ### Invocation
711
+ #### Invocation
708
712
 
709
713
  There are two ways to use the `credit` module.
710
714
 
711
- #### By a module
715
+ ##### By a module
712
716
 
713
- A module can invoke `credit` in this way:
717
+ A module can invoke `credit()` in this way:
714
718
 
715
719
  ```javaScript
716
720
  const {credit} = require('testilo/credit');
@@ -720,9 +724,9 @@ credit(scoredReports)
720
724
 
721
725
  The argument to `credit()` is an array of scored report objects. The `credit()` function returns a credit report. The invoking module can further dispose of the credit report as needed.
722
726
 
723
- #### By a user
727
+ ##### By a user
724
728
 
725
- A user can invoke `credit` in this way:
729
+ A user can invoke `credit()` in this way:
726
730
 
727
731
  ```bash
728
732
  node call credit legislators 23pl
@@ -734,9 +738,73 @@ When a user invokes `credit` in this example, the `call` module:
734
738
 
735
739
  The third argument to `call` (`23pl` in this example) is optional. If it is omitted, `call` will get and `credit()` will tabulate all the reports in the `scored` directory.
736
740
 
737
- ### Validation
741
+ ### Summarization
738
742
 
739
- To test the `compare` module, in the project directory you can execute the statement `node validation/compare/validate`. If `compare` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
743
+ The `summarize` module of Testilo can summarize a collection of scored reports. The summary of each report contains, insofar as they exist in the report, its ID, end time, order ID, target data, and total score.
744
+
745
+ #### Invocation
746
+
747
+ ##### By a module
748
+
749
+ A module can invoke `summarize()` in this way:
750
+
751
+ ```javaScript
752
+ const {summarize} = require('testilo/summarize');
753
+ const reports = […];
754
+ const summary = summarize(reports);
755
+
756
+ ```
757
+
758
+ The `reports` argument is an array of scored reports. The `summary` constant is an object. The module can further dispose of `summary` as needed.
759
+
760
+ ##### By a user
761
+
762
+ A user can invoke `summarize()` in either of these two ways:
763
+
764
+ ```javaScript
765
+ node call summarize divisions
766
+ node call summarize divisions 2411
767
+ ```
768
+
769
+ When a user invokes `summarize` in this example, the `call` module:
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, 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.
740
808
 
741
809
  ## Origin
742
810
 
package/call.js CHANGED
@@ -34,6 +34,10 @@ const {digest} = require('./digest');
34
34
  const {difgest} = require('./difgest');
35
35
  // Function to compare scores.
36
36
  const {compare} = require('./compare');
37
+ // Function to summarize reports.
38
+ const {summarize} = require('./summarize');
39
+ // Function to track audits.
40
+ const {track} = require('./track');
37
41
 
38
42
  // ########## CONSTANTS
39
43
 
@@ -151,8 +155,7 @@ const callDigest = async (digesterID, selector = '') => {
151
155
  // If any exist:
152
156
  if (reports.length) {
153
157
  // Get the digester.
154
- const digesterDir = `${functionDir}/digest/${digesterID}`;
155
- const {digester} = require(`${digesterDir}/index`);
158
+ const {digester} = require(`${functionDir}/digest/${digesterID}/index`);
156
159
  // Digest the reports.
157
160
  const digestedReports = await digest(digester, reports);
158
161
  const digestedReportDir = `${reportDir}/digested`;
@@ -170,7 +173,7 @@ const callDigest = async (digesterID, selector = '') => {
170
173
  console.log('ERROR: No scored reports to be digested');
171
174
  }
172
175
  };
173
- // Fulfills a digesting request.
176
+ // Fulfills a difgesting request.
174
177
  const callDifgest = async (difgesterID, reportAID, reportBID) => {
175
178
  // Get the scored reports to be difgested.
176
179
  const reportAArray = await getReports('scored', reportAID);
@@ -230,7 +233,7 @@ const callCredit = async (tallyID, selector = '') => {
230
233
  const creditDir = `${reportDir}/credit`;
231
234
  await fs.mkdir(creditDir, {recursive: true});
232
235
  await fs.writeFile(`${creditDir}/${tallyID}.json`, JSON.stringify(tally, null, 2));
233
- console.log(`Reports tallied and credit report saved in ${creditDir}`);
236
+ console.log(`Reports tallied and credit report ${tallyID} saved in ${creditDir}`);
234
237
  }
235
238
  // Otherwise, i.e. if no scored reports are to be tallied:
236
239
  else {
@@ -238,6 +241,68 @@ const callCredit = async (tallyID, selector = '') => {
238
241
  console.log('ERROR: No scored reports to be tallied');
239
242
  }
240
243
  };
244
+ // Fulfills a summarization request.
245
+ const callSummarize = async (what, selector = '') => {
246
+ // Get the scored reports to be summarized.
247
+ const reports = await getReports('scored', selector);
248
+ // If any exist:
249
+ if (reports.length) {
250
+ // Summarize them.
251
+ const summary = summarize(what, reports);
252
+ // Add the selector, if any, to the summary.
253
+ if (selector) {
254
+ summary.selector = selector;
255
+ }
256
+ // Save the summary.
257
+ const summaryDir = `${reportDir}/summarized`;
258
+ await fs.mkdir(summaryDir, {recursive: true});
259
+ const filePath = `${summaryDir}/${summary.id}.json`;
260
+ await fs.writeFile(filePath, `${JSON.stringify(summary, null, 2)}\n`);
261
+ console.log(`Reports summarized and summary saved as ${filePath}`);
262
+ }
263
+ // Otherwise, i.e. if no scored reports are to be summarized:
264
+ else {
265
+ // Report this.
266
+ console.log('ERROR: No scored reports to be summarized');
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
+ };
241
306
 
242
307
  // ########## OPERATION
243
308
 
@@ -296,6 +361,18 @@ else if (fn === 'credit' && fnArgs.length > 0 && fnArgs.length < 3) {
296
361
  console.log('Execution completed');
297
362
  });
298
363
  }
364
+ else if (fn === 'summarize' && fnArgs.length > 0 && fnArgs.length < 3) {
365
+ callSummarize(... fnArgs)
366
+ .then(() => {
367
+ console.log('Execution completed');
368
+ });
369
+ }
370
+ else if (fn === 'track' && fnArgs.length > 1 && fnArgs.length < 5) {
371
+ callTrack(... fnArgs)
372
+ .then(() => {
373
+ console.log('Execution completed');
374
+ });
375
+ }
299
376
  else {
300
377
  console.log('ERROR: Invalid statement');
301
378
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "25.1.3",
3
+ "version": "27.0.0",
4
4
  "description": "Prepares and processes Testaro reports",
5
5
  "main": "call.js",
6
6
  "scripts": {
@@ -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
- const date = new Date();
67
- query.dateISO = date.toISOString().slice(0, 10);
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 = new Date().toISOString().slice(0, 10);
50
- query.dateSlash = query.dateISO.replace(/-/g, '/');
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 = jobData.endTime.slice(0, 8);
38
- query.dateSlash = query.dateISO.replace(/-/g, '/');
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,42 @@
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
+ <h2>Introduction</h2>
20
+ <p>This is tracking report <code>__id__</code>.</p>
21
+ <p>It tracks accessibility scores over time. A perfect score is 0. The tracking was performed by procedure __tp__.</p>
22
+ </header>
23
+ <table class="allBorder secondCellRight thirdCellRight">
24
+ <caption>Accessibility scores</caption>
25
+ <thead>
26
+ <tr>
27
+ <th>Date and time</th>
28
+ <th>Score</th>
29
+ <th>Order</th>
30
+ <th>Target</th>
31
+ </tr>
32
+ </thead>
33
+ <tbody>
34
+ __scoreRows__
35
+ </tbody>
36
+ </table>
37
+ <footer>
38
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
39
+ </footer>
40
+ </main>
41
+ </body>
42
+ </html>
@@ -0,0 +1,63 @@
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 = (id, summary, query) => {
25
+ // General parameters.
26
+ query.id = id;
27
+ query.tp = trackerID;
28
+ query.dateISO = getNowDate();
29
+ query.dateSlash = getNowDateSlash();
30
+ // For each score:
31
+ const rows = [];
32
+ const results = summary.data;
33
+ const targetWhats = Array.from(new Set(results.map(result => result.target.what))).sort();
34
+ summary.data.forEach(result => {
35
+ // Create an HTML table row for it.
36
+ const timeCell = `<td>${result.endTime}</td>`;
37
+ const digestLinkDestination = digestURL.replace('__id__', result.id);
38
+ const scoreCell = `<td><a href=${digestLinkDestination}>${result.score}</a></td>`;
39
+ const orderCell = `<td class="center">${result.order}</td>`;
40
+ const targetID = alphaNumOf(targetWhats.indexOf(result.target.what));
41
+ const targetLink = `<a href="${result.target.which}">${result.target.what}</a>`;
42
+ const targetCell = `<td>${targetID}: ${targetLink}</td>`;
43
+ const row = `<tr>${[timeCell, scoreCell, orderCell, targetCell].join('')}</tr>`;
44
+ // Add the row to the array of rows.
45
+ rows.push(row);
46
+ });
47
+ // Add the rows to the query.
48
+ query.scoreRows = rows.join(innerJoiner);
49
+ };
50
+ // Returns a tracking report.
51
+ exports.tracker = async (id, summary) => {
52
+ // Create a query to replace placeholders.
53
+ const query = {};
54
+ populateQuery(id, summary, query);
55
+ // Get the template.
56
+ let template = await fs.readFile(`${__dirname}/index.html`, 'utf8');
57
+ // Replace its placeholders.
58
+ Object.keys(query).forEach(param => {
59
+ template = template.replace(new RegExp(`__${param}__`, 'g'), query[param]);
60
+ });
61
+ // Return the tracking report.
62
+ return template;
63
+ };
package/procs/util.js CHANGED
@@ -18,13 +18,17 @@ 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
25
29
  = date => getTimeString(date).replace(/[-:]/g, '').slice(2, 13);
26
30
  // Returns a time stamp representing the date and time.
27
- exports.getNowStamp = () => getTimeStamp(new Date());
31
+ const getNowStamp = exports.getNowStamp = () => getTimeStamp(new Date());
28
32
  // Inserts a character periodically in a string.
29
33
  const punctuate = (string, insertion, chunkSize) => {
30
34
  const segments = [];
@@ -56,13 +60,19 @@ exports.alphaNumOf = num => {
56
60
  return resultDigits.join('');
57
61
  };
58
62
  // Returns a random string.
59
- exports.getRandomString = length => {
63
+ const getRandomString = exports.getRandomString = length => {
60
64
  const chars = [];
61
65
  for (let i = 0; i < length; i++) {
62
66
  chars.push(alphaNumChars[Math.floor(62 * Math.random())]);
63
67
  }
64
68
  return chars.join('');
65
69
  };
70
+ // Returns a file ID.
71
+ exports.getFileID = randomLength => {
72
+ const timePart = getNowStamp();
73
+ const randomPart = getRandomString(randomLength);
74
+ return `${timePart}-${randomPart}-0`;
75
+ };
66
76
  // Returns a horizontal SVG graph bar.
67
77
  const getSVGBar = (num, max, isRight) => {
68
78
  const widthFrac = 100 * num / max;
@@ -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 ADDED
@@ -0,0 +1,35 @@
1
+ /*
2
+ summarize.js
3
+ Returns a summary of reports.
4
+ */
5
+
6
+ // ########## IMPORTS
7
+
8
+ // Module to keep secrets.
9
+ require('dotenv').config();
10
+ // Module to perform common operations.
11
+ const {getFileID} = require('./procs/util');
12
+
13
+ // ########## FUNCTIONS
14
+
15
+ // Returns a summary.
16
+ exports.summarize = (what, reports) => {
17
+ const data = reports.map(report => {
18
+ const {id, jobData, score, sources} = report;
19
+ const order = sources && sources.order || '';
20
+ const target = sources && sources.target || '';
21
+ return {
22
+ id: id || '',
23
+ endTime: jobData && jobData.endTime || '',
24
+ order: order || '',
25
+ target,
26
+ score: score && score.summary && score.summary.total || null
27
+ };
28
+ });
29
+ const summary = {
30
+ id: getFileID(2),
31
+ what,
32
+ data
33
+ };
34
+ return summary;
35
+ };
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
+ };