@mikitasazan/notify 1.4.5 → 1.6.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/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -3
- package/dist/render.js +55 -59
- package/dist/trend.d.ts +23 -0
- package/dist/trend.js +39 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export type { EventType, NotifyEvent, Project } from './events.ts';
|
|
|
2
2
|
export { severity } from './events.ts';
|
|
3
3
|
export { notify, sendReport } from './send.ts';
|
|
4
4
|
export { render } from './render.ts';
|
|
5
|
+
export { trend } from './trend.ts';
|
|
5
6
|
export type { SendResult } from './send.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export { severity } from "./events.js";
|
|
2
2
|
export { notify, sendReport } from "./send.js";
|
|
3
|
-
// `render`
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// `render` is exported so a sender can put on disk EXACTLY the card that was
|
|
4
|
+
// sent rather than its own second version of the text. A hand-built copy had
|
|
5
|
+
// already drifted from what went out.
|
|
6
6
|
export { render } from "./render.js";
|
|
7
|
+
// `trend` is exported for the same reason one floor down: the shape of a
|
|
8
|
+
// number is the package's, not each sender's. Two report senders had grown two
|
|
9
|
+
// dialects for one thing.
|
|
10
|
+
export { trend } from "./trend.js";
|
package/dist/render.js
CHANGED
|
@@ -345,14 +345,21 @@ const twoBlocks = (run, change) => {
|
|
|
345
345
|
// report and asked what "open" was — the answer is the report itself, which was
|
|
346
346
|
// sitting three lines above as dead text. So line 2 takes an optional URL and
|
|
347
347
|
// the action text becomes the link: `Report: <a>Analytics for 12.08</a>`.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
348
|
+
// `aside` is the one qualifier an identifier needs to be readable on its own —
|
|
349
|
+
// which day a report covers, which day its arrows are measured against. It
|
|
350
|
+
// rides in brackets ON the type line instead of taking a row of its own,
|
|
351
|
+
// because a row of its own reads as another fact about the subject rather than
|
|
352
|
+
// as part of the name.
|
|
353
|
+
const typeLine = (icon, type, action, url, aside) => {
|
|
354
|
+
// `field` returns null on an empty value, and interpolating null into a
|
|
355
|
+
// template prints the word "null". That is how line 2 of a card became
|
|
356
|
+
// `ℹ️ null` — reachable through `--json` and through a direct call from JS,
|
|
357
|
+
// where there are no types.
|
|
352
358
|
// `action || 'open'` in the linked case: an empty title must not swallow the
|
|
353
359
|
// link, which would be the one thing the card cannot afford to lose.
|
|
354
360
|
const line = url ? fieldLink(type, url, action || 'open') : field(type, action);
|
|
355
|
-
|
|
361
|
+
const tail = aside ? ` (${esc(aside)})` : '';
|
|
362
|
+
return line === null ? `${icon} <b>${esc(cap(type))}</b>${tail}` : `${icon} ${line}${tail}`;
|
|
356
363
|
};
|
|
357
364
|
// `workflowUrl ?? url`: половина отправителей шлёт ссылку на прогон под именем
|
|
358
365
|
// `--url` — это имя было в пакете раньше и осталось в вызовах. Рендер читал
|
|
@@ -386,8 +393,10 @@ const renderDeploy = (e) => {
|
|
|
386
393
|
const icon = iconFor(e);
|
|
387
394
|
const runUrl = e.workflowUrl ?? e.url;
|
|
388
395
|
return join([
|
|
389
|
-
|
|
390
|
-
|
|
396
|
+
// Имя того, чем выкатили, — на строке типа. Исход говорят значок и третий
|
|
397
|
+
// тег; повторять его словом нечего, и это тот же закон, что у задачи и у
|
|
398
|
+
// отчёта. Строки `Via` больше нет: она несла это имя этажом ниже.
|
|
399
|
+
typeLine(icon, 'Deploy', mechanism(e.workflowName, e.via, runUrl) ?? e.status, runUrl),
|
|
391
400
|
...twoBlocks([field('Target', e.target), field('Reason', e.note)], [fieldLink('Commit', e.commitUrl, e.commit), titleField(e.commitTitle), bodyQuote(e.commitBody)])
|
|
392
401
|
]);
|
|
393
402
|
};
|
|
@@ -436,22 +445,15 @@ const renderJob = (e) => {
|
|
|
436
445
|
const renderReport = (e) => {
|
|
437
446
|
if (e.groups && e.groups.length > 0) {
|
|
438
447
|
const body = e.groups.flatMap((g, i) => (i === 0 ? renderGroup(g) : ['', ...renderGroup(g)]));
|
|
439
|
-
// `lines`
|
|
440
|
-
//
|
|
441
|
-
//
|
|
448
|
+
// `lines` AND `groups` together, not one or the other: the branch with
|
|
449
|
+
// groups used to print ONLY the groups, and the report's numbers vanished
|
|
450
|
+
// without a word.
|
|
442
451
|
const numbers = labelled(e.lines);
|
|
443
452
|
return join([
|
|
444
|
-
typeLine(iconFor(e), 'Report', e.title, e.url),
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
// Владелец: «период пошёл не туда, он же должен быть рядом с датой».
|
|
449
|
-
field('Period', e.period),
|
|
450
|
-
// Строки БЕЗ группы идут вплотную к шапке, а не отдельным куском под
|
|
451
|
-
// пустой строкой: это факты про сам отчёт — период, номер, — а не про
|
|
452
|
-
// то, о чём он. Владелец: «number отдельно и период отдельно, непонятно,
|
|
453
|
-
// куда их относить». `labelled` сам ставит пустую строку перед первой
|
|
454
|
-
// группой, поэтому её здесь больше нет.
|
|
453
|
+
typeLine(iconFor(e), 'Report', e.title, e.url, e.period),
|
|
454
|
+
// Rows with no group of their own sit flush against the header instead of
|
|
455
|
+
// forming a separate slab under a blank line. `labelled` puts the blank
|
|
456
|
+
// line before the first group itself, so there is none here.
|
|
455
457
|
...numbers,
|
|
456
458
|
body.length > 0 ? '' : null,
|
|
457
459
|
...body
|
|
@@ -461,10 +463,8 @@ const renderReport = (e) => {
|
|
|
461
463
|
return join([
|
|
462
464
|
// Both analytics jobs send a link to the day's snapshot in docs/. It used to
|
|
463
465
|
// hang off a trailing `Details: open` row; now it is the report's own name.
|
|
464
|
-
typeLine(iconFor(e), 'Report', e.title, e.url),
|
|
465
|
-
//
|
|
466
|
-
field('Period', e.period),
|
|
467
|
-
// Вплотную к шапке — см. соседнюю ветку.
|
|
466
|
+
typeLine(iconFor(e), 'Report', e.title, e.url, e.period),
|
|
467
|
+
// Flush against the header — see the branch above.
|
|
468
468
|
...labelled(e.lines),
|
|
469
469
|
items.length > 0 ? '' : null,
|
|
470
470
|
...items
|
|
@@ -478,61 +478,54 @@ const renderCi = (e) => {
|
|
|
478
478
|
const icon = iconFor(e);
|
|
479
479
|
const runUrl = e.workflowUrl ?? e.url;
|
|
480
480
|
return join([
|
|
481
|
-
typeLine(icon, 'CI', e.status),
|
|
482
|
-
fieldLink('Check', runUrl, mechanism(e.workflowName, undefined, runUrl)),
|
|
481
|
+
typeLine(icon, 'CI', mechanism(e.workflowName, undefined, runUrl) ?? e.status, runUrl),
|
|
483
482
|
...twoBlocks([field('Actor', e.actor), field('Reason', e.note)], [fieldLink('Commit', e.commitUrl, e.commit), titleField(e.commitTitle), bodyQuote(e.commitBody)])
|
|
484
483
|
]);
|
|
485
484
|
};
|
|
485
|
+
// A pull request and an issue are identified the way GitHub itself identifies
|
|
486
|
+
// them: `#118 <title>`, one string, and it is the link. It used to take three
|
|
487
|
+
// rows — the action on line 2, `Number:` under it, `Title:` under that — so
|
|
488
|
+
// the thing the card is about could not be read without reading three lines.
|
|
489
|
+
// The action is not repeated in words: the icon carries it, and no two actions
|
|
490
|
+
// of one type share an icon.
|
|
491
|
+
const named = (number, title) => title ? `#${number} ${title}` : `#${number}`;
|
|
486
492
|
const renderPr = (e) => join([
|
|
487
|
-
typeLine(iconFor(e), 'PR', e.
|
|
488
|
-
'',
|
|
489
|
-
// Идентификатор первым, заголовок под ним: так вещь читается «#118, вот
|
|
490
|
-
// такая», а не «вот такая, кстати #118» — и так её пишет сам GitHub.
|
|
491
|
-
fieldLink('Number', e.url, `#${e.number}`),
|
|
492
|
-
titleField(e.title),
|
|
493
|
+
typeLine(iconFor(e), 'PR', named(e.number, e.title), e.url),
|
|
493
494
|
bodyQuote(e.body),
|
|
494
|
-
// Без пустой строки перед автором: у задачи её нет, и одно и то же поле
|
|
495
|
-
// не должно стоять по-разному в двух соседних карточках. Пустая строка в
|
|
496
|
-
// этом формате означает «дальше указатель, куда пойти» — автор не он.
|
|
497
495
|
field('Author', e.author),
|
|
498
496
|
field('Reviewer', e.reviewer)
|
|
499
497
|
]);
|
|
500
498
|
const renderIssue = (e) => join([
|
|
501
|
-
typeLine(iconFor(e), 'Issue', e.
|
|
502
|
-
'',
|
|
503
|
-
fieldLink('Number', e.url, `#${e.number}`),
|
|
504
|
-
titleField(e.title),
|
|
499
|
+
typeLine(iconFor(e), 'Issue', named(e.number, e.title), e.url),
|
|
505
500
|
bodyQuote(e.body),
|
|
506
501
|
field('Author', e.author),
|
|
507
502
|
field('Assignee', e.assignee)
|
|
508
503
|
]);
|
|
504
|
+
// The incident's own title IS line 2, exactly as an issue's is. It used to say
|
|
505
|
+
// the word `open` there — which the 🚨 already says, and no other card repeats
|
|
506
|
+
// its icon in words — with the real title one row below under a `Title:` label.
|
|
507
|
+
//
|
|
508
|
+
// `detail` is a diagnosis of several lines (vault greps three of them plus a
|
|
509
|
+
// log path). It used to go through `field`, which keeps only the first line, so
|
|
510
|
+
// every alarm this package ever sent arrived gutted. It is quoted now, the same
|
|
511
|
+
// shape a commit body takes.
|
|
509
512
|
const renderIncident = (e) => join([
|
|
510
|
-
typeLine(iconFor(e), 'Incident',
|
|
511
|
-
'',
|
|
512
|
-
// `detail` is a diagnosis of several lines (vault greps three of them plus a
|
|
513
|
-
// log path). It used to go through `field`, which keeps only the first line,
|
|
514
|
-
// so every alarm this package ever sent arrived gutted. Same shape as a
|
|
515
|
-
// commit now: short label, full text quoted under it.
|
|
516
|
-
// Ярлык `Title`, а не `Reason`: у аварии заголовок — такой же заголовок,
|
|
517
|
-
// как у коммита и задачи, и называться в одной карточке он должен так же.
|
|
518
|
-
// Same rule as the report: the link rides on the incident's own title
|
|
519
|
-
// rather than on a trailing row whose only text is the word `open`.
|
|
520
|
-
fieldLink('Title', e.url, e.title),
|
|
513
|
+
typeLine(iconFor(e), 'Incident', e.title, e.url),
|
|
521
514
|
e.detail && e.detail !== e.title ? note(e.detail) : null,
|
|
522
515
|
e.logs ? '' : null,
|
|
523
516
|
fieldCode('Logs', e.logs)
|
|
524
517
|
]);
|
|
525
|
-
// Раньше всё это склеивалось в одну строку `Reason:` через тире: «имя — no
|
|
526
|
-
// reports — expected X, last seen Y». Каждая другая карточка кладёт факт на
|
|
527
|
-
// свою строку с ярлыком, и владелец справедливо спросил, зачем тут отдельный
|
|
528
|
-
// формат. Отдельного формата больше нет.
|
|
529
518
|
// A session in trouble. Same law as every other card: identifier first, then
|
|
530
519
|
// the facts as fields, then his own words as a quote — never as a field, which
|
|
531
520
|
// keeps one line and clipped the name of the very session the card is about.
|
|
521
|
+
//
|
|
522
|
+
// A session has no name, so line 2 says what happened to it. The 36-character
|
|
523
|
+
// id is not printed: he cannot type it, cannot search it and cannot act on it.
|
|
524
|
+
// It is still in the card — inside the `rm` command at the bottom, which is the
|
|
525
|
+
// one place it is of any use.
|
|
532
526
|
const renderSession = (e) => join([
|
|
533
527
|
typeLine(iconFor(e), 'Session', e.action),
|
|
534
528
|
'',
|
|
535
|
-
field('Id', e.id),
|
|
536
529
|
field('Project', e.workdir),
|
|
537
530
|
field('Reason', e.reason),
|
|
538
531
|
e.opened ? '' : null,
|
|
@@ -544,9 +537,12 @@ const renderHeartbeatMiss = (e) => {
|
|
|
544
537
|
const icon = iconFor(e);
|
|
545
538
|
const action = e.recovered ? 'ok' : 'miss';
|
|
546
539
|
return join([
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
540
|
+
// The task's name on the type line, exactly as a job card carries it. The
|
|
541
|
+
// `Task:` row said the same thing a floor below. No sender in any
|
|
542
|
+
// repository builds this event any more — the silence watchdog sends an
|
|
543
|
+
// ordinary job with `--status silent` — but a machine still running the old
|
|
544
|
+
// copy of that watchdog can, and the card it gets must obey the template.
|
|
545
|
+
typeLine(icon, 'Heartbeat', e.job, undefined, action),
|
|
550
546
|
field('Reason', e.note),
|
|
551
547
|
field('Expected', e.expected),
|
|
552
548
|
field(e.recovered ? 'Last run' : 'Last seen', e.lastSeen)
|
package/dist/trend.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One number, written the one way every card writes a number.
|
|
3
|
+
*
|
|
4
|
+
* Two report senders each grew their own dialect: one printed `485 ▲207` and
|
|
5
|
+
* `0 =`, the other printed `210 +3` for a real comparison and `+37` for a
|
|
6
|
+
* plain count of what happened today — a plus sign in front of a number that
|
|
7
|
+
* was never compared to anything. Inside a single group `Removed: 3` stood
|
|
8
|
+
* next to `Added: +6`, the same kind of fact in two shapes.
|
|
9
|
+
*
|
|
10
|
+
* So the shape is not a sender's business any more. It lives here, one
|
|
11
|
+
* implementation, and every report calls it:
|
|
12
|
+
*
|
|
13
|
+
* trend(210, 207) → '210 ▲3'
|
|
14
|
+
* trend(202, 207) → '202 ▼5'
|
|
15
|
+
* trend(0, 0) → '0 ='
|
|
16
|
+
* trend(37) → '37' nothing to compare to, so no mark
|
|
17
|
+
* trend(4.4, 3.6, '%') → '4.4% ▲0.8' the mark is in the unit left of it
|
|
18
|
+
*
|
|
19
|
+
* The rule the owner asked for, in one line: where there is data to compare
|
|
20
|
+
* against, the arrow is printed; where there is none, nothing is printed —
|
|
21
|
+
* never a sign that only looks like a comparison.
|
|
22
|
+
*/
|
|
23
|
+
export declare const trend: (now: number, was?: number, unit?: string) => string;
|
package/dist/trend.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One number, written the one way every card writes a number.
|
|
3
|
+
*
|
|
4
|
+
* Two report senders each grew their own dialect: one printed `485 ▲207` and
|
|
5
|
+
* `0 =`, the other printed `210 +3` for a real comparison and `+37` for a
|
|
6
|
+
* plain count of what happened today — a plus sign in front of a number that
|
|
7
|
+
* was never compared to anything. Inside a single group `Removed: 3` stood
|
|
8
|
+
* next to `Added: +6`, the same kind of fact in two shapes.
|
|
9
|
+
*
|
|
10
|
+
* So the shape is not a sender's business any more. It lives here, one
|
|
11
|
+
* implementation, and every report calls it:
|
|
12
|
+
*
|
|
13
|
+
* trend(210, 207) → '210 ▲3'
|
|
14
|
+
* trend(202, 207) → '202 ▼5'
|
|
15
|
+
* trend(0, 0) → '0 ='
|
|
16
|
+
* trend(37) → '37' nothing to compare to, so no mark
|
|
17
|
+
* trend(4.4, 3.6, '%') → '4.4% ▲0.8' the mark is in the unit left of it
|
|
18
|
+
*
|
|
19
|
+
* The rule the owner asked for, in one line: where there is data to compare
|
|
20
|
+
* against, the arrow is printed; where there is none, nothing is printed —
|
|
21
|
+
* never a sign that only looks like a comparison.
|
|
22
|
+
*/
|
|
23
|
+
/** Integers stay integers; anything else keeps one decimal. */
|
|
24
|
+
const fmt = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(1));
|
|
25
|
+
/**
|
|
26
|
+
* Two values that round to the same first decimal are equal: `4.42%` against
|
|
27
|
+
* `4.44%` is not movement, it is noise, and `▲0.0` reads as a lie.
|
|
28
|
+
*/
|
|
29
|
+
const same = (a, b) => Math.abs(a - b) < 0.05;
|
|
30
|
+
export const trend = (now, was, unit = '') => {
|
|
31
|
+
const head = `${fmt(now)}${unit}`;
|
|
32
|
+
if (was === undefined || !Number.isFinite(was)) {
|
|
33
|
+
return head;
|
|
34
|
+
}
|
|
35
|
+
if (same(now, was)) {
|
|
36
|
+
return `${head} =`;
|
|
37
|
+
}
|
|
38
|
+
return now > was ? `${head} ▲${fmt(now - was)}` : `${head} ▼${fmt(was - now)}`;
|
|
39
|
+
};
|
package/package.json
CHANGED