@letpeoplework/lighthouse-client 1.10.0 → 1.12.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/index.cjs +228 -34
- package/dist/index.d.cts +158 -11
- package/dist/index.d.ts +158 -11
- package/dist/index.js +211 -35
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,20 @@ const PROCESS_BEHAVIOR_CHART_ROUTES = {
|
|
|
37
37
|
const isProcessBehaviorMetricType = (value) => Object.hasOwn(PROCESS_BEHAVIOR_CHART_ROUTES, value);
|
|
38
38
|
//#endregion
|
|
39
39
|
//#region src/usageData.ts
|
|
40
|
+
const reportTemplateWeDisclose = { "then-and-now": "ThenAndNow" };
|
|
41
|
+
/**
|
|
42
|
+
* The opening of a report Lighthouse sent: its type and nothing else about it. A refused read, or a report of a
|
|
43
|
+
* type this version does not know, counts nothing.
|
|
44
|
+
*/
|
|
45
|
+
const reportOpenedOccurrencesOf = (answered) => {
|
|
46
|
+
if (!answered.ok || !isRecord$1(answered.value)) return [];
|
|
47
|
+
const { templateKey } = answered.value;
|
|
48
|
+
const reportTemplate = Object.entries(reportTemplateWeDisclose).find(([key]) => key === templateKey)?.[1];
|
|
49
|
+
return reportTemplate === void 0 ? [] : [{
|
|
50
|
+
name: "ReportOpened",
|
|
51
|
+
reportTemplate
|
|
52
|
+
}];
|
|
53
|
+
};
|
|
40
54
|
/**
|
|
41
55
|
* When a vote was cast, from Lighthouse's answer rather than this machine's clock: Lighthouse counts days in
|
|
42
56
|
* the instance's time zone and knows which cadence days are blacked out. The web counts it the same way.
|
|
@@ -242,13 +256,26 @@ const textOf = (value) => typeof value === "string" && value.length > 0 ? value
|
|
|
242
256
|
const isDay = (value) => isText(value) && formatCalendarDay(value) !== null;
|
|
243
257
|
/** The calendar day as a person reads it, or the text exactly as sent when it is not one. */
|
|
244
258
|
const dayOf = (wire) => formatCalendarDay(wire) ?? wire;
|
|
245
|
-
/** The
|
|
246
|
-
const
|
|
259
|
+
/** The chance a list gives for one probability, or undefined when it holds none for it. */
|
|
260
|
+
const chanceAt = (chances, probability) => {
|
|
247
261
|
if (!Array.isArray(chances)) return;
|
|
248
262
|
const chance = chances.find((entry) => isRecord(entry) && entry.probability === probability);
|
|
249
|
-
return isRecord(chance) ?
|
|
263
|
+
return isRecord(chance) ? chance : void 0;
|
|
250
264
|
};
|
|
251
265
|
//#endregion
|
|
266
|
+
//#region src/forecastPercentileText.ts
|
|
267
|
+
/**
|
|
268
|
+
* The years of the horizon a percentile lies beyond, or undefined when its date is the forecast. Keyed on
|
|
269
|
+
* the flag itself, never on the server's version: an older Lighthouse never sends it.
|
|
270
|
+
*/
|
|
271
|
+
const yearsBeyondHorizon = (percentile) => isRecord(percentile) && percentile.beyondHorizon === true && isNumber(percentile.horizonYears) ? percentile.horizonYears : void 0;
|
|
272
|
+
/** The words shown in place of a date for a percentile too few simulated runs reached. */
|
|
273
|
+
const moreThanYearsText = (horizonYears) => `More than ${horizonYears} years`;
|
|
274
|
+
/** A percentile's day as a person reads it, or "More than 5 years" when it lies beyond that horizon. */
|
|
275
|
+
const describePercentileDay = (expectedDate, horizonYearsBeyond) => horizonYearsBeyond === void 0 ? dayOf(expectedDate) : moreThanYearsText(horizonYearsBeyond);
|
|
276
|
+
/** One percentile as text: its day, or the words when it lies beyond the horizon. */
|
|
277
|
+
const describePercentileDate = (percentile) => describePercentileDay(percentile.expectedDate, yearsBeyondHorizon(percentile));
|
|
278
|
+
//#endregion
|
|
252
279
|
//#region src/ownerWording.ts
|
|
253
280
|
/** A mark for a cell Lighthouse sent nothing for. */
|
|
254
281
|
const NOT_SENT = "—";
|
|
@@ -408,9 +435,11 @@ const startOf = (value) => {
|
|
|
408
435
|
observedOn: void 0,
|
|
409
436
|
likelyBy: void 0
|
|
410
437
|
};
|
|
438
|
+
const chance = chanceAt(value.percentiles, LISTED_CHANCE$1);
|
|
411
439
|
return {
|
|
412
440
|
observedOn: value.source === "Observed" ? textOf(value.observedDate) : void 0,
|
|
413
|
-
likelyBy:
|
|
441
|
+
likelyBy: textOf(chance?.expectedDate),
|
|
442
|
+
likelyByBeyondHorizonYears: yearsBeyondHorizon(chance)
|
|
414
443
|
};
|
|
415
444
|
};
|
|
416
445
|
const readFeatureListItem = (value) => {
|
|
@@ -420,6 +449,7 @@ const readFeatureListItem = (value) => {
|
|
|
420
449
|
const state = textOf(value.state);
|
|
421
450
|
const totalWork = workOf(value.totalWork);
|
|
422
451
|
const remainingWork = workOf(value.remainingWork);
|
|
452
|
+
const completion = chanceAt(value.forecasts, LISTED_CHANCE$1);
|
|
423
453
|
if (referenceId === void 0 || name === void 0 || state === void 0 || totalWork === null || remainingWork === null) return null;
|
|
424
454
|
return {
|
|
425
455
|
referenceId,
|
|
@@ -429,7 +459,8 @@ const readFeatureListItem = (value) => {
|
|
|
429
459
|
remainingWork,
|
|
430
460
|
cannotBeForecast: Array.isArray(value.teamsWithoutForecast) && value.teamsWithoutForecast.length > 0,
|
|
431
461
|
start: startOf(value.startForecast),
|
|
432
|
-
likelyBy:
|
|
462
|
+
likelyBy: textOf(completion?.expectedDate),
|
|
463
|
+
likelyByBeyondHorizonYears: yearsBeyondHorizon(completion)
|
|
433
464
|
};
|
|
434
465
|
};
|
|
435
466
|
/**
|
|
@@ -465,12 +496,15 @@ const describeFeatureProgress = (feature, terms) => `${feature.totalWork - featu
|
|
|
465
496
|
const describeFeatureStart = (feature) => {
|
|
466
497
|
if (feature.start.observedOn !== void 0) return dayOf(feature.start.observedOn);
|
|
467
498
|
if (feature.cannotBeForecast) return CANNOT_FORECAST_SHORT;
|
|
468
|
-
return feature.start.likelyBy === void 0 ? "—" :
|
|
499
|
+
return feature.start.likelyBy === void 0 ? "—" : describePercentileDay(feature.start.likelyBy, feature.start.likelyByBeyondHorizonYears);
|
|
469
500
|
};
|
|
470
|
-
/**
|
|
501
|
+
/**
|
|
502
|
+
* The day the Feature is 85% likely to be done by, "More than 5 years" when that lies beyond the simulation
|
|
503
|
+
* horizon, "Cannot forecast", or the mark when none was sent.
|
|
504
|
+
*/
|
|
471
505
|
const describeFeatureCompletion = (feature) => {
|
|
472
506
|
if (feature.cannotBeForecast) return CANNOT_FORECAST_SHORT;
|
|
473
|
-
return feature.likelyBy === void 0 ? "—" :
|
|
507
|
+
return feature.likelyBy === void 0 ? "—" : describePercentileDay(feature.likelyBy, feature.likelyByBeyondHorizonYears);
|
|
474
508
|
};
|
|
475
509
|
/** One Feature as a row of the list, in the order of its headings. */
|
|
476
510
|
const describeFeatureRow = (feature, terms) => [
|
|
@@ -633,6 +667,7 @@ const readDeliveryListItem = (value) => {
|
|
|
633
667
|
const name = textOf(value.name);
|
|
634
668
|
const date = textOf(value.date);
|
|
635
669
|
if (name === void 0 || date === void 0) return null;
|
|
670
|
+
const likely = chanceAt(value.completionDates, LISTED_CHANCE);
|
|
636
671
|
return {
|
|
637
672
|
id: value.id,
|
|
638
673
|
name,
|
|
@@ -644,7 +679,8 @@ const readDeliveryListItem = (value) => {
|
|
|
644
679
|
cannotBeForecast: teamsWithoutForecastIn(value),
|
|
645
680
|
hasSufficientData: flagOf(value.hasSufficientData),
|
|
646
681
|
isOverdue: flagOf(value.isOverdue),
|
|
647
|
-
likelyBy:
|
|
682
|
+
likelyBy: textOf(likely?.expectedDate),
|
|
683
|
+
likelyByBeyondHorizonYears: yearsBeyondHorizon(likely)
|
|
648
684
|
};
|
|
649
685
|
};
|
|
650
686
|
/**
|
|
@@ -728,7 +764,7 @@ const describeDeliveryRow = (delivery, terms) => [
|
|
|
728
764
|
delivery.featureCount === void 0 ? "—" : String(delivery.featureCount),
|
|
729
765
|
describeDeliveryDone(delivery, terms),
|
|
730
766
|
describeDeliveryLikelihood(delivery),
|
|
731
|
-
delivery.likelyBy === void 0 ? "—" :
|
|
767
|
+
delivery.likelyBy === void 0 ? "—" : describePercentileDay(delivery.likelyBy, delivery.likelyByBeyondHorizonYears)
|
|
732
768
|
];
|
|
733
769
|
/** What heads the archived Deliveries, below the active ones: "Archived Deliveries". */
|
|
734
770
|
const describeArchivedDeliveriesTitle = (terms) => `Archived ${terms.deliveries}`;
|
|
@@ -831,18 +867,23 @@ const describeDeliveryFeatureRow = (feature) => [
|
|
|
831
867
|
];
|
|
832
868
|
/** The forecast distribution's column headings. */
|
|
833
869
|
const describeDeliveryChanceHeadings = () => ["Chance", "Done by"];
|
|
834
|
-
/** One chance of the day's forecast: "85%" and the day it is that likely to be done by. */
|
|
835
|
-
const describeDeliveryChanceRow = (chance) => [`${chance.probability}%`,
|
|
870
|
+
/** One chance of the day's forecast: "85%" and the day it is that likely to be done by, or "More than 5 years". */
|
|
871
|
+
const describeDeliveryChanceRow = (chance) => [`${chance.probability}%`, describePercentileDate(chance)];
|
|
836
872
|
//#endregion
|
|
837
873
|
//#region src/forecastWording.ts
|
|
838
|
-
const optionalBoolean = (value) => {
|
|
874
|
+
const optionalBoolean$1 = (value) => {
|
|
839
875
|
if (value === void 0 || typeof value === "boolean") return value;
|
|
840
876
|
return null;
|
|
841
877
|
};
|
|
842
|
-
const readChanceByDay = (value) =>
|
|
843
|
-
probability
|
|
844
|
-
|
|
845
|
-
|
|
878
|
+
const readChanceByDay = (value) => {
|
|
879
|
+
if (!isRecord(value) || !isNumber(value.probability) || !isDay(value.expectedDate)) return null;
|
|
880
|
+
return {
|
|
881
|
+
probability: value.probability,
|
|
882
|
+
expectedDate: value.expectedDate,
|
|
883
|
+
...typeof value.beyondHorizon === "boolean" ? { beyondHorizon: value.beyondHorizon } : {},
|
|
884
|
+
...isNumber(value.horizonYears) ? { horizonYears: value.horizonYears } : {}
|
|
885
|
+
};
|
|
886
|
+
};
|
|
846
887
|
const readChanceOfCount = (value) => isRecord(value) && isNumber(value.probability) && isNumber(value.value) ? {
|
|
847
888
|
probability: value.probability,
|
|
848
889
|
value: value.value
|
|
@@ -858,8 +899,8 @@ const readManualForecast = (value) => {
|
|
|
858
899
|
const { remainingItems, targetDate, likelihood } = value;
|
|
859
900
|
const whenForecasts = readEvery$1(value.whenForecasts, readChanceByDay);
|
|
860
901
|
const howManyForecasts = readEvery$1(value.howManyForecasts, readChanceOfCount);
|
|
861
|
-
const filterApplied = optionalBoolean(value.filterApplied);
|
|
862
|
-
const hasSufficientData = optionalBoolean(value.hasSufficientData);
|
|
902
|
+
const filterApplied = optionalBoolean$1(value.filterApplied);
|
|
903
|
+
const hasSufficientData = optionalBoolean$1(value.hasSufficientData);
|
|
863
904
|
if (!isNumber(remainingItems) || !(targetDate === null || isDay(targetDate)) || !(likelihood === null || isNumber(likelihood)) || whenForecasts === null || howManyForecasts === null || filterApplied === null || hasSufficientData === null) return null;
|
|
864
905
|
return {
|
|
865
906
|
remainingItems,
|
|
@@ -1146,13 +1187,28 @@ const readRunChart = (value, range) => {
|
|
|
1146
1187
|
const optionalText = (value) => isText(value) ? value : void 0;
|
|
1147
1188
|
const optionalNumber = (value) => isNumber(value) ? value : void 0;
|
|
1148
1189
|
const optionalDay = (value) => isDay(value) ? value : void 0;
|
|
1190
|
+
const optionalBoolean = (value) => typeof value === "boolean" ? value : void 0;
|
|
1191
|
+
const STALE_REASON_KINDS = [
|
|
1192
|
+
"TimeInState",
|
|
1193
|
+
"BlockedDuration",
|
|
1194
|
+
"TimeInStateWhileBlocked"
|
|
1195
|
+
];
|
|
1196
|
+
const isStaleReasonKind = (value) => STALE_REASON_KINDS.some((kind) => kind === value);
|
|
1197
|
+
const readStaleReason = (value) => isRecord(value) && isStaleReasonKind(value.kind) && isNumber(value.days) ? {
|
|
1198
|
+
kind: value.kind,
|
|
1199
|
+
days: value.days,
|
|
1200
|
+
stateName: optionalText(value.stateName)
|
|
1201
|
+
} : null;
|
|
1202
|
+
const optionalStaleReasons = (value) => Array.isArray(value) ? value.map(readStaleReason).filter((reason) => reason !== null) : void 0;
|
|
1149
1203
|
const readInProgressItem = (value) => isRecord(value) && (value.isBlocked === void 0 || typeof value.isBlocked === "boolean") ? {
|
|
1150
1204
|
isBlocked: value.isBlocked,
|
|
1151
1205
|
referenceId: optionalText(value.referenceId),
|
|
1152
1206
|
name: optionalText(value.name),
|
|
1153
1207
|
state: optionalText(value.state),
|
|
1154
1208
|
workItemAge: optionalNumber(value.workItemAge),
|
|
1155
|
-
blockedSince: optionalDay(value.blockedSince)
|
|
1209
|
+
blockedSince: optionalDay(value.blockedSince),
|
|
1210
|
+
isStale: optionalBoolean(value.isStale),
|
|
1211
|
+
staleReasons: optionalStaleReasons(value.staleReasons)
|
|
1156
1212
|
} : null;
|
|
1157
1213
|
const readInProgressNow = (value) => {
|
|
1158
1214
|
if (!isRecord(value) || !isDay(value.asOfDate) || !isNumber(value.count)) return null;
|
|
@@ -1351,6 +1407,7 @@ const metricsHeadlineLabel = (part, scope, terms) => {
|
|
|
1351
1407
|
throughput: `Total ${terms.throughput}`,
|
|
1352
1408
|
arrivals: "Total Arrivals",
|
|
1353
1409
|
blocked: `${terms.blocked} ${counted.many}`,
|
|
1410
|
+
stale: `Stale ${counted.many}`,
|
|
1354
1411
|
totalWorkItemAge: `Total ${terms.workItemAge}`,
|
|
1355
1412
|
predictabilityScore: "Predictability Score",
|
|
1356
1413
|
cycleTimePercentiles: `${terms.cycleTime} percentiles`,
|
|
@@ -1394,27 +1451,31 @@ const describeInProgressNow = (now, scope, terms, systemWipLimit) => ({
|
|
|
1394
1451
|
value: String(now.count),
|
|
1395
1452
|
detail: systemWipLimit === void 0 ? "" : wipLimitOf(systemWipLimit, scope, terms)
|
|
1396
1453
|
});
|
|
1454
|
+
const blockedFlag = (item) => item.isBlocked;
|
|
1455
|
+
const staleFlag = (item) => item.isStale;
|
|
1456
|
+
const saysWhichAre = (now, flag) => now.items.some((item) => flag(item) !== void 0);
|
|
1457
|
+
const countFlaggedNow = (part, flag, now, scope, terms) => saysWhichAre(now, flag) ? {
|
|
1458
|
+
label: metricsHeadlineLabel(part, scope, terms),
|
|
1459
|
+
value: String(now.items.filter((item) => flag(item) === true).length),
|
|
1460
|
+
detail: ""
|
|
1461
|
+
} : null;
|
|
1397
1462
|
/** "Blocked Work Items 2", or null when Lighthouse does not say which items are blocked. */
|
|
1398
|
-
const describeBlockedNow = (now, scope, terms) =>
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
return {
|
|
1402
|
-
label: metricsHeadlineLabel("blocked", scope, terms),
|
|
1403
|
-
value: String(blocked),
|
|
1404
|
-
detail: ""
|
|
1405
|
-
};
|
|
1406
|
-
};
|
|
1463
|
+
const describeBlockedNow = (now, scope, terms) => countFlaggedNow("blocked", blockedFlag, now, scope, terms);
|
|
1464
|
+
/** "Stale Work Items 2", or null when Lighthouse does not say which items are stale. */
|
|
1465
|
+
const describeStaleNow = (now, scope, terms) => countFlaggedNow("stale", staleFlag, now, scope, terms);
|
|
1407
1466
|
/**
|
|
1408
1467
|
* The sentences that stand in for what a WIP answer cannot show: no System WIP Limit, a Lighthouse that does
|
|
1409
|
-
* not say which Work Items are Blocked (which is not the same as none being
|
|
1410
|
-
* A limit of null means the Team or Portfolio could not be read; it may well have one, so nothing
|
|
1468
|
+
* not say which Work Items are Blocked or Stale (which is not the same as none being so), and nothing in
|
|
1469
|
+
* progress. A limit of null means the Team or Portfolio could not be read; it may well have one, so nothing
|
|
1470
|
+
* is said.
|
|
1411
1471
|
*/
|
|
1412
1472
|
const describeWhatWipLeavesUnsaid = (now, scope, terms, systemWipLimit) => {
|
|
1413
1473
|
const counted = countedOf(scope, terms);
|
|
1414
|
-
const
|
|
1474
|
+
const silentOn = (flag) => now.items.length > 0 && !saysWhichAre(now, flag);
|
|
1415
1475
|
return [
|
|
1416
1476
|
...systemWipLimit === void 0 ? [`No System ${terms.wip} Limit is set.`] : [],
|
|
1417
|
-
...
|
|
1477
|
+
...silentOn(blockedFlag) ? [`Lighthouse does not say which ${counted.many} are ${terms.blocked}.`] : [],
|
|
1478
|
+
...silentOn(staleFlag) ? [`Lighthouse does not say which ${counted.many} are Stale.`] : [],
|
|
1418
1479
|
...now.items.length === 0 ? [`No ${counted.many} are in progress.`] : []
|
|
1419
1480
|
];
|
|
1420
1481
|
};
|
|
@@ -2118,6 +2179,114 @@ const readNameOfMyVote = async (client, teamId, workItem, voterKey) => {
|
|
|
2118
2179
|
return log.ok ? nameOfMyVote(log.value) : null;
|
|
2119
2180
|
};
|
|
2120
2181
|
//#endregion
|
|
2182
|
+
//#region src/reportWording.ts
|
|
2183
|
+
const readWindow = (value) => isRecord(value) && isDay(value.start) && isDay(value.end) ? {
|
|
2184
|
+
start: value.start,
|
|
2185
|
+
end: value.end
|
|
2186
|
+
} : null;
|
|
2187
|
+
/** The report's name and windows, or null when the answer does not carry all three. */
|
|
2188
|
+
const readReport = (value) => {
|
|
2189
|
+
if (!isRecord(value) || !isRecord(value.thenAndNow)) return null;
|
|
2190
|
+
const name = textOf(value.name);
|
|
2191
|
+
const thenWindow = readWindow(value.thenAndNow.thenWindow);
|
|
2192
|
+
const nowWindow = readWindow(value.thenAndNow.nowWindow);
|
|
2193
|
+
if (name === void 0 || thenWindow === null || nowWindow === null) return null;
|
|
2194
|
+
return {
|
|
2195
|
+
name,
|
|
2196
|
+
thenWindow,
|
|
2197
|
+
nowWindow
|
|
2198
|
+
};
|
|
2199
|
+
};
|
|
2200
|
+
const readReportListItem = (value) => {
|
|
2201
|
+
if (!isRecord(value) || typeof value.id !== "number") return null;
|
|
2202
|
+
const name = textOf(value.name);
|
|
2203
|
+
if (name === void 0) return null;
|
|
2204
|
+
return {
|
|
2205
|
+
id: value.id,
|
|
2206
|
+
name,
|
|
2207
|
+
templateKey: textOf(value.templateKey),
|
|
2208
|
+
createdOn: textOf(value.createdOn)
|
|
2209
|
+
};
|
|
2210
|
+
};
|
|
2211
|
+
/**
|
|
2212
|
+
* Every report in the list, or null when the answer holds no list or any one report comes without its id or
|
|
2213
|
+
* name: a row that cannot say which report it is would misname the whole list.
|
|
2214
|
+
*/
|
|
2215
|
+
const readReportList = (value) => {
|
|
2216
|
+
if (!isRecord(value) || !Array.isArray(value.reports)) return null;
|
|
2217
|
+
const items = value.reports.map(readReportListItem);
|
|
2218
|
+
return items.every((item) => item !== null) ? items : null;
|
|
2219
|
+
};
|
|
2220
|
+
const ownerTerm$1 = (kind, terms) => kind === "team" ? terms.team : terms.portfolio;
|
|
2221
|
+
/** The owner as the instance names it, "Team Lightspeed", or by its id when its name could not be read. */
|
|
2222
|
+
const describeReportOwner = (kind, id, owner, terms) => {
|
|
2223
|
+
const term = ownerTerm$1(kind, terms);
|
|
2224
|
+
const name = isRecord(owner) ? textOf(owner.name) : void 0;
|
|
2225
|
+
return name === void 0 ? `${term} [id: ${id}]` : `${term} ${name}`;
|
|
2226
|
+
};
|
|
2227
|
+
const describeWindow = (label, window) => `${label} ${dayOf(window.start)} – ${dayOf(window.end)}`;
|
|
2228
|
+
/** The report's name, its owner and its two windows, one to a line. */
|
|
2229
|
+
const describeReportSummary = (report, owner) => [
|
|
2230
|
+
report.name,
|
|
2231
|
+
owner,
|
|
2232
|
+
`${describeWindow("Then", report.thenWindow)} · ${describeWindow("Now", report.nowWindow)}`
|
|
2233
|
+
].join("\n");
|
|
2234
|
+
const describeReportCount = (count) => {
|
|
2235
|
+
if (count === 0) return "No reports";
|
|
2236
|
+
return count === 1 ? "1 report" : `${count} reports`;
|
|
2237
|
+
};
|
|
2238
|
+
const describeReportListItem = (item) => [
|
|
2239
|
+
`${item.name} [id: ${item.id}]`,
|
|
2240
|
+
item.templateKey,
|
|
2241
|
+
item.createdOn === void 0 ? void 0 : `created ${dayOf(item.createdOn)}`
|
|
2242
|
+
].filter((part) => part !== void 0).join(" · ");
|
|
2243
|
+
/** How many reports the owner has, then each report's name, type and created day. */
|
|
2244
|
+
const describeReportListSummary = (reports, owner) => [`${owner} · ${describeReportCount(reports.length)}`, ...reports.map(describeReportListItem)].join("\n");
|
|
2245
|
+
const summariseReport = (value, owner) => {
|
|
2246
|
+
const report = readReport(value);
|
|
2247
|
+
return report === null ? null : describeReportSummary(report, owner);
|
|
2248
|
+
};
|
|
2249
|
+
const summariseReportList = (value, owner) => {
|
|
2250
|
+
const reports = readReportList(value);
|
|
2251
|
+
return reports === null ? null : describeReportListSummary(reports, owner);
|
|
2252
|
+
};
|
|
2253
|
+
const FORBIDDEN = 403;
|
|
2254
|
+
const NOT_FOUND = 404;
|
|
2255
|
+
const NOTHING_THERE = {
|
|
2256
|
+
list: (owner) => `There is no such ${owner}.`,
|
|
2257
|
+
report: (owner) => `There is no such report for that ${owner}.`
|
|
2258
|
+
};
|
|
2259
|
+
/**
|
|
2260
|
+
* Why a report or a list could not be read, in words the reader can act on: the web's sentence where it has
|
|
2261
|
+
* one. Any other refusal, the client's own "upgrade Lighthouse" among them, keeps its category and reason.
|
|
2262
|
+
*/
|
|
2263
|
+
const describeReportRefusal = (error, kind, read, terms) => {
|
|
2264
|
+
if (error.statusCode === FORBIDDEN) return `You have no access to that ${ownerTerm$1(kind, terms)}'s reports.`;
|
|
2265
|
+
if (error.problemCode === "template-not-supported") return "This report was made by a newer version of Lighthouse and can't be shown here.";
|
|
2266
|
+
if (error.statusCode === NOT_FOUND) return NOTHING_THERE[read](ownerTerm$1(kind, terms));
|
|
2267
|
+
return `${error.category}: ${error.reason}`;
|
|
2268
|
+
};
|
|
2269
|
+
/**
|
|
2270
|
+
* {@link describeReportRefusal} in the instance's own words. A refusal the client made before asking, as for a
|
|
2271
|
+
* Lighthouse too old for reports, asks that Lighthouse nothing more, not even its words.
|
|
2272
|
+
*/
|
|
2273
|
+
const readReportRefusal = async (source, error, kind, read) => describeReportRefusal(error, kind, read, error.statusCode === void 0 ? SEEDED_TERMS : await readTerms(source));
|
|
2274
|
+
//#endregion
|
|
2275
|
+
//#region src/reportReads.ts
|
|
2276
|
+
/** The owner's list of reports, exactly as Lighthouse answers it. */
|
|
2277
|
+
const readOwnersReports = (reads, owner) => owner.kind === "team" ? reads.getTeamReports(owner.id) : reads.getPortfolioReports(owner.id);
|
|
2278
|
+
/** One of the owner's reports, exactly as Lighthouse answers it. */
|
|
2279
|
+
const readOwnersReport = (reads, owner, reportId) => owner.kind === "team" ? reads.getTeamReport(owner.id, reportId) : reads.getPortfolioReport(owner.id, reportId);
|
|
2280
|
+
/**
|
|
2281
|
+
* {@link describeReportOwner} for an owner read from Lighthouse. Its name only labels a summary, so an owner that
|
|
2282
|
+
* cannot be read is named by its id instead, and this never fails.
|
|
2283
|
+
*/
|
|
2284
|
+
const readReportOwner = async (source, owner) => {
|
|
2285
|
+
const read = owner.kind === "team" ? source.getTeam(owner.id) : source.getPortfolio(owner.id);
|
|
2286
|
+
const [terms, named] = await Promise.all([readTerms(source), read.then((answer) => answer.ok ? answer.value : void 0, () => void 0)]);
|
|
2287
|
+
return describeReportOwner(owner.kind, owner.id, named, terms);
|
|
2288
|
+
};
|
|
2289
|
+
//#endregion
|
|
2121
2290
|
//#region src/usageDataReporter.ts
|
|
2122
2291
|
/** A command's own answer never waits on usage data for longer than this, whatever Lighthouse does. */
|
|
2123
2292
|
const USAGE_DATA_BUDGET_MS = 1e3;
|
|
@@ -3159,7 +3328,9 @@ const FEATURE_REQUIRES_SERVER_NEWER_THAN = {
|
|
|
3159
3328
|
deliveryMetricsHistory: "v26.5.29.5",
|
|
3160
3329
|
sleRisk: "v26.9.19.10",
|
|
3161
3330
|
teamRefinement: "v26.10.3.6",
|
|
3162
|
-
refinementVotes: "v26.10.3.6"
|
|
3331
|
+
refinementVotes: "v26.10.3.6",
|
|
3332
|
+
staleness: "v26.10.16.6",
|
|
3333
|
+
reports: "v26.10.10.1"
|
|
3163
3334
|
};
|
|
3164
3335
|
/**
|
|
3165
3336
|
* Parses a Lighthouse version string ("v26.5.24.10", "26.5.24") into numeric
|
|
@@ -3221,6 +3392,7 @@ const createLighthouseClient = (configuration, dependencies = {}) => {
|
|
|
3221
3392
|
}
|
|
3222
3393
|
};
|
|
3223
3394
|
};
|
|
3395
|
+
const readReports = async (route) => await ensureServerSupports("reports") ?? requestJson(configuration, dependencies, route, { method: "GET" });
|
|
3224
3396
|
return {
|
|
3225
3397
|
checkConnectivity: async () => {
|
|
3226
3398
|
const fetchDependency = getFetchDependency(dependencies);
|
|
@@ -3297,6 +3469,10 @@ const createLighthouseClient = (configuration, dependencies = {}) => {
|
|
|
3297
3469
|
headers: voterKeyHeaders(options?.voterKey)
|
|
3298
3470
|
});
|
|
3299
3471
|
},
|
|
3472
|
+
getTeamReports: async (teamId) => readReports(`/v1/teams/${teamId}/reports`),
|
|
3473
|
+
getTeamReport: async (teamId, reportId) => readReports(`/v1/teams/${teamId}/reports/${reportId}`),
|
|
3474
|
+
getPortfolioReports: async (portfolioId) => readReports(`/v1/portfolios/${portfolioId}/reports`),
|
|
3475
|
+
getPortfolioReport: async (portfolioId, reportId) => readReports(`/v1/portfolios/${portfolioId}/reports/${reportId}`),
|
|
3300
3476
|
getTerminology: async () => requestJson(configuration, dependencies, "/v1/terminology/all", { method: "GET" }),
|
|
3301
3477
|
listPortfolios: async () => requestJson(configuration, dependencies, "/v1/portfolios", { method: "GET" }),
|
|
3302
3478
|
getPortfolio: async (portfolioId) => requestJson(configuration, dependencies, `/v1/portfolios/${portfolioId}`, { method: "GET" }),
|
|
@@ -3706,6 +3882,8 @@ exports.describeOwnerListHeadings = describeOwnerListHeadings;
|
|
|
3706
3882
|
exports.describeOwnerListTitle = describeOwnerListTitle;
|
|
3707
3883
|
exports.describeOwnerName = describeOwnerName;
|
|
3708
3884
|
exports.describeOwnerWriteConfirmation = describeOwnerWriteConfirmation;
|
|
3885
|
+
exports.describePercentileDate = describePercentileDate;
|
|
3886
|
+
exports.describePercentileDay = describePercentileDay;
|
|
3709
3887
|
exports.describePercentileRows = describePercentileRows;
|
|
3710
3888
|
exports.describePercentilesOverTime = describePercentilesOverTime;
|
|
3711
3889
|
exports.describePercentilesOverTimeDays = describePercentilesOverTimeDays;
|
|
@@ -3725,7 +3903,12 @@ exports.describeRecordedVote = describeRecordedVote;
|
|
|
3725
3903
|
exports.describeRefinementSummary = describeRefinementSummary;
|
|
3726
3904
|
exports.describeRefreshConfirmation = describeRefreshConfirmation;
|
|
3727
3905
|
exports.describeRefusedMetric = describeRefusedMetric;
|
|
3906
|
+
exports.describeReportListSummary = describeReportListSummary;
|
|
3907
|
+
exports.describeReportOwner = describeReportOwner;
|
|
3908
|
+
exports.describeReportRefusal = describeReportRefusal;
|
|
3909
|
+
exports.describeReportSummary = describeReportSummary;
|
|
3728
3910
|
exports.describeSleRiskNow = describeSleRiskNow;
|
|
3911
|
+
exports.describeStaleNow = describeStaleNow;
|
|
3729
3912
|
exports.describeTags = describeTags;
|
|
3730
3913
|
exports.describeTakenBack = describeTakenBack;
|
|
3731
3914
|
exports.describeTeamSummary = describeTeamSummary;
|
|
@@ -3774,6 +3957,7 @@ exports.likelihoodAnswer = likelihoodAnswer;
|
|
|
3774
3957
|
exports.loadStandaloneDiscoveryContract = loadStandaloneDiscoveryContract;
|
|
3775
3958
|
exports.metricsHeadlineLabel = metricsHeadlineLabel;
|
|
3776
3959
|
exports.mintVoterKey = mintVoterKey;
|
|
3960
|
+
exports.moreThanYearsText = moreThanYearsText;
|
|
3777
3961
|
exports.myVoteOn = myVoteOn;
|
|
3778
3962
|
exports.nameOrFallback = nameOrFallback;
|
|
3779
3963
|
exports.ordinalOf = ordinalOf;
|
|
@@ -3805,6 +3989,8 @@ exports.readMetricAnswer = readMetricAnswer;
|
|
|
3805
3989
|
exports.readMetricsSubject = readMetricsSubject;
|
|
3806
3990
|
exports.readNameOfMyVote = readNameOfMyVote;
|
|
3807
3991
|
exports.readOwnerList = readOwnerList;
|
|
3992
|
+
exports.readOwnersReport = readOwnersReport;
|
|
3993
|
+
exports.readOwnersReports = readOwnersReports;
|
|
3808
3994
|
exports.readPercentilesOverTime = readPercentilesOverTime;
|
|
3809
3995
|
exports.readPortfolio = readPortfolio;
|
|
3810
3996
|
exports.readPortfolioDeliveries = readPortfolioDeliveries;
|
|
@@ -3813,6 +3999,10 @@ exports.readProcessBehaviorChart = readProcessBehaviorChart;
|
|
|
3813
3999
|
exports.readProcessBehaviorOverTime = readProcessBehaviorOverTime;
|
|
3814
4000
|
exports.readRefinementTerms = readRefinementTerms;
|
|
3815
4001
|
exports.readRefinementWording = readRefinementWording;
|
|
4002
|
+
exports.readReport = readReport;
|
|
4003
|
+
exports.readReportList = readReportList;
|
|
4004
|
+
exports.readReportOwner = readReportOwner;
|
|
4005
|
+
exports.readReportRefusal = readReportRefusal;
|
|
3816
4006
|
exports.readRunChart = readRunChart;
|
|
3817
4007
|
exports.readServiceLevelExpectation = readServiceLevelExpectation;
|
|
3818
4008
|
exports.readSleRisk = readSleRisk;
|
|
@@ -3834,14 +4024,18 @@ exports.readWorkTrackingConnections = readWorkTrackingConnections;
|
|
|
3834
4024
|
exports.readWrittenBlackoutRule = readWrittenBlackoutRule;
|
|
3835
4025
|
exports.readWrittenOwner = readWrittenOwner;
|
|
3836
4026
|
exports.refinementDayVerdictOf = refinementDayVerdictOf;
|
|
4027
|
+
exports.reportOpenedOccurrencesOf = reportOpenedOccurrencesOf;
|
|
3837
4028
|
exports.resolveTerms = resolveTerms;
|
|
3838
4029
|
exports.settleUsageDataStep = settleUsageDataStep;
|
|
3839
4030
|
exports.sizingMomentOf = sizingMomentOf;
|
|
3840
4031
|
exports.startCliAuthSession = startCliAuthSession;
|
|
3841
4032
|
exports.summariseDeliveryMetricsHistory = summariseDeliveryMetricsHistory;
|
|
4033
|
+
exports.summariseReport = summariseReport;
|
|
4034
|
+
exports.summariseReportList = summariseReportList;
|
|
3842
4035
|
exports.switchUsageDataOn = switchUsageDataOn;
|
|
3843
4036
|
exports.timeInStateItemCount = timeInStateItemCount;
|
|
3844
4037
|
exports.usageDataStoreFor = usageDataStoreFor;
|
|
3845
4038
|
exports.validateLighthouseConnectivity = validateLighthouseConnectivity;
|
|
3846
4039
|
exports.voterKeyStoreFor = voterKeyStoreFor;
|
|
3847
4040
|
exports.withdrawUsageData = withdrawUsageData;
|
|
4041
|
+
exports.yearsBeyondHorizon = yearsBeyondHorizon;
|