@pipeline-builder/pipeline-data 4.1.23 → 4.1.25
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/lib/api/alert-destinations.d.ts +17 -0
- package/lib/api/alert-destinations.js +36 -0
- package/lib/api/reporting/analytics-queries.d.ts +274 -0
- package/lib/api/reporting/analytics-queries.js +598 -0
- package/lib/api/reporting/daily-rollup.d.ts +41 -0
- package/lib/api/reporting/daily-rollup.js +173 -0
- package/lib/api/reporting/deploy-rollup.d.ts +78 -0
- package/lib/api/reporting/deploy-rollup.js +203 -0
- package/lib/api/reporting/dora-scoring.d.ts +14 -0
- package/lib/api/reporting/dora-scoring.js +25 -1
- package/lib/api/reporting/dora.js +30 -6
- package/lib/api/reporting/failure-classifier.d.ts +54 -0
- package/lib/api/reporting/failure-classifier.js +179 -0
- package/lib/api/reporting/ingest.d.ts +2 -2
- package/lib/api/reporting/ingest.js +129 -4
- package/lib/api/reporting/last-deploy.d.ts +31 -0
- package/lib/api/reporting/last-deploy.js +93 -0
- package/lib/api/reporting/needs-attention.d.ts +93 -0
- package/lib/api/reporting/needs-attention.js +152 -0
- package/lib/api/reporting/plugin-exposure.d.ts +129 -0
- package/lib/api/reporting/plugin-exposure.js +312 -0
- package/lib/api/reporting/retention-sweep.js +9 -1
- package/lib/api/reporting/sql-helpers.d.ts +35 -2
- package/lib/api/reporting/sql-helpers.js +39 -1
- package/lib/api/reporting/stakeholder/compose.d.ts +115 -0
- package/lib/api/reporting/stakeholder/compose.js +234 -0
- package/lib/api/reporting/stakeholder/index.d.ts +14 -0
- package/lib/api/reporting/stakeholder/index.js +17 -0
- package/lib/api/reporting/stakeholder/period.d.ts +102 -0
- package/lib/api/reporting/stakeholder/period.js +273 -0
- package/lib/api/reporting/stakeholder/report-html.d.ts +93 -0
- package/lib/api/reporting/stakeholder/report-html.js +208 -0
- package/lib/api/reporting/stakeholder/sections.d.ts +117 -0
- package/lib/api/reporting/stakeholder/sections.js +249 -0
- package/lib/api/reporting/stakeholder/store.d.ts +450 -0
- package/lib/api/reporting/stakeholder/store.js +981 -0
- package/lib/api/reporting/stakeholder/templates.d.ts +21 -0
- package/lib/api/reporting/stakeholder/templates.js +104 -0
- package/lib/api/reporting/types.d.ts +73 -0
- package/lib/api/reporting/types.js +1 -1
- package/lib/api/reporting-service.d.ts +94 -5
- package/lib/api/reporting-service.js +169 -10
- package/lib/api/soft-delete-sweep.d.ts +11 -2
- package/lib/api/soft-delete-sweep.js +17 -6
- package/lib/database/drizzle-schema.d.ts +2263 -385
- package/lib/database/drizzle-schema.js +18 -7
- package/lib/database/index.d.ts +1 -1
- package/lib/database/index.js +4 -2
- package/lib/database/schema/compliance.d.ts +0 -682
- package/lib/database/schema/compliance.js +6 -66
- package/lib/database/schema/ecosystem.d.ts +20 -157
- package/lib/database/schema/ecosystem.js +23 -16
- package/lib/database/schema/pipeline.d.ts +200 -0
- package/lib/database/schema/pipeline.js +100 -1
- package/lib/database/schema/plugin.d.ts +20 -1
- package/lib/database/schema/plugin.js +13 -1
- package/lib/database/schema/reporting-analytics.d.ts +1212 -0
- package/lib/database/schema/reporting-analytics.js +250 -0
- package/lib/database/schema/reporting-stakeholder.d.ts +1405 -0
- package/lib/database/schema/reporting-stakeholder.js +251 -0
- package/lib/index.d.ts +10 -1
- package/lib/index.js +15 -1
- package/lib/testing/exports.json +67 -4
- package/package.json +4 -3
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/** HTML-escape. Applied at every insertion point; see the file header. */
|
|
4
|
+
function esc(value) {
|
|
5
|
+
return String(value ?? '')
|
|
6
|
+
.replace(/&/g, '&')
|
|
7
|
+
.replace(/</g, '<')
|
|
8
|
+
.replace(/>/g, '>')
|
|
9
|
+
.replace(/"/g, '"')
|
|
10
|
+
.replace(/'/g, ''');
|
|
11
|
+
}
|
|
12
|
+
/** A date as a plain calendar day, in UTC, or `—` when absent. */
|
|
13
|
+
function day(iso) {
|
|
14
|
+
if (!iso)
|
|
15
|
+
return '—';
|
|
16
|
+
const d = new Date(iso);
|
|
17
|
+
if (Number.isNaN(d.getTime()))
|
|
18
|
+
return '—';
|
|
19
|
+
return d.toISOString().slice(0, 10);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The trend, IN WORDS.
|
|
23
|
+
*
|
|
24
|
+
* "down 3 points" rather than a red arrow, because the arrow is the whole fact for a
|
|
25
|
+
* section whose headline barely moved, and an arrow is invisible to a screen reader and
|
|
26
|
+
* ambiguous in greyscale. `unknown` renders nothing at all rather than "flat" — a section
|
|
27
|
+
* with no comparable previous period has not held steady, we just cannot say.
|
|
28
|
+
*/
|
|
29
|
+
export function trendSentence(section) {
|
|
30
|
+
const { change, headline } = section;
|
|
31
|
+
if (!change || change.direction === 'unknown')
|
|
32
|
+
return null;
|
|
33
|
+
const unit = headline?.unit === '%' ? 'points' : null;
|
|
34
|
+
const magnitude = Math.abs(change.absolute);
|
|
35
|
+
if (change.direction === 'flat')
|
|
36
|
+
return 'unchanged from the previous period';
|
|
37
|
+
const word = change.direction === 'up' ? 'up' : 'down';
|
|
38
|
+
const pct = change.percent !== null && change.percent !== undefined
|
|
39
|
+
? ` (${change.percent > 0 ? '+' : ''}${change.percent}%)`
|
|
40
|
+
: '';
|
|
41
|
+
return `${word} ${magnitude}${unit ? ` ${unit}` : ''}${pct} from the previous period`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One section as a table row.
|
|
45
|
+
*
|
|
46
|
+
* A TABLE, not a grid of cards, because that is what makes the PDF's structure tree
|
|
47
|
+
* navigable: a screen reader announces "Build success rate, 91%, down 3 points from the
|
|
48
|
+
* previous period" from the row's association with its headers. Cards would print the
|
|
49
|
+
* same ink and carry none of that.
|
|
50
|
+
*/
|
|
51
|
+
function sectionRow(section) {
|
|
52
|
+
const title = esc(section.title);
|
|
53
|
+
if (section.state === 'locked') {
|
|
54
|
+
const feature = section.requiresFeature ? esc(section.requiresFeature) : 'an add-on';
|
|
55
|
+
return `<tr><th scope="row">${title}</th>`
|
|
56
|
+
+ '<td class="muted">Not included</td>'
|
|
57
|
+
+ `<td class="muted">Not on your plan — needs ${feature}. The rest of this report is unaffected.</td></tr>`;
|
|
58
|
+
}
|
|
59
|
+
if (section.state === 'failed') {
|
|
60
|
+
return `<tr><th scope="row">${title}</th>`
|
|
61
|
+
+ '<td class="muted">Unavailable</td>'
|
|
62
|
+
+ '<td class="muted">Could not be computed for this period.</td></tr>';
|
|
63
|
+
}
|
|
64
|
+
const { headline } = section;
|
|
65
|
+
// A section with no single number is not an error and must not read like one: several
|
|
66
|
+
// legitimately have none (a breakdown, a list of offenders).
|
|
67
|
+
const value = headline
|
|
68
|
+
? `${esc(headline.value)}${esc(headline.unit ?? '')}`
|
|
69
|
+
: '—';
|
|
70
|
+
const label = headline?.label ? esc(headline.label) : 'No single headline for this section';
|
|
71
|
+
const trend = trendSentence(section);
|
|
72
|
+
const note = trend ? `${label} · ${esc(trend)}` : label;
|
|
73
|
+
return `<tr><th scope="row">${title}</th><td class="value">${value}</td><td>${note}</td></tr>`;
|
|
74
|
+
}
|
|
75
|
+
/** The data-quality notes, which are the difference between "0 deploys" and "we cannot see your deploys". */
|
|
76
|
+
function notesList(notes) {
|
|
77
|
+
if (notes.length === 0)
|
|
78
|
+
return '';
|
|
79
|
+
const items = notes.map((n) => `<li>${esc(n.message)}</li>`).join('');
|
|
80
|
+
return '<section class="block"><h2>What to know about these numbers</h2>'
|
|
81
|
+
+ `<ul class="notes">${items}</ul></section>`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Paragraphs from free text.
|
|
85
|
+
*
|
|
86
|
+
* Blank-line separated, and NOT a single `<p>` with newlines: a lead who wrote three
|
|
87
|
+
* paragraphs gets three, and the PDF's structure tree gets three, instead of one wall
|
|
88
|
+
* that a screen reader reads as a single utterance.
|
|
89
|
+
*/
|
|
90
|
+
function paragraphs(text) {
|
|
91
|
+
return text
|
|
92
|
+
.split(/\n{2,}/)
|
|
93
|
+
.map((p) => p.trim())
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
.map((p) => `<p>${esc(p).replace(/\n/g, '<br />')}</p>`)
|
|
96
|
+
.join('');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The print stylesheet.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately small and deliberately not the product's design system: this document is
|
|
102
|
+
* printed once, to paper size, by one engine. Importing the app's CSS would drag in a web
|
|
103
|
+
* font (a network fetch the renderer forbids) and a dark-mode block that would render a
|
|
104
|
+
* manager's PDF black.
|
|
105
|
+
*
|
|
106
|
+
* `@page` owns the margins so Chromium's own `margin` option does not have to, which keeps
|
|
107
|
+
* the page geometry in the document rather than split across two places.
|
|
108
|
+
*/
|
|
109
|
+
const STYLE = `
|
|
110
|
+
:root { color-scheme: only light; }
|
|
111
|
+
* { box-sizing: border-box; }
|
|
112
|
+
@page { size: A4; margin: 18mm 16mm 20mm; }
|
|
113
|
+
body {
|
|
114
|
+
margin: 0; background: #ffffff; color: #111827;
|
|
115
|
+
font: 11pt/1.45 -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
116
|
+
}
|
|
117
|
+
h1 { font-size: 20pt; margin: 0 0 2mm; }
|
|
118
|
+
h2 { font-size: 12pt; margin: 0 0 2mm; }
|
|
119
|
+
p { margin: 0 0 2.5mm; }
|
|
120
|
+
.sub { color: #4b5563; font-size: 9.5pt; margin: 0 0 6mm; }
|
|
121
|
+
.block { margin: 0 0 7mm; page-break-inside: avoid; }
|
|
122
|
+
table { width: 100%; border-collapse: collapse; font-size: 10pt; }
|
|
123
|
+
caption { text-align: left; font-weight: 600; font-size: 12pt; padding: 0 0 2mm; }
|
|
124
|
+
th, td { text-align: left; vertical-align: top; padding: 2mm 2mm 2mm 0; border-bottom: 1px solid #e5e7eb; }
|
|
125
|
+
thead th { border-bottom: 1.5px solid #9ca3af; font-size: 9pt; text-transform: uppercase; letter-spacing: .04em; }
|
|
126
|
+
tbody th { font-weight: 600; width: 42%; }
|
|
127
|
+
td.value { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; width: 18%; }
|
|
128
|
+
.muted { color: #4b5563; }
|
|
129
|
+
.notes { margin: 0; padding-left: 5mm; }
|
|
130
|
+
.notes li { margin: 0 0 1.5mm; }
|
|
131
|
+
.footer { margin-top: 8mm; padding-top: 3mm; border-top: 1px solid #e5e7eb; color: #4b5563; font-size: 8.5pt; }
|
|
132
|
+
.footer p { margin: 0 0 1mm; }
|
|
133
|
+
tr { page-break-inside: avoid; }
|
|
134
|
+
`;
|
|
135
|
+
/**
|
|
136
|
+
* Render one report as a complete HTML document.
|
|
137
|
+
*
|
|
138
|
+
* ORDER IS MEANING, and it is the same order the shared web page uses: the lead's words,
|
|
139
|
+
* then the executive summary, then the numbers. A reader who meets the table first has
|
|
140
|
+
* already formed a view of the dip by the time they reach the sentence explaining it, so
|
|
141
|
+
* the context the data cannot supply has to come first. `a11y-stakeholder-reports.test.tsx`
|
|
142
|
+
* pins that ordering for the page; `report-html.test.ts` pins it here.
|
|
143
|
+
*
|
|
144
|
+
* A NULL SNAPSHOT STILL RENDERS. A run can be read before or without a snapshot (a failed
|
|
145
|
+
* compose, a draft), and answering an empty file would look like a broken download. The
|
|
146
|
+
* document says there is nothing to show instead.
|
|
147
|
+
*/
|
|
148
|
+
export function renderReportHtml(input) {
|
|
149
|
+
const snapshot = input.snapshot;
|
|
150
|
+
const sections = snapshot?.sections ?? [];
|
|
151
|
+
const lang = 'en';
|
|
152
|
+
const head = [
|
|
153
|
+
'<!DOCTYPE html>',
|
|
154
|
+
`<html lang="${lang}"><head><meta charset="utf-8" />`,
|
|
155
|
+
// No external anything: the one directive that makes the promise in the file header
|
|
156
|
+
// enforceable by the engine rather than by review.
|
|
157
|
+
'<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; style-src \'unsafe-inline\'" />',
|
|
158
|
+
`<title>${esc(input.title)} — ${esc(input.periodLabel)}</title>`,
|
|
159
|
+
`<style>${STYLE}</style>`,
|
|
160
|
+
'</head><body>',
|
|
161
|
+
].join('');
|
|
162
|
+
const version = input.version > 1 ? ` · revision ${esc(input.version)}` : '';
|
|
163
|
+
const published = input.publishedAt ? ` · published ${esc(day(input.publishedAt))}` : '';
|
|
164
|
+
const header = `<h1>${esc(input.title)}</h1>`
|
|
165
|
+
+ `<p class="sub">${esc(input.periodLabel)} · ${esc(day(input.periodStart))} to `
|
|
166
|
+
+ `${esc(day(input.periodEnd))}${version}${published}</p>`;
|
|
167
|
+
const summary = input.executiveSummary
|
|
168
|
+
? `<section class="block"><h2>Executive summary</h2>${paragraphs(input.executiveSummary)}</section>`
|
|
169
|
+
: '';
|
|
170
|
+
const notes = input.leadNotes
|
|
171
|
+
? `<section class="block"><h2>Notes from the team</h2>${paragraphs(input.leadNotes)}</section>`
|
|
172
|
+
: '';
|
|
173
|
+
const body = sections.length > 0
|
|
174
|
+
? '<section class="block"><table>'
|
|
175
|
+
+ `<caption>Delivery measures for ${esc(input.periodLabel)}</caption>`
|
|
176
|
+
+ '<thead><tr><th scope="col">Measure</th><th scope="col">Value</th><th scope="col">Detail</th></tr></thead>'
|
|
177
|
+
+ `<tbody>${sections.map(sectionRow).join('')}</tbody></table></section>`
|
|
178
|
+
: '<section class="block"><h2>No measures</h2>'
|
|
179
|
+
+ '<p class="muted">This report has no computed sections. Nothing was measured for this period.</p></section>';
|
|
180
|
+
const footerLines = [];
|
|
181
|
+
if (input.namesRedacted) {
|
|
182
|
+
footerLines.push('Project and pipeline names have been replaced in this copy.');
|
|
183
|
+
}
|
|
184
|
+
if (snapshot?.methodology)
|
|
185
|
+
footerLines.push(snapshot.methodology);
|
|
186
|
+
if (snapshot?.generatedAt) {
|
|
187
|
+
footerLines.push(`Figures computed ${day(snapshot.generatedAt)} and frozen for the period shown.`);
|
|
188
|
+
}
|
|
189
|
+
if (input.expiresAt) {
|
|
190
|
+
footerLines.push(`The link this copy came from expires on ${day(input.expiresAt)}.`);
|
|
191
|
+
}
|
|
192
|
+
footerLines.push(`This file was created ${day(input.renderedAt)}.`);
|
|
193
|
+
const footer = `<footer class="footer">${footerLines.map((l) => `<p>${esc(l)}</p>`).join('')}</footer>`;
|
|
194
|
+
return `${head}${header}${notes}${summary}${body}${notesList(snapshot?.notes ?? [])}${footer}</body></html>`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The download's file name.
|
|
198
|
+
*
|
|
199
|
+
* Built from the period and the version rather than the run id, because the person saving
|
|
200
|
+
* it is filing it next to last month's, and `report-2026-W38-v2.pdf` sorts and reads while
|
|
201
|
+
* a uuid does not. Sanitizing is the ROUTE's job (`attachmentDisposition`) — this only
|
|
202
|
+
* chooses the words.
|
|
203
|
+
*/
|
|
204
|
+
export function reportFileName(title, periodLabel, version) {
|
|
205
|
+
const base = `${title} ${periodLabel}${version > 1 ? ` v${version}` : ''}`;
|
|
206
|
+
return `${base.trim()}.pdf`;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwb3J0LWh0bWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBpL3JlcG9ydGluZy9zdGFrZWhvbGRlci9yZXBvcnQtaHRtbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBa0V0QywwRUFBMEU7QUFDMUUsU0FBUyxHQUFHLENBQUMsS0FBYztJQUN6QixPQUFPLE1BQU0sQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDO1NBQ3ZCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDO1NBQ3RCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDO1NBQ3JCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDO1NBQ3JCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDO1NBQ3ZCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFDNUIsQ0FBQztBQUVELGtFQUFrRTtBQUNsRSxTQUFTLEdBQUcsQ0FBQyxHQUE4QjtJQUN6QyxJQUFJLENBQUMsR0FBRztRQUFFLE9BQU8sR0FBRyxDQUFDO0lBQ3JCLE1BQU0sQ0FBQyxHQUFHLElBQUksSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ3hCLElBQUksTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7UUFBRSxPQUFPLEdBQUcsQ0FBQztJQUMxQyxPQUFPLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ3RDLENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGFBQWEsQ0FBQyxPQUF3QjtJQUNwRCxNQUFNLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxHQUFHLE9BQU8sQ0FBQztJQUNyQyxJQUFJLENBQUMsTUFBTSxJQUFJLE1BQU0sQ0FBQyxTQUFTLEtBQUssU0FBUztRQUFFLE9BQU8sSUFB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SUFBSSxFQUFFLENBQUMsR0FBRyxNQUFNLGdCQUFnQixDQUFDO0FBQy9HLENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLGNBQWMsQ0FBQyxLQUFhLEVBQUUsV0FBbUIsRUFBRSxPQUFlO0lBQ2hGLE1BQU0sSUFBSSxHQUFHLEdBQUcsS0FBSyxJQUFJLFdBQVcsR0FBRyxPQUFPLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQztJQUMzRSxPQUFPLEdBQUcsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUM7QUFDOUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFRoZSBzbmFwc2hvdCwgYXMgb25lIHNlbGYtY29udGFpbmVkIEhUTUwgZG9jdW1lbnQg4oCUIHRoZSBpbnB1dCB0aGUgUERGIHJlbmRlcmVyIHByaW50cy5cbiAqXG4gKiBXSFkgSFRNTCBBTkQgTk9UIEEgUERGIExJQlJBUlkuIEEgbGF5b3V0IGVuZ2luZSB3ZSB3b3VsZCBoYXZlIHRvIHRlYWNoIGFib3V0IHBhZ2VcbiAqIGJyZWFrcywgdGFibGUgd2lkdGhzIGFuZCBmb250IG1ldHJpY3MgaXMgYSBzZWNvbmQgcmVuZGVyZXIgdG8gbWFpbnRhaW47IENocm9taXVtXG4gKiBhbHJlYWR5IGhhcyBvbmUuIFNvIHRoZSBwaXBlbGluZSBpcyBzbmFwc2hvdCAtPiBIVE1MIC0+IGBQYWdlLnByaW50VG9QREZgLCBhbmQgdGhpc1xuICogZmlsZSBvd25zIHRoZSBmaXJzdCBhcnJvdy4gSXQgaXMgcHVyZTogYSBzdHJpbmcgaW4sIGEgc3RyaW5nIG91dCwgbm8gSS9PLCBubyBjbG9jaywgbm9cbiAqIGRhdGFiYXNlLiBUaGF0IGlzIHdoYXQgbWFrZXMgdGhlIFBERidzIGNvbnRlbnQgdGVzdGFibGUgd2l0aG91dCBhIGJyb3dzZXIuXG4gKlxuICogU0VMRi1DT05UQUlORUQsIEFORCBUSEFUIElTIEEgU0VDVVJJVFkgUFJPUEVSVFksIG5vdCB0aWRpbmVzcy4gTm8gPHNjcmlwdD4sIG5vXG4gKiBzdHlsZXNoZWV0IGxpbmssIG5vIHdlYiBmb250LCBubyBpbWFnZSBVUkwg4oCUIGV2ZXJ5dGhpbmcgaXMgaW5saW5lLiBUaGUgcmVuZGVyZXIgbG9hZHNcbiAqIHRoaXMgdGhyb3VnaCBgc2V0Q29udGVudGAgd2l0aCB0aGUgcGFnZSBvZmZsaW5lLCBzbyBhIGRvY3VtZW50IHRoYXQgcmVmZXJlbmNlZCBhbnl0aGluZ1xuICogZXh0ZXJuYWwgd291bGQgZWl0aGVyIGhhbmcgb3IgcXVpZXRseSByZW5kZXIgd2l0aG91dCBpdC4gU3RhdGVkIGhlcmUgYmVjYXVzZSB0aGVcbiAqIHRlbXB0YXRpb24gdG8gYWRkIGEgbG9nbyBieSBVUkwgd2lsbCBjb21lIHVwLCBhbmQgaXQgd291bGQgYmUgdGhlIG9uZSBjaGFuZ2UgdGhhdCBnaXZlc1xuICogdGhlIHJlbmRlcmVyIGEgcmVhc29uIHRvIHRhbGsgdG8gdGhlIG5ldHdvcmsuXG4gKlxuICogRVZFUlkgSU5URVJQT0xBVElPTiBJUyBFU0NBUEVELiBUaGUgc25hcHNob3QgY2FycmllcyBvcmctY29udHJvbGxlZCB0ZXh0IOKAlCBwaXBlbGluZVxuICogbmFtZXMsIGEgbGVhZCdzIG5vdGVzLCBhIHNlY3Rpb24gdGl0bGUg4oCUIGludG8gbWFya3VwIHRoYXQgYSBicm93c2VyIHRoZW4gcGFyc2VzLiBUaGF0XG4gKiBpcyB0aGUgc2FtZSBib3VuZGFyeSBhcyBhbnkgWFNTIHNpbmssIGFuZCBpdCBkb2VzIG5vdCBzdG9wIG1hdHRlcmluZyBiZWNhdXNlIHRoZSBvdXRwdXRcbiAqIGlzIGEgUERGOiB1bmVzY2FwZWQgbm90ZXMgY291bGQgY2xvc2UgYSB0YWcgYW5kIHJld3JpdGUgdGhlIHJlc3Qgb2YgdGhlIHJlcG9ydCwgd2hpY2ggaXNcbiAqIGEgZG9jdW1lbnQtZm9yZ2VyeSBidWcgaW4gYSBkb2N1bWVudCBwZW9wbGUgbWFrZSBkZWNpc2lvbnMgZnJvbS4gYGVzYygpYCBpcyBhcHBsaWVkIGF0XG4gKiBldmVyeSBzaW5nbGUgaW5zZXJ0aW9uIHBvaW50IGFuZCB0aGVyZSBpcyBubyBcInRydXN0ZWRcIiBjYWxsZXIuXG4gKlxuICogTk8gQ09MT1VSLU9OTFkgTUVBTklORywgYW5kIG5vIGJhcmUgZ2x5cGggY2FycnlpbmcgaW5mb3JtYXRpb24gKFdDQUcgMS40LjEgLyAxLjEuMSkuIEFcbiAqIGxvY2tlZCBwYW5lbCBzYXlzIFwiTm90IG9uIHlvdXIgcGxhblwiIGluIHdvcmRzOyBhIHRyZW5kIHNheXMgXCJkb3duIDMgcG9pbnRzXCIgaW4gd29yZHNcbiAqIHJhdGhlciB0aGFuIGxlYW5pbmcgb24gYW4gYXJyb3cuIFRoZXJlIGFyZSBubyBjaGFydHMgaW4gYSBzbmFwc2hvdCB0b2RheSDigJQgZXZlcnkgc2VjdGlvblxuICogcmVuZGVycyBhcyBhIG51bWJlciwgYSB3b3JkIGFuZCBhIHRhYmxlIHJvdyDigJQgc28gdGhlIHBsYW4ncyBcImFsdCB0ZXh0IG9uIGNoYXJ0c1wiIGhhc1xuICogbm90aGluZyB0byBhdHRhY2ggdG8geWV0LiBUaGUgcnVsZSB0aGF0IHJlcGxhY2VzIGl0IGlzIHN0cmljdGVyIGFuZCBzdXJ2aXZlcyBhIGNoYXJ0XG4gKiBiZWluZyBhZGRlZCBsYXRlcjogaWYgYSByZWFkZXIgd2l0aCBubyBjb2xvdXIgdmlzaW9uLCBubyBpbWFnZXMgYW5kIGEgc2NyZWVuIHJlYWRlclxuICogY2Fubm90IGdldCBhIGZhY3Qgb3V0IG9mIHRoaXMgZG9jdW1lbnQsIHRoZSBmYWN0IGlzIG5vdCBpbiBpdC5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IENvbXBvc2VkU2VjdGlvbiwgRGF0YVF1YWxpdHlOb3RlLCBSZXBvcnRTbmFwc2hvdCB9IGZyb20gJy4vY29tcG9zZS5qcyc7XG5cbi8qKiBXaGF0IHRoZSByZW5kZXJlciBuZWVkcyB0byBwcm9kdWNlIG9uZSByZXBvcnQgZG9jdW1lbnQuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlcG9ydEh0bWxJbnB1dCB7XG4gIC8qKiBUaGUgZGVmaW5pdGlvbidzIG5hbWUuIEhlYWRzIHRoZSBkb2N1bWVudC4gKi9cbiAgdGl0bGU6IHN0cmluZztcbiAgcGVyaW9kTGFiZWw6IHN0cmluZztcbiAgcGVyaW9kU3RhcnQ6IHN0cmluZztcbiAgcGVyaW9kRW5kOiBzdHJpbmc7XG4gIC8qKiBSZWdlbmVyYXRpb25zIGFyZSBub3JtYWw7IGEgcmVhZGVyIG11c3QgYmUgYWJsZSB0byB0ZWxsIHdoaWNoIG9uZSB0aGV5IGhvbGQuICovXG4gIHZlcnNpb246IG51bWJlcjtcbiAgcHVibGlzaGVkQXQ/OiBzdHJpbmcgfCBudWxsO1xuICAvKiogVGhlIGxlYWQncyBvd24gd29yZHMuIFJlbmRlcmVkIEFCT1ZFIHRoZSBudW1iZXJzIOKAlCBzZWUgdGhlIG9yZGVyaW5nIG5vdGUgYmVsb3cuICovXG4gIGxlYWROb3Rlcz86IHN0cmluZyB8IG51bGw7XG4gIC8qKlxuICAgKiBUaGUgQUktZHJhZnRlZCBleGVjdXRpdmUgc3VtbWFyeSwgd2hlbiB0aGUgcnVuIGhhcyBvbmUgYW5kIHRoZSBjYWxsZXIgaXMgYWxsb3dlZCB0b1xuICAgKiBzZWUgaXQuIE5ldmVyIGZldGNoZWQgaGVyZTogYSBjYWxsZXIgdGhhdCBzaG91bGQgbm90IGRpc2Nsb3NlIGl0IHNpbXBseSBvbWl0cyBpdC5cbiAgICovXG4gIGV4ZWN1dGl2ZVN1bW1hcnk/OiBzdHJpbmcgfCBudWxsO1xuICBzbmFwc2hvdDogUmVwb3J0U25hcHNob3QgfCBudWxsO1xuICAvKiogVHJ1ZSB3aGVuIHBpcGVsaW5lL3Byb2plY3QgbmFtZXMgd2VyZSByZXBsYWNlZCDigJQgc2FpZCBpbiB0aGUgZG9jdW1lbnQsIG5vdCBpbXBsaWVkLiAqL1xuICBuYW1lc1JlZGFjdGVkPzogYm9vbGVhbjtcbiAgLyoqIFNob3duIGluIHRoZSBmb290ZXIgb2YgYSBzaGFyZWQgY29weSwgc28gYSBib29rbWFya2VkIGxpbmsncyBkZWF0aCBpcyBub3QgYSBzdXJwcmlzZS4gKi9cbiAgZXhwaXJlc0F0Pzogc3RyaW5nIHwgbnVsbDtcbiAgLyoqXG4gICAqIFN0YW1wZWQgaW4gdGhlIGZvb3RlciBhcyB0aGUgbW9tZW50IHRoZSBGSUxFIHdhcyBtYWRlLCBkaXN0aW5jdCBmcm9tIHRoZSBzbmFwc2hvdCdzXG4gICAqIG93biBgZ2VuZXJhdGVkQXRgLiBUd28gZGF0ZXMgYmVjYXVzZSB0aGV5IGFuc3dlciBkaWZmZXJlbnQgcXVlc3Rpb25zOiBcImlzIHRoaXMgcmVwb3J0XG4gICAqIGN1cnJlbnQ/XCIgYW5kIFwiaXMgdGhpcyB0aGUgY29weSBJIHdhcyBzZW50P1wiLlxuICAgKi9cbiAgcmVuZGVyZWRBdDogc3RyaW5nO1xufVxuXG4vKiogSFRNTC1lc2NhcGUuIEFwcGxpZWQgYXQgZXZlcnkgaW5zZXJ0aW9uIHBvaW50OyBzZWUgdGhlIGZpbGUgaGVhZGVyLiAqL1xuZnVuY3Rpb24gZXNjKHZhbHVlOiB1bmtub3duKTogc3RyaW5nIHtcbiAgcmV0dXJuIFN0cmluZyh2YWx1ZSA/PyAnJylcbiAgICAucmVwbGFjZSgvJi9nLCAnJmFtcDsnKVxuICAgIC5yZXBsYWNlKC88L2csICcmbHQ7JylcbiAgICAucmVwbGFjZSgvPi9nLCAnJmd0OycpXG4gICAgLnJlcGxhY2UoL1wiL2csICcmcXVvdDsnKVxuICAgIC5yZXBsYWNlKC8nL2csICcmIzM5OycpO1xufVxuXG4vKiogQSBkYXRlIGFzIGEgcGxhaW4gY2FsZW5kYXIgZGF5LCBpbiBVVEMsIG9yIGDigJRgIHdoZW4gYWJzZW50LiAqL1xuZnVuY3Rpb24gZGF5KGlzbzogc3RyaW5nIHwgbnVsbCB8IHVuZGVmaW5lZCk6IHN0cmluZyB7XG4gIGlmICghaXNvKSByZXR1cm4gJ+KAlCc7XG4gIGNvbnN0IGQgPSBuZXcgRGF0ZShpc28pO1xuICBpZiAoTnVtYmVyLmlzTmFOKGQuZ2V0VGltZSgpKSkgcmV0dXJuICfigJQnO1xuICByZXR1cm4gZC50b0lTT1N0cmluZygpLnNsaWNlKDAsIDEwKTtcbn1cblxuLyoqXG4gKiBUaGUgdHJlbmQsIElOIFdPUkRTLlxuICpcbiAqIFwiZG93biAzIHBvaW50c1wiIHJhdGhlciB0aGFuIGEgcmVkIGFycm93LCBiZWNhdXNlIHRoZSBhcnJvdyBpcyB0aGUgd2hvbGUgZmFjdCBmb3IgYVxuICogc2VjdGlvbiB3aG9zZSBoZWFkbGluZSBiYXJlbHkgbW92ZWQsIGFuZCBhbiBhcnJvdyBpcyBpbnZpc2libGUgdG8gYSBzY3JlZW4gcmVhZGVyIGFuZFxuICogYW1iaWd1b3VzIGluIGdyZXlzY2FsZS4gYHVua25vd25gIHJlbmRlcnMgbm90aGluZyBhdCBhbGwgcmF0aGVyIHRoYW4gXCJmbGF0XCIg4oCUIGEgc2VjdGlvblxuICogd2l0aCBubyBjb21wYXJhYmxlIHByZXZpb3VzIHBlcmlvZCBoYXMgbm90IGhlbGQgc3RlYWR5LCB3ZSBqdXN0IGNhbm5vdCBzYXkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0cmVuZFNlbnRlbmNlKHNlY3Rpb246IENvbXBvc2VkU2VjdGlvbik6IHN0cmluZyB8IG51bGwge1xuICBjb25zdCB7IGNoYW5nZSwgaGVhZGxpbmUgfSA9IHNlY3Rpb247XG4gIGlmICghY2hhbmdlIHx8IGNoYW5nZS5kaXJlY3Rpb24gPT09ICd1bmtub3duJykgcmV0dXJuIG51bGw7XG4gIGNvbnN0IHVuaXQgPSBoZWFkbGluZT8udW5pdCA9PT0gJyUnID8gJ3BvaW50cycgOiBudWxsO1xuICBjb25zdCBtYWduaXR1ZGUgPSBNYXRoLmFicyhjaGFuZ2UuYWJzb2x1dGUpO1xuICBpZiAoY2hhbmdlLmRpcmVjdGlvbiA9PT0gJ2ZsYXQnKSByZXR1cm4gJ3VuY2hhbmdlZCBmcm9tIHRoZSBwcmV2aW91cyBwZXJpb2QnO1xuICBjb25zdCB3b3JkID0gY2hhbmdlLmRpcmVjdGlvbiA9PT0gJ3VwJyA/ICd1cCcgOiAnZG93bic7XG4gIGNvbnN0IHBjdCA9IGNoYW5nZS5wZXJjZW50ICE9PSBudWxsICYmIGNoYW5nZS5wZXJjZW50ICE9PSB1bmRlZmluZWRcbiAgICA/IGAgKCR7Y2hhbmdlLnBlcmNlbnQgPiAwID8gJysnIDogJyd9JHtjaGFuZ2UucGVyY2VudH0lKWBcbiAgICA6ICcnO1xuICByZXR1cm4gYCR7d29yZH0gJHttYWduaXR1ZGV9JHt1bml0ID8gYCAke3VuaXR9YCA6ICcnfSR7cGN0fSBmcm9tIHRoZSBwcmV2aW91cyBwZXJpb2RgO1xufVxuXG4vKipcbiAqIE9uZSBzZWN0aW9uIGFzIGEgdGFibGUgcm93LlxuICpcbiAqIEEgVEFCTEUsIG5vdCBhIGdyaWQgb2YgY2FyZHMsIGJlY2F1c2UgdGhhdCBpcyB3aGF0IG1ha2VzIHRoZSBQREYncyBzdHJ1Y3R1cmUgdHJlZVxuICogbmF2aWdhYmxlOiBhIHNjcmVlbiByZWFkZXIgYW5ub3VuY2VzIFwiQnVpbGQgc3VjY2VzcyByYXRlLCA5MSUsIGRvd24gMyBwb2ludHMgZnJvbSB0aGVcbiAqIHByZXZpb3VzIHBlcmlvZFwiIGZyb20gdGhlIHJvdydzIGFzc29jaWF0aW9uIHdpdGggaXRzIGhlYWRlcnMuIENhcmRzIHdvdWxkIHByaW50IHRoZVxuICogc2FtZSBpbmsgYW5kIGNhcnJ5IG5vbmUgb2YgdGhhdC5cbiAqL1xuZnVuY3Rpb24gc2VjdGlvblJvdyhzZWN0aW9uOiBDb21wb3NlZFNlY3Rpb24pOiBzdHJpbmcge1xuICBjb25zdCB0aXRsZSA9IGVzYyhzZWN0aW9uLnRpdGxlKTtcbiAgaWYgKHNlY3Rpb24uc3RhdGUgPT09ICdsb2NrZWQnKSB7XG4gICAgY29uc3QgZmVhdHVyZSA9IHNlY3Rpb24ucmVxdWlyZXNGZWF0dXJlID8gZXNjKHNlY3Rpb24ucmVxdWlyZXNGZWF0dXJlKSA6ICdhbiBhZGQtb24nO1xuICAgIHJldHVybiBgPHRyPjx0aCBzY29wZT1cInJvd1wiPiR7dGl0bGV9PC90aD5gXG4gICAgICArICc8dGQgY2xhc3M9XCJtdXRlZFwiPk5vdCBpbmNsdWRlZDwvdGQ+J1xuICAgICAgKyBgPHRkIGNsYXNzPVwibXV0ZWRcIj5Ob3Qgb24geW91ciBwbGFuIOKAlCBuZWVkcyAke2ZlYXR1cmV9LiBUaGUgcmVzdCBvZiB0aGlzIHJlcG9ydCBpcyB1bmFmZmVjdGVkLjwvdGQ+PC90cj5gO1xuICB9XG4gIGlmIChzZWN0aW9uLnN0YXRlID09PSAnZmFpbGVkJykge1xuICAgIHJldHVybiBgPHRyPjx0aCBzY29wZT1cInJvd1wiPiR7dGl0bGV9PC90aD5gXG4gICAgICArICc8dGQgY2xhc3M9XCJtdXRlZFwiPlVuYXZhaWxhYmxlPC90ZD4nXG4gICAgICArICc8dGQgY2xhc3M9XCJtdXRlZFwiPkNvdWxkIG5vdCBiZSBjb21wdXRlZCBmb3IgdGhpcyBwZXJpb2QuPC90ZD48L3RyPic7XG4gIH1cbiAgY29uc3QgeyBoZWFkbGluZSB9ID0gc2VjdGlvbjtcbiAgLy8gQSBzZWN0aW9uIHdpdGggbm8gc2luZ2xlIG51bWJlciBpcyBub3QgYW4gZXJyb3IgYW5kIG11c3Qgbm90IHJlYWQgbGlrZSBvbmU6IHNldmVyYWxcbiAgLy8gbGVnaXRpbWF0ZWx5IGhhdmUgbm9uZSAoYSBicmVha2Rvd24sIGEgbGlzdCBvZiBvZmZlbmRlcnMpLlxuICBjb25zdCB2YWx1ZSA9IGhlYWRsaW5lXG4gICAgPyBgJHtlc2MoaGVhZGxpbmUudmFsdWUpfSR7ZXNjKGhlYWRsaW5lLnVuaXQgPz8gJycpfWBcbiAgICA6ICfigJQnO1xuICBjb25zdCBsYWJlbCA9IGhlYWRsaW5lPy5sYWJlbCA/IGVzYyhoZWFkbGluZS5sYWJlbCkgOiAnTm8gc2luZ2xlIGhlYWRsaW5lIGZvciB0aGlzIHNlY3Rpb24nO1xuICBjb25zdCB0cmVuZCA9IHRyZW5kU2VudGVuY2Uoc2VjdGlvbik7XG4gIGNvbnN0IG5vdGUgPSB0cmVuZCA/IGAke2xhYmVsfSDCtyAke2VzYyh0cmVuZCl9YCA6IGxhYmVsO1xuICByZXR1cm4gYDx0cj48dGggc2NvcGU9XCJyb3dcIj4ke3RpdGxlfTwvdGg+PHRkIGNsYXNzPVwidmFsdWVcIj4ke3ZhbHVlfTwvdGQ+PHRkPiR7bm90ZX08L3RkPjwvdHI+YDtcbn1cblxuLyoqIFRoZSBkYXRhLXF1YWxpdHkgbm90ZXMsIHdoaWNoIGFyZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIFwiMCBkZXBsb3lzXCIgYW5kIFwid2UgY2Fubm90IHNlZSB5b3VyIGRlcGxveXNcIi4gKi9cbmZ1bmN0aW9uIG5vdGVzTGlzdChub3RlczogcmVhZG9ubHkgRGF0YVF1YWxpdHlOb3RlW10pOiBzdHJpbmcge1xuICBpZiAobm90ZXMubGVuZ3RoID09PSAwKSByZXR1cm4gJyc7XG4gIGNvbnN0IGl0ZW1zID0gbm90ZXMubWFwKChuKSA9PiBgPGxpPiR7ZXNjKG4ubWVzc2FnZSl9PC9saT5gKS5qb2luKCcnKTtcbiAgcmV0dXJuICc8c2VjdGlvbiBjbGFzcz1cImJsb2NrXCI+PGgyPldoYXQgdG8ga25vdyBhYm91dCB0aGVzZSBudW1iZXJzPC9oMj4nXG4gICAgKyBgPHVsIGNsYXNzPVwibm90ZXNcIj4ke2l0ZW1zfTwvdWw+PC9zZWN0aW9uPmA7XG59XG5cbi8qKlxuICogUGFyYWdyYXBocyBmcm9tIGZyZWUgdGV4dC5cbiAqXG4gKiBCbGFuay1saW5lIHNlcGFyYXRlZCwgYW5kIE5PVCBhIHNpbmdsZSBgPHA+YCB3aXRoIG5ld2xpbmVzOiBhIGxlYWQgd2hvIHdyb3RlIHRocmVlXG4gKiBwYXJhZ3JhcGhzIGdldHMgdGhyZWUsIGFuZCB0aGUgUERGJ3Mgc3RydWN0dXJlIHRyZWUgZ2V0cyB0aHJlZSwgaW5zdGVhZCBvZiBvbmUgd2FsbFxuICogdGhhdCBhIHNjcmVlbiByZWFkZXIgcmVhZHMgYXMgYSBzaW5nbGUgdXR0ZXJhbmNlLlxuICovXG5mdW5jdGlvbiBwYXJhZ3JhcGhzKHRleHQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiB0ZXh0XG4gICAgLnNwbGl0KC9cXG57Mix9LylcbiAgICAubWFwKChwKSA9PiBwLnRyaW0oKSlcbiAgICAuZmlsdGVyKEJvb2xlYW4pXG4gICAgLm1hcCgocCkgPT4gYDxwPiR7ZXNjKHApLnJlcGxhY2UoL1xcbi9nLCAnPGJyIC8+Jyl9PC9wPmApXG4gICAgLmpvaW4oJycpO1xufVxuXG4vKipcbiAqIFRoZSBwcmludCBzdHlsZXNoZWV0LlxuICpcbiAqIERlbGliZXJhdGVseSBzbWFsbCBhbmQgZGVsaWJlcmF0ZWx5IG5vdCB0aGUgcHJvZHVjdCdzIGRlc2lnbiBzeXN0ZW06IHRoaXMgZG9jdW1lbnQgaXNcbiAqIHByaW50ZWQgb25jZSwgdG8gcGFwZXIgc2l6ZSwgYnkgb25lIGVuZ2luZS4gSW1wb3J0aW5nIHRoZSBhcHAncyBDU1Mgd291bGQgZHJhZyBpbiBhIHdlYlxuICogZm9udCAoYSBuZXR3b3JrIGZldGNoIHRoZSByZW5kZXJlciBmb3JiaWRzKSBhbmQgYSBkYXJrLW1vZGUgYmxvY2sgdGhhdCB3b3VsZCByZW5kZXIgYVxuICogbWFuYWdlcidzIFBERiBibGFjay5cbiAqXG4gKiBgQHBhZ2VgIG93bnMgdGhlIG1hcmdpbnMgc28gQ2hyb21pdW0ncyBvd24gYG1hcmdpbmAgb3B0aW9uIGRvZXMgbm90IGhhdmUgdG8sIHdoaWNoIGtlZXBzXG4gKiB0aGUgcGFnZSBnZW9tZXRyeSBpbiB0aGUgZG9jdW1lbnQgcmF0aGVyIHRoYW4gc3BsaXQgYWNyb3NzIHR3byBwbGFjZXMuXG4gKi9cbmNvbnN0IFNUWUxFID0gYFxuOnJvb3QgeyBjb2xvci1zY2hlbWU6IG9ubHkgbGlnaHQ7IH1cbiogeyBib3gtc2l6aW5nOiBib3JkZXItYm94OyB9XG5AcGFnZSB7IHNpemU6IEE0OyBtYXJnaW46IDE4bW0gMTZtbSAyMG1tOyB9XG5ib2R5IHtcbiAgbWFyZ2luOiAwOyBiYWNrZ3JvdW5kOiAjZmZmZmZmOyBjb2xvcjogIzExMTgyNztcbiAgZm9udDogMTFwdC8xLjQ1IC1hcHBsZS1zeXN0ZW0sIFwiU2Vnb2UgVUlcIiwgUm9ib3RvLCBcIkhlbHZldGljYSBOZXVlXCIsIEFyaWFsLCBzYW5zLXNlcmlmO1xufVxuaDEgeyBmb250LXNpemU6IDIwcHQ7IG1hcmdpbjogMCAwIDJtbTsgfVxuaDIgeyBmb250LXNpemU6IDEycHQ7IG1hcmdpbjogMCAwIDJtbTsgfVxucCB7IG1hcmdpbjogMCAwIDIuNW1tOyB9XG4uc3ViIHsgY29sb3I6ICM0YjU1NjM7IGZvbnQtc2l6ZTogOS41cHQ7IG1hcmdpbjogMCAwIDZtbTsgfVxuLmJsb2NrIHsgbWFyZ2luOiAwIDAgN21tOyBwYWdlLWJyZWFrLWluc2lkZTogYXZvaWQ7IH1cbnRhYmxlIHsgd2lkdGg6IDEwMCU7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7IGZvbnQtc2l6ZTogMTBwdDsgfVxuY2FwdGlvbiB7IHRleHQtYWxpZ246IGxlZnQ7IGZvbnQtd2VpZ2h0OiA2MDA7IGZvbnQtc2l6ZTogMTJwdDsgcGFkZGluZzogMCAwIDJtbTsgfVxudGgsIHRkIHsgdGV4dC1hbGlnbjogbGVmdDsgdmVydGljYWwtYWxpZ246IHRvcDsgcGFkZGluZzogMm1tIDJtbSAybW0gMDsgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNlNWU3ZWI7IH1cbnRoZWFkIHRoIHsgYm9yZGVyLWJvdHRvbTogMS41cHggc29saWQgIzljYTNhZjsgZm9udC1zaXplOiA5cHQ7IHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7IGxldHRlci1zcGFjaW5nOiAuMDRlbTsgfVxudGJvZHkgdGggeyBmb250LXdlaWdodDogNjAwOyB3aWR0aDogNDIlOyB9XG50ZC52YWx1ZSB7IGZvbnQtdmFyaWFudC1udW1lcmljOiB0YWJ1bGFyLW51bXM7IGZvbnQtd2VpZ2h0OiA2MDA7IHdoaXRlLXNwYWNlOiBub3dyYXA7IHdpZHRoOiAxOCU7IH1cbi5tdXRlZCB7IGNvbG9yOiAjNGI1NTYzOyB9XG4ubm90ZXMgeyBtYXJnaW46IDA7IHBhZGRpbmctbGVmdDogNW1tOyB9XG4ubm90ZXMgbGkgeyBtYXJnaW46IDAgMCAxLjVtbTsgfVxuLmZvb3RlciB7IG1hcmdpbi10b3A6IDhtbTsgcGFkZGluZy10b3A6IDNtbTsgYm9yZGVyLXRvcDogMXB4IHNvbGlkICNlNWU3ZWI7IGNvbG9yOiAjNGI1NTYzOyBmb250LXNpemU6IDguNXB0OyB9XG4uZm9vdGVyIHAgeyBtYXJnaW46IDAgMCAxbW07IH1cbnRyIHsgcGFnZS1icmVhay1pbnNpZGU6IGF2b2lkOyB9XG5gO1xuXG4vKipcbiAqIFJlbmRlciBvbmUgcmVwb3J0IGFzIGEgY29tcGxldGUgSFRNTCBkb2N1bWVudC5cbiAqXG4gKiBPUkRFUiBJUyBNRUFOSU5HLCBhbmQgaXQgaXMgdGhlIHNhbWUgb3JkZXIgdGhlIHNoYXJlZCB3ZWIgcGFnZSB1c2VzOiB0aGUgbGVhZCdzIHdvcmRzLFxuICogdGhlbiB0aGUgZXhlY3V0aXZlIHN1bW1hcnksIHRoZW4gdGhlIG51bWJlcnMuIEEgcmVhZGVyIHdobyBtZWV0cyB0aGUgdGFibGUgZmlyc3QgaGFzXG4gKiBhbHJlYWR5IGZvcm1lZCBhIHZpZXcgb2YgdGhlIGRpcCBieSB0aGUgdGltZSB0aGV5IHJlYWNoIHRoZSBzZW50ZW5jZSBleHBsYWluaW5nIGl0LCBzb1xuICogdGhlIGNvbnRleHQgdGhlIGRhdGEgY2Fubm90IHN1cHBseSBoYXMgdG8gY29tZSBmaXJzdC4gYGExMXktc3Rha2Vob2xkZXItcmVwb3J0cy50ZXN0LnRzeGBcbiAqIHBpbnMgdGhhdCBvcmRlcmluZyBmb3IgdGhlIHBhZ2U7IGByZXBvcnQtaHRtbC50ZXN0LnRzYCBwaW5zIGl0IGhlcmUuXG4gKlxuICogQSBOVUxMIFNOQVBTSE9UIFNUSUxMIFJFTkRFUlMuIEEgcnVuIGNhbiBiZSByZWFkIGJlZm9yZSBvciB3aXRob3V0IGEgc25hcHNob3QgKGEgZmFpbGVkXG4gKiBjb21wb3NlLCBhIGRyYWZ0KSwgYW5kIGFuc3dlcmluZyBhbiBlbXB0eSBmaWxlIHdvdWxkIGxvb2sgbGlrZSBhIGJyb2tlbiBkb3dubG9hZC4gVGhlXG4gKiBkb2N1bWVudCBzYXlzIHRoZXJlIGlzIG5vdGhpbmcgdG8gc2hvdyBpbnN0ZWFkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVuZGVyUmVwb3J0SHRtbChpbnB1dDogUmVwb3J0SHRtbElucHV0KTogc3RyaW5nIHtcbiAgY29uc3Qgc25hcHNob3QgPSBpbnB1dC5zbmFwc2hvdDtcbiAgY29uc3Qgc2VjdGlvbnMgPSBzbmFwc2hvdD8uc2VjdGlvbnMgPz8gW107XG4gIGNvbnN0IGxhbmcgPSAnZW4nO1xuXG4gIGNvbnN0IGhlYWQgPSBbXG4gICAgJzwhRE9DVFlQRSBodG1sPicsXG4gICAgYDxodG1sIGxhbmc9XCIke2xhbmd9XCI+PGhlYWQ+PG1ldGEgY2hhcnNldD1cInV0Zi04XCIgLz5gLFxuICAgIC8vIE5vIGV4dGVybmFsIGFueXRoaW5nOiB0aGUgb25lIGRpcmVjdGl2ZSB0aGF0IG1ha2VzIHRoZSBwcm9taXNlIGluIHRoZSBmaWxlIGhlYWRlclxuICAgIC8vIGVuZm9yY2VhYmxlIGJ5IHRoZSBlbmdpbmUgcmF0aGVyIHRoYW4gYnkgcmV2aWV3LlxuICAgICc8bWV0YSBodHRwLWVxdWl2PVwiQ29udGVudC1TZWN1cml0eS1Qb2xpY3lcIiBjb250ZW50PVwiZGVmYXVsdC1zcmMgXFwnbm9uZVxcJzsgc3R5bGUtc3JjIFxcJ3Vuc2FmZS1pbmxpbmVcXCdcIiAvPicsXG4gICAgYDx0aXRsZT4ke2VzYyhpbnB1dC50aXRsZSl9IOKAlCAke2VzYyhpbnB1dC5wZXJpb2RMYWJlbCl9PC90aXRsZT5gLFxuICAgIGA8c3R5bGU+JHtTVFlMRX08L3N0eWxlPmAsXG4gICAgJzwvaGVhZD48Ym9keT4nLFxuICBdLmpvaW4oJycpO1xuXG4gIGNvbnN0IHZlcnNpb24gPSBpbnB1dC52ZXJzaW9uID4gMSA/IGAgwrcgcmV2aXNpb24gJHtlc2MoaW5wdXQudmVyc2lvbil9YCA6ICcnO1xuICBjb25zdCBwdWJsaXNoZWQgPSBpbnB1dC5wdWJsaXNoZWRBdCA/IGAgwrcgcHVibGlzaGVkICR7ZXNjKGRheShpbnB1dC5wdWJsaXNoZWRBdCkpfWAgOiAnJztcbiAgY29uc3QgaGVhZGVyID0gYDxoMT4ke2VzYyhpbnB1dC50aXRsZSl9PC9oMT5gXG4gICAgKyBgPHAgY2xhc3M9XCJzdWJcIj4ke2VzYyhpbnB1dC5wZXJpb2RMYWJlbCl9IMK3ICR7ZXNjKGRheShpbnB1dC5wZXJpb2RTdGFydCkpfSB0byBgXG4gICAgKyBgJHtlc2MoZGF5KGlucHV0LnBlcmlvZEVuZCkpfSR7dmVyc2lvbn0ke3B1Ymxpc2hlZH08L3A+YDtcblxuICBjb25zdCBzdW1tYXJ5ID0gaW5wdXQuZXhlY3V0aXZlU3VtbWFyeVxuICAgID8gYDxzZWN0aW9uIGNsYXNzPVwiYmxvY2tcIj48aDI+RXhlY3V0aXZlIHN1bW1hcnk8L2gyPiR7cGFyYWdyYXBocyhpbnB1dC5leGVjdXRpdmVTdW1tYXJ5KX08L3NlY3Rpb24+YFxuICAgIDogJyc7XG5cbiAgY29uc3Qgbm90ZXMgPSBpbnB1dC5sZWFkTm90ZXNcbiAgICA/IGA8c2VjdGlvbiBjbGFzcz1cImJsb2NrXCI+PGgyPk5vdGVzIGZyb20gdGhlIHRlYW08L2gyPiR7cGFyYWdyYXBocyhpbnB1dC5sZWFkTm90ZXMpfTwvc2VjdGlvbj5gXG4gICAgOiAnJztcblxuICBjb25zdCBib2R5ID0gc2VjdGlvbnMubGVuZ3RoID4gMFxuICAgID8gJzxzZWN0aW9uIGNsYXNzPVwiYmxvY2tcIj48dGFibGU+J1xuICAgICAgKyBgPGNhcHRpb24+RGVsaXZlcnkgbWVhc3VyZXMgZm9yICR7ZXNjKGlucHV0LnBlcmlvZExhYmVsKX08L2NhcHRpb24+YFxuICAgICAgKyAnPHRoZWFkPjx0cj48dGggc2NvcGU9XCJjb2xcIj5NZWFzdXJlPC90aD48dGggc2NvcGU9XCJjb2xcIj5WYWx1ZTwvdGg+PHRoIHNjb3BlPVwiY29sXCI+RGV0YWlsPC90aD48L3RyPjwvdGhlYWQ+J1xuICAgICAgKyBgPHRib2R5PiR7c2VjdGlvbnMubWFwKHNlY3Rpb25Sb3cpLmpvaW4oJycpfTwvdGJvZHk+PC90YWJsZT48L3NlY3Rpb24+YFxuICAgIDogJzxzZWN0aW9uIGNsYXNzPVwiYmxvY2tcIj48aDI+Tm8gbWVhc3VyZXM8L2gyPidcbiAgICAgICsgJzxwIGNsYXNzPVwibXV0ZWRcIj5UaGlzIHJlcG9ydCBoYXMgbm8gY29tcHV0ZWQgc2VjdGlvbnMuIE5vdGhpbmcgd2FzIG1lYXN1cmVkIGZvciB0aGlzIHBlcmlvZC48L3A+PC9zZWN0aW9uPic7XG5cbiAgY29uc3QgZm9vdGVyTGluZXM6IHN0cmluZ1tdID0gW107XG4gIGlmIChpbnB1dC5uYW1lc1JlZGFjdGVkKSB7XG4gICAgZm9vdGVyTGluZXMucHVzaCgnUHJvamVjdCBhbmQgcGlwZWxpbmUgbmFtZXMgaGF2ZSBiZWVuIHJlcGxhY2VkIGluIHRoaXMgY29weS4nKTtcbiAgfVxuICBpZiAoc25hcHNob3Q/Lm1ldGhvZG9sb2d5KSBmb290ZXJMaW5lcy5wdXNoKHNuYXBzaG90Lm1ldGhvZG9sb2d5KTtcbiAgaWYgKHNuYXBzaG90Py5nZW5lcmF0ZWRBdCkge1xuICAgIGZvb3RlckxpbmVzLnB1c2goYEZpZ3VyZXMgY29tcHV0ZWQgJHtkYXkoc25hcHNob3QuZ2VuZXJhdGVkQXQpfSBhbmQgZnJvemVuIGZvciB0aGUgcGVyaW9kIHNob3duLmApO1xuICB9XG4gIGlmIChpbnB1dC5leHBpcmVzQXQpIHtcbiAgICBmb290ZXJMaW5lcy5wdXNoKGBUaGUgbGluayB0aGlzIGNvcHkgY2FtZSBmcm9tIGV4cGlyZXMgb24gJHtkYXkoaW5wdXQuZXhwaXJlc0F0KX0uYCk7XG4gIH1cbiAgZm9vdGVyTGluZXMucHVzaChgVGhpcyBmaWxlIHdhcyBjcmVhdGVkICR7ZGF5KGlucHV0LnJlbmRlcmVkQXQpfS5gKTtcbiAgY29uc3QgZm9vdGVyID0gYDxmb290ZXIgY2xhc3M9XCJmb290ZXJcIj4ke2Zvb3RlckxpbmVzLm1hcCgobCkgPT4gYDxwPiR7ZXNjKGwpfTwvcD5gKS5qb2luKCcnKX08L2Zvb3Rlcj5gO1xuXG4gIHJldHVybiBgJHtoZWFkfSR7aGVhZGVyfSR7bm90ZXN9JHtzdW1tYXJ5fSR7Ym9keX0ke25vdGVzTGlzdChzbmFwc2hvdD8ubm90ZXMgPz8gW10pfSR7Zm9vdGVyfTwvYm9keT48L2h0bWw+YDtcbn1cblxuLyoqXG4gKiBUaGUgZG93bmxvYWQncyBmaWxlIG5hbWUuXG4gKlxuICogQnVpbHQgZnJvbSB0aGUgcGVyaW9kIGFuZCB0aGUgdmVyc2lvbiByYXRoZXIgdGhhbiB0aGUgcnVuIGlkLCBiZWNhdXNlIHRoZSBwZXJzb24gc2F2aW5nXG4gKiBpdCBpcyBmaWxpbmcgaXQgbmV4dCB0byBsYXN0IG1vbnRoJ3MsIGFuZCBgcmVwb3J0LTIwMjYtVzM4LXYyLnBkZmAgc29ydHMgYW5kIHJlYWRzIHdoaWxlXG4gKiBhIHV1aWQgZG9lcyBub3QuIFNhbml0aXppbmcgaXMgdGhlIFJPVVRFJ3Mgam9iIChgYXR0YWNobWVudERpc3Bvc2l0aW9uYCkg4oCUIHRoaXMgb25seVxuICogY2hvb3NlcyB0aGUgd29yZHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXBvcnRGaWxlTmFtZSh0aXRsZTogc3RyaW5nLCBwZXJpb2RMYWJlbDogc3RyaW5nLCB2ZXJzaW9uOiBudW1iZXIpOiBzdHJpbmcge1xuICBjb25zdCBiYXNlID0gYCR7dGl0bGV9ICR7cGVyaW9kTGFiZWx9JHt2ZXJzaW9uID4gMSA/IGAgdiR7dmVyc2lvbn1gIDogJyd9YDtcbiAgcmV0dXJuIGAke2Jhc2UudHJpbSgpfS5wZGZgO1xufVxuIl19
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The section registry: the one place that maps a report section id to the
|
|
3
|
+
* ReportingService method that computes it.
|
|
4
|
+
*
|
|
5
|
+
* Why a registry rather than each template calling the service directly:
|
|
6
|
+
*
|
|
7
|
+
* - The numbers in a report MUST match the dashboard. A manager who compares the
|
|
8
|
+
* two and finds a difference stops trusting both, so both read the same query —
|
|
9
|
+
* there is no separate "report" implementation of success rate.
|
|
10
|
+
* - Feature gating belongs with the section, not the route. DORA sections need
|
|
11
|
+
* `advanced_reporting`; a report that includes one without the add-on renders a
|
|
12
|
+
* LOCKED section and still delivers, rather than failing the whole run. A run
|
|
13
|
+
* that dies because of one gated section is a support ticket; a locked panel is
|
|
14
|
+
* an upsell.
|
|
15
|
+
* - Per-person metrics are refused HERE, at registration. Ranking individual
|
|
16
|
+
* developers destroys trust in the whole report, and a guideline in a document
|
|
17
|
+
* does not stop someone adding `topCommitters` later — a throwing registry does.
|
|
18
|
+
*/
|
|
19
|
+
import type { FeatureFlag } from '@pipeline-builder/api-core';
|
|
20
|
+
import type { AnalyticsScope } from '../analytics-queries.js';
|
|
21
|
+
/** What a section needs to run one period's query. */
|
|
22
|
+
export interface SectionContext {
|
|
23
|
+
orgId: string;
|
|
24
|
+
/** Rollup org ids, when the definition's scope is a team rollup. */
|
|
25
|
+
orgIds?: string[];
|
|
26
|
+
/** Period bounds as ISO strings, which is what every service method takes. */
|
|
27
|
+
from: string;
|
|
28
|
+
to: string;
|
|
29
|
+
/** The report's bucketing settings, for the time-series sections. */
|
|
30
|
+
tz: string;
|
|
31
|
+
weekStart: 'monday' | 'sunday';
|
|
32
|
+
/**
|
|
33
|
+
* The PREVIOUS period's bounds, for the sections that compute their own trend
|
|
34
|
+
* internally rather than being run twice.
|
|
35
|
+
*
|
|
36
|
+
* Most sections are simply re-run over the previous window by the composer and
|
|
37
|
+
* compared on a single headline. The analytics sections cannot be: their trend is
|
|
38
|
+
* per category, per stage or per pipeline, and comparing two whole result sets
|
|
39
|
+
* afterwards would mean re-implementing the join in the composer.
|
|
40
|
+
*/
|
|
41
|
+
previousFrom?: string;
|
|
42
|
+
previousTo?: string;
|
|
43
|
+
}
|
|
44
|
+
/** The service surface a section is allowed to call. Structural on purpose: the
|
|
45
|
+
* registry never imports the service, so it stays testable with a stub. */
|
|
46
|
+
export interface SectionDataSource {
|
|
47
|
+
getExecutionCount(orgId: string, orgIds?: string[], range?: {
|
|
48
|
+
from?: string;
|
|
49
|
+
to?: string;
|
|
50
|
+
}): Promise<unknown>;
|
|
51
|
+
getSuccessRate(orgId: string, interval: string, from: string, to: string, orgIds?: string[], bucket?: {
|
|
52
|
+
tz?: string;
|
|
53
|
+
weekStart?: 'monday' | 'sunday';
|
|
54
|
+
}): Promise<unknown>;
|
|
55
|
+
getAverageDuration(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
|
|
56
|
+
getStageFailures(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
|
|
57
|
+
getStageBottlenecks(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
|
|
58
|
+
getActionFailures(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
|
|
59
|
+
getErrors(orgId: string, from: string, to: string, limit?: number, orgIds?: string[]): Promise<unknown>;
|
|
60
|
+
getPluginSummary(orgId: string): Promise<unknown>;
|
|
61
|
+
getPluginDistribution(orgId: string): Promise<unknown>;
|
|
62
|
+
getPluginVersions(orgId: string): Promise<unknown>;
|
|
63
|
+
getBuildSuccessRate(orgId: string, interval: string, from: string, to: string, orgIds?: string[], bucket?: {
|
|
64
|
+
tz?: string;
|
|
65
|
+
weekStart?: 'monday' | 'sunday';
|
|
66
|
+
}): Promise<unknown>;
|
|
67
|
+
getBuildDuration(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
|
|
68
|
+
getBuildFailures(orgId: string, from: string, to: string, limit?: number, orgIds?: string[]): Promise<unknown>;
|
|
69
|
+
getDoraMetrics(orgId: string, from: string, to: string, orgIds?: string[], opts?: Record<string, unknown>): Promise<unknown>;
|
|
70
|
+
getDoraTrend(orgId: string, interval: string, from: string, to: string, orgIds?: string[], opts?: Record<string, unknown>): Promise<unknown>;
|
|
71
|
+
getBuildHealth(orgId: string, pipelineId: string, from: string, to: string): Promise<unknown>;
|
|
72
|
+
getReportEnvironments(orgId: string, from: string, to: string, orgIds?: string[]): Promise<unknown>;
|
|
73
|
+
getPipelineBreakdown(scope: AnalyticsScope, limit?: number): Promise<unknown>;
|
|
74
|
+
getFailureAnalysis(scope: AnalyticsScope, previous?: {
|
|
75
|
+
from: string;
|
|
76
|
+
to: string;
|
|
77
|
+
}): Promise<unknown>;
|
|
78
|
+
getStagePerformance(scope: AnalyticsScope, pipelineId: string, previous?: {
|
|
79
|
+
from: string;
|
|
80
|
+
to: string;
|
|
81
|
+
}): Promise<unknown>;
|
|
82
|
+
getResourceConsumption(scope: AnalyticsScope, limit?: number): Promise<unknown>;
|
|
83
|
+
getPromotionView(scope: AnalyticsScope, productionEnv?: string, limit?: number): Promise<unknown>;
|
|
84
|
+
getOutdatedPlugins(scope: AnalyticsScope, limit?: number): Promise<unknown>;
|
|
85
|
+
getPluginVulnerabilities(scope: AnalyticsScope, limit?: number): Promise<unknown>;
|
|
86
|
+
getAdoption(scope: AnalyticsScope, timeSavedMinutes?: number | null): Promise<unknown>;
|
|
87
|
+
/** Compliance + access posture, over a service-to-service read. Degrades. */
|
|
88
|
+
getCompliancePosture?(scope: AnalyticsScope): Promise<unknown>;
|
|
89
|
+
/** The evaluated needs-attention findings for the period. */
|
|
90
|
+
getNeedsAttention?(scope: AnalyticsScope, previous?: {
|
|
91
|
+
from: string;
|
|
92
|
+
to: string;
|
|
93
|
+
}): Promise<unknown>;
|
|
94
|
+
}
|
|
95
|
+
/** One registered section. */
|
|
96
|
+
export interface SectionSpec {
|
|
97
|
+
id: string;
|
|
98
|
+
/** Manager-facing title. Not a metric name — "Deploys", not "deployment_frequency". */
|
|
99
|
+
title: string;
|
|
100
|
+
/** Feature the org must hold, or undefined for a free section. */
|
|
101
|
+
requiresFeature?: FeatureFlag;
|
|
102
|
+
/**
|
|
103
|
+
* True when this section's value is a single comparable number, so the composer
|
|
104
|
+
* can compute period-over-period change. A list section (top failures) has no
|
|
105
|
+
* meaningful "change", and pretending otherwise produces nonsense arrows.
|
|
106
|
+
*/
|
|
107
|
+
comparable: boolean;
|
|
108
|
+
run(source: SectionDataSource, ctx: SectionContext): Promise<unknown>;
|
|
109
|
+
}
|
|
110
|
+
/** Register a section. Throws on a duplicate id or a per-person one. */
|
|
111
|
+
export declare function registerSection(spec: SectionSpec): SectionSpec;
|
|
112
|
+
/** Look a section up; undefined when the id is unknown. */
|
|
113
|
+
export declare function getSection(id: string): SectionSpec | undefined;
|
|
114
|
+
/** Every registered section, in registration order. */
|
|
115
|
+
export declare function allSections(): SectionSpec[];
|
|
116
|
+
/** The ids a caller may put in a definition. */
|
|
117
|
+
export declare function sectionIds(): string[];
|