@hot-updater/console 1.1.3 → 1.1.4
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/.output/nitro.json +1 -1
- package/.output/public/assets/EventDetails-gbD_fCqc.js +1 -0
- package/.output/public/assets/{InsightsPageHeader-SYEOMClX.js → InsightsPageHeader-BRw8XkJW.js} +1 -1
- package/.output/public/assets/api-keys-D81VHHNM.js +1 -0
- package/.output/public/assets/api-keys-Dia6i8K1.js +1 -0
- package/.output/public/assets/badge-Bqf3UDee.js +1 -0
- package/.output/public/assets/circle-off-BsbZQtsF.js +1 -0
- package/.output/public/assets/field-GxF9SyUo.js +1 -0
- package/.output/public/assets/{index-C5S4DrO2.js → index-DrkdiRIn.js} +3 -3
- package/.output/public/assets/insights-C-5t8wh2.js +2 -0
- package/.output/public/assets/insights-D_ngQdiz.js +1 -0
- package/.output/public/assets/insights-KigRBDgC.js +42 -0
- package/.output/public/assets/insights-api-1ZjVu3Sb.js +1 -0
- package/.output/public/assets/insights-search-aT8BVCci.js +1 -0
- package/.output/public/assets/{insights-usage-rpc-DTL0_Rp8.js → insights-usage-rpc-DqTiXI1_.js} +1 -1
- package/.output/public/assets/insights_.distribution-Bzl6RFBR.js +2 -0
- package/.output/public/assets/insights_.distribution-Cthgttqb.js +1 -0
- package/.output/public/assets/insights_.distribution-D2c2v1bu.js +1 -0
- package/.output/public/assets/installations-2Nx8Htf-.js +1 -0
- package/.output/public/assets/{installations-we1IV0xp.js → installations-BskJ1xQr.js} +2 -2
- package/.output/public/assets/installations-K2iJ_r0h.js +1 -0
- package/.output/public/assets/{remote-config-uBgCaI-p.js → remote-config-D9X3NiwV.js} +4 -4
- package/.output/public/assets/{remote-config-DyTLPRKh.js → remote-config-DzOOiBIO.js} +1 -1
- package/.output/public/assets/remote-config-ikg7kak0.js +1 -0
- package/.output/public/assets/routes-9rb9P5Y7.js +2 -0
- package/.output/public/assets/routes-P7683vKQ.js +1 -0
- package/.output/public/assets/{select-CV8nkfsf.js → select-M3H2gxCc.js} +1 -1
- package/.output/public/assets/styles-CpiW_YdW.css +2 -0
- package/.output/public/assets/{switch-C8ycp9AH.js → switch-By_vfm8I.js} +1 -1
- package/.output/server/{__23tanstack-start-server-fn-resolver-Gb7cVpng.mjs → __23tanstack-start-server-fn-resolver-KpQ5Z_24.mjs} +40 -40
- package/.output/server/_libs/@base-ui/react+[...].mjs +77 -1
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +1 -1
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +2 -2
- package/.output/server/_libs/lucide-react.mjs +1 -1
- package/.output/server/_libs/next-themes.mjs +1 -1
- package/.output/server/_libs/react-redux+[...].mjs +1 -1
- package/.output/server/_libs/recharts+[...].mjs +1 -1
- package/.output/server/_libs/sonner.mjs +1 -1
- package/.output/server/_libs/tanstack__react-query.mjs +1 -1
- package/.output/server/_ssr/{BundleIdDisplay-DDiNTVT3.mjs → BundleIdDisplay-CjSnY65J.mjs} +3 -3
- package/.output/server/_ssr/{ConsoleFeatureUnavailable-DE65d293.mjs → ConsoleFeatureUnavailable-CHxn9f-w.mjs} +7 -7
- package/.output/server/_ssr/{EventDetails-Bg1V3JsJ.mjs → EventDetails-l6dF54zp.mjs} +8 -8
- package/.output/server/_ssr/{HashValueDisplay-BDHvSsxT.mjs → HashValueDisplay-Zs7ABoOi.mjs} +3 -3
- package/.output/server/_ssr/{InsightsPageHeader-Db-y_PRS.mjs → InsightsPageHeader-ZlAlDxQy.mjs} +8 -8
- package/.output/server/_ssr/{NotFoundPage-DHgL7Uzm.mjs → NotFoundPage-u28ndvp1.mjs} +3 -3
- package/.output/server/_ssr/{alert-dialog-MbJW0K9H.mjs → alert-dialog-B8AqlJwo.mjs} +2 -2
- package/.output/server/_ssr/{api-XOdKU3tw.mjs → api-C_0B7NXM.mjs} +3 -3
- package/.output/server/_ssr/{api-keys-v7wa2_rX.mjs → api-keys-CLH38u9B.mjs} +2 -2
- package/.output/server/_ssr/{api-keys-BHJdcnez.mjs → api-keys-pRNvn2-9.mjs} +9 -8
- package/.output/server/_ssr/{api-keys-rpc-B389z2LY.mjs → api-keys-rpc-wecWs0nA.mjs} +3 -3
- package/.output/server/_ssr/{api-rpc-T6dVPhPr.mjs → api-rpc-DWN8eOHS.mjs} +6 -41
- package/.output/server/_ssr/{auth-rpc-CzPT9U2N.mjs → auth-rpc-CLRArbIl.mjs} +4 -4
- package/.output/server/_ssr/{auth.server-LmCV7BTA.mjs → auth.server-DR9zc0EU.mjs} +2 -2
- package/.output/server/_ssr/badge-BpT7r-ng.mjs +108 -0
- package/.output/server/_ssr/{bundleActivity-M5gzBFNg.mjs → bundleActivity-0qFIcvqt.mjs} +1 -1
- package/.output/server/_ssr/{config.server-COvZCTA4.mjs → config.server-DTdsG47T.mjs} +4 -4
- package/.output/server/_ssr/{console-access-CRAcskRF.mjs → console-access-aUpQs49I.mjs} +2 -2
- package/.output/server/_ssr/{console-features-Oz0q20jJ.mjs → console-features-4xAGfZHw.mjs} +1 -1
- package/.output/server/_ssr/{console-features-api-qihYDNRo.mjs → console-features-api-DSSgCV3v.mjs} +5 -5
- package/.output/server/_ssr/{console-features-rpc-r1KcSohP.mjs → console-features-rpc-D6kMlR2A.mjs} +3 -3
- package/.output/server/_ssr/{dialog-CPbM7GQc.mjs → dialog-B4ALf7NJ.mjs} +2 -2
- package/.output/server/_ssr/{downloadBundle-CZysjmNW.mjs → downloadBundle-94jPBGSO.mjs} +1 -1
- package/.output/server/_ssr/{empty-8scgvztU.mjs → empty-Bh1FEeYF.mjs} +3 -3
- package/.output/server/_ssr/{failureReports-DKDhVsXA.mjs → failureReports-Bh22wois.mjs} +4 -4
- package/.output/server/_ssr/field-HKCaPH3e.mjs +89 -0
- package/.output/server/_ssr/{getBundleChildren-Cv9iz1As.mjs → getBundleChildren-DfxB2K8D.mjs} +1 -1
- package/.output/server/_ssr/{input-group-BTB34g3v.mjs → input-group-xm3LzDuq.mjs} +2 -2
- package/.output/server/_ssr/{insights-CGpn8W5V.mjs → insights-BKZpJ_K4.mjs} +590 -360
- package/.output/server/_ssr/{insights-BulsRVnt.mjs → insights-CCyTACIw.mjs} +5 -5
- package/.output/server/_ssr/{insights-Btnkt5-Y.mjs → insights-CNqWQLJR.mjs} +2 -2
- package/.output/server/_ssr/{insights-api-Dg3D7Vkg.mjs → insights-api-DtRFz5X3.mjs} +5 -5
- package/.output/server/_ssr/{insights-errors-BjnFUyZj.mjs → insights-errors-Iy-lBaM7.mjs} +2 -2
- package/.output/server/_ssr/{insights-failures-xaa41Buf.mjs → insights-failures-BJN3RfLu.mjs} +3 -3
- package/.output/server/_ssr/{insights-failures-rpc-DdK55wRd.mjs → insights-failures-rpc-DLZ91uOG.mjs} +12 -12
- package/.output/server/_ssr/{insights-recovery-BWQhiZaF.mjs → insights-recovery-BUuQdlA3.mjs} +1 -1
- package/.output/server/_ssr/{insights-recovery-rpc-D-dwlIPT.mjs → insights-recovery-rpc-BQHUhPi7.mjs} +6 -6
- package/.output/server/_ssr/{insights-rpc-Dn-jdkQK.mjs → insights-rpc-C6Vukyn7.mjs} +3 -3
- package/.output/server/_ssr/{insights-search-C8EWA0hu.mjs → insights-search-CBYoxT7v.mjs} +8 -7
- package/.output/server/_ssr/{insights-usage-BpBM0U0c.mjs → insights-usage-CHqvtehH.mjs} +1 -1
- package/.output/server/_ssr/{insights-usage-rpc-DIa-5vUO.mjs → insights-usage-rpc-BJ7a-xco.mjs} +6 -6
- package/.output/server/_ssr/{insights-usage-rpc-BCulD17c.mjs → insights-usage-rpc-C-lNUIsD.mjs} +5 -5
- package/.output/server/_ssr/{insightsUsage-CzhVjsc8.mjs → insightsUsage-COUG-_-U.mjs} +3 -3
- package/.output/server/_ssr/{insights_.distribution-CKe3T4k1.mjs → insights_.distribution-CTwilMPm.mjs} +5 -5
- package/.output/server/_ssr/{insights_.distribution-D7ZzykQb.mjs → insights_.distribution-DmLW8I8a.mjs} +2 -2
- package/.output/server/_ssr/{insights_.distribution-CE0ve_Rx.mjs → insights_.distribution-DztDyrBj.mjs} +14 -13
- package/.output/server/_ssr/{installations-CJHIYH0u.mjs → installations-B6MK5iA8.mjs} +2 -2
- package/.output/server/_ssr/{installations-Bu4Sk4W6.mjs → installations-Bct7ag5y.mjs} +4 -4
- package/.output/server/_ssr/{installations-CGMSRurj.mjs → installations-_N4PtYFl.mjs} +12 -11
- package/.output/server/_ssr/{release-adoption-005tQRpo.mjs → release-adoption-DuYJTGuX.mjs} +17 -7
- package/.output/server/_ssr/{release-adoption-rpc-DPMYNIlM.mjs → release-adoption-rpc-CHl7G_Zy.mjs} +8 -8
- package/.output/server/_ssr/{releaseAdoption-CtOFkwli.mjs → releaseAdoption-B39cpvfo.mjs} +42 -26
- package/.output/server/_ssr/releaseReachability-bFsuElSS.mjs +39 -0
- package/.output/server/_ssr/{remote-config-BCW6vPOD.mjs → remote-config-10-obSZ0.mjs} +3 -3
- package/.output/server/_ssr/{remote-config-DrC_3H7K.mjs → remote-config-Dj1eTYi4.mjs} +2 -2
- package/.output/server/_ssr/{remote-config-Ca-Wv5sF.mjs → remote-config-Mj05qVYZ.mjs} +18 -17
- package/.output/server/_ssr/{remote-config-rpc-CcGmTmvS.mjs → remote-config-rpc-lteqDSLn.mjs} +3 -3
- package/.output/server/_ssr/{router-C5eI7aq7.mjs → router-Dr4jjjhx.mjs} +17 -17
- package/.output/server/_ssr/{routes-Bd3qlRny.mjs → routes-B5KrCDwk.mjs} +17 -16
- package/.output/server/_ssr/{routes-DZoij0js.mjs → routes-BZbrY_qw.mjs} +17 -17
- package/.output/server/_ssr/{runtime.server-De0QVWc9.mjs → runtime.server-DsHqVqTr.mjs} +2 -2
- package/.output/server/_ssr/{select-usyW0pcU.mjs → select-BtdGNSmp.mjs} +2 -2
- package/.output/server/_ssr/{server-Bp9nEe3Z.mjs → server-BxaHSwHh.mjs} +4 -4
- package/.output/server/_ssr/{sidebar-Bqu4emj1.mjs → sidebar-CVsgosaD.mjs} +3 -3
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/{switch-DJPcwvwD.mjs → switch-BmARgkUP.mjs} +3 -3
- package/.output/server/_ssr/{table-DEzdHi1t.mjs → table-M5P3YUfY.mjs} +2 -2
- package/.output/server/_ssr/{tabs-HzTSngsE.mjs → tabs-CrmfOlJj.mjs} +3 -3
- package/.output/server/_ssr/updateFailures-z1IQy_4q.mjs +103 -0
- package/.output/server/{_tanstack-start-manifest_v-DTOQk1l4.mjs → _tanstack-start-manifest_v-_BY9hYTm.mjs} +51 -50
- package/.output/server/index.mjs +198 -191
- package/package.json +7 -7
- package/src/components/features/insights/AppUsage.tsx +15 -2
- package/src/components/features/insights/InsightsControls.tsx +264 -246
- package/src/components/features/insights/InsightsOverview.tsx +90 -68
- package/src/components/features/insights/InsightsScope.tsx +39 -0
- package/src/components/features/insights/ReleaseActivity.tsx +4 -4
- package/src/components/features/insights/ReleaseCombobox.tsx +196 -0
- package/src/components/features/insights/RollbackButton.tsx +4 -0
- package/src/components/features/insights/UpdateFailures.tsx +7 -1
- package/src/lib/insights-failures-rpc.ts +3 -1
- package/src/lib/insights-failures.ts +5 -2
- package/src/lib/insights-recovery.ts +5 -0
- package/src/lib/insights-search.ts +10 -14
- package/src/lib/release-adoption-api.ts +24 -32
- package/src/lib/release-adoption.ts +45 -8
- package/src/lib/server/failureReports.ts +1 -1
- package/src/lib/server/releaseAdoption.ts +75 -26
- package/src/lib/server/updateFailures.ts +131 -10
- package/src/routes/insights.tsx +56 -24
- package/src/routes/insights_.distribution.tsx +2 -1
- package/.output/public/assets/EventDetails-CSX2ayg9.js +0 -1
- package/.output/public/assets/api-keys-BEdLKiib.js +0 -1
- package/.output/public/assets/api-keys-TdTtKm2b.js +0 -1
- package/.output/public/assets/chevron-right-BnvHIJdZ.js +0 -1
- package/.output/public/assets/field-DBMkBfkD.js +0 -1
- package/.output/public/assets/insights-BMKRCIg1.js +0 -2
- package/.output/public/assets/insights-CPyViuyC.js +0 -42
- package/.output/public/assets/insights-D0PbX-Rm.js +0 -1
- package/.output/public/assets/insights-api-Dmd_LgT3.js +0 -1
- package/.output/public/assets/insights-search-DAa0f7Dr.js +0 -1
- package/.output/public/assets/insights_.distribution-BaOQlBV9.js +0 -1
- package/.output/public/assets/insights_.distribution-CxXMIEb5.js +0 -2
- package/.output/public/assets/insights_.distribution-EE5OqxyL.js +0 -1
- package/.output/public/assets/installations-BgISglUQ.js +0 -1
- package/.output/public/assets/installations-DQbgRkSi.js +0 -1
- package/.output/public/assets/remote-config-AQmdmbT8.js +0 -1
- package/.output/public/assets/routes-Br26jzt9.js +0 -1
- package/.output/public/assets/routes-CyirpG2O.js +0 -2
- package/.output/public/assets/styles-DTGEAJFv.css +0 -2
- package/.output/server/_ssr/field-DHrweW4l.mjs +0 -189
- package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +0 -40
- /package/.output/public/assets/{ConsoleFeatureUnavailable-B-iNWpuE.js → ConsoleFeatureUnavailable-C0KfsKWL.js} +0 -0
- /package/.output/public/assets/{table-DgGD67Fx.js → table-ek1DmRHq.js} +0 -0
- /package/.output/public/assets/{valueToPercent-C4C1WZVn.js → valueToPercent-B44vDssN.js} +0 -0
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { Link } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CircleOff,
|
|
4
|
+
ListIcon,
|
|
5
|
+
RotateCcw,
|
|
6
|
+
RotateCw,
|
|
7
|
+
TriangleAlert,
|
|
8
|
+
X,
|
|
9
|
+
} from "lucide-react";
|
|
3
10
|
import { useState } from "react";
|
|
4
11
|
|
|
5
12
|
import { BundleIdDisplay } from "@/components/BundleIdDisplay";
|
|
6
13
|
import { InfoTooltip } from "@/components/InfoTooltip";
|
|
14
|
+
import { PlatformIcon } from "@/components/PlatformIcon";
|
|
7
15
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
8
16
|
import { Badge } from "@/components/ui/badge";
|
|
9
17
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
@@ -21,14 +29,6 @@ import {
|
|
|
21
29
|
EmptyHeader,
|
|
22
30
|
EmptyTitle,
|
|
23
31
|
} from "@/components/ui/empty";
|
|
24
|
-
import {
|
|
25
|
-
Select,
|
|
26
|
-
SelectContent,
|
|
27
|
-
SelectGroup,
|
|
28
|
-
SelectItem,
|
|
29
|
-
SelectTrigger,
|
|
30
|
-
SelectValue,
|
|
31
|
-
} from "@/components/ui/select";
|
|
32
32
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
33
33
|
import {
|
|
34
34
|
Table,
|
|
@@ -39,13 +39,12 @@ import {
|
|
|
39
39
|
TableRow,
|
|
40
40
|
} from "@/components/ui/table";
|
|
41
41
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
42
|
-
import type {
|
|
42
|
+
import type { ReleaseHealthInput } from "@/lib/insights-recovery";
|
|
43
43
|
import type { HealthChart } from "@/lib/insights-search";
|
|
44
44
|
import {
|
|
45
45
|
CRASH_MIN_ATTEMPTS,
|
|
46
46
|
CRASH_RATE_THRESHOLD,
|
|
47
47
|
crashRateOf,
|
|
48
|
-
DEFAULT_ADOPTION_RELEASES,
|
|
49
48
|
MAX_ADOPTION_RELEASES,
|
|
50
49
|
recommendsRollback,
|
|
51
50
|
} from "@/lib/release-adoption";
|
|
@@ -62,6 +61,8 @@ import {
|
|
|
62
61
|
} from "./ComparedBundlesChart";
|
|
63
62
|
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
64
63
|
import { InsightsPeriodSelector } from "./InsightsPeriodSelector";
|
|
64
|
+
import { InsightsScope, platformLabel } from "./InsightsScope";
|
|
65
|
+
import { ReleaseCombobox } from "./ReleaseCombobox";
|
|
65
66
|
import { RollbackButton } from "./RollbackButton";
|
|
66
67
|
|
|
67
68
|
const HOUR = 3_600_000;
|
|
@@ -113,7 +114,7 @@ function LineSample({ dashed }: { readonly dashed?: boolean }) {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
/**
|
|
116
|
-
* Release health: how the
|
|
117
|
+
* Release health: how the live bundles fare after their deployment, on
|
|
117
118
|
* one timeline, per interval. Adoption shows installations downloading each
|
|
118
119
|
* bundle (dashed) and launching it (solid), so the gap is the restarts still
|
|
119
120
|
* to come; Crashes shows them crashing back from it, and offers a rollback
|
|
@@ -126,22 +127,22 @@ export function InsightsOverview({
|
|
|
126
127
|
health,
|
|
127
128
|
onReleasesChange,
|
|
128
129
|
onWindowChange,
|
|
130
|
+
onShowAllPlatforms,
|
|
129
131
|
}: {
|
|
130
|
-
readonly input:
|
|
132
|
+
readonly input: ReleaseHealthInput;
|
|
131
133
|
readonly chart: HealthChart;
|
|
132
134
|
readonly onChartChange: (chart: HealthChart) => void;
|
|
133
135
|
readonly health: ReleaseHealthState;
|
|
134
136
|
/** Chooses the bundles to compare; undefined returns to the default. */
|
|
135
137
|
readonly onReleasesChange: (releaseIds?: readonly string[]) => void;
|
|
136
|
-
readonly onWindowChange: (window:
|
|
138
|
+
readonly onWindowChange: (window: ReleaseHealthInput["window"]) => void;
|
|
139
|
+
/** Widens the platform filter, offered when one platform has no bundles. */
|
|
140
|
+
readonly onShowAllPlatforms?: () => void;
|
|
137
141
|
}) {
|
|
138
142
|
const [active, setActive] = useState<string | null>(null);
|
|
139
143
|
const { period, candidates, releases } = health;
|
|
140
144
|
const ids = (releases ?? []).map(({ release }) => release.releaseId);
|
|
141
145
|
const colorOf = useSeriesColors(ids);
|
|
142
|
-
const addable = (candidates ?? []).filter(
|
|
143
|
-
(candidate) => !ids.includes(candidate.releaseId),
|
|
144
|
-
);
|
|
145
146
|
const full = ids.length >= MAX_ADOPTION_RELEASES;
|
|
146
147
|
const wider =
|
|
147
148
|
period.durationMs < 7 * DAY
|
|
@@ -156,7 +157,7 @@ export function InsightsOverview({
|
|
|
156
157
|
onClick={() => onReleasesChange(undefined)}
|
|
157
158
|
>
|
|
158
159
|
<RotateCcw aria-hidden="true" data-icon="inline-start" />
|
|
159
|
-
Show
|
|
160
|
+
Show live bundles
|
|
160
161
|
</Button>
|
|
161
162
|
);
|
|
162
163
|
// Adoption draws each bundle's launches solid and its downloads dashed;
|
|
@@ -241,14 +242,28 @@ export function InsightsOverview({
|
|
|
241
242
|
return (
|
|
242
243
|
<Empty className="min-h-64">
|
|
243
244
|
<EmptyHeader>
|
|
244
|
-
<EmptyTitle>
|
|
245
|
+
<EmptyTitle>
|
|
246
|
+
No bundles deployed to {input.channel} ·{" "}
|
|
247
|
+
{platformLabel(input.platform)}
|
|
248
|
+
</EmptyTitle>
|
|
245
249
|
<EmptyDescription>
|
|
246
|
-
|
|
247
|
-
channel and health platform. Deploy one, or choose another in the
|
|
248
|
-
filters.
|
|
250
|
+
Deploy one, or choose another channel or platform in the filters.
|
|
249
251
|
</EmptyDescription>
|
|
250
252
|
</EmptyHeader>
|
|
251
|
-
{reset
|
|
253
|
+
{reset || (input.platform !== "all" && onShowAllPlatforms) ? (
|
|
254
|
+
<EmptyContent className="flex-row flex-wrap justify-center">
|
|
255
|
+
{input.platform !== "all" && onShowAllPlatforms ? (
|
|
256
|
+
<Button
|
|
257
|
+
variant="outline"
|
|
258
|
+
size="sm"
|
|
259
|
+
onClick={onShowAllPlatforms}
|
|
260
|
+
>
|
|
261
|
+
Show all platforms
|
|
262
|
+
</Button>
|
|
263
|
+
) : null}
|
|
264
|
+
{reset}
|
|
265
|
+
</EmptyContent>
|
|
266
|
+
) : null}
|
|
252
267
|
</Empty>
|
|
253
268
|
);
|
|
254
269
|
}
|
|
@@ -339,7 +354,14 @@ export function InsightsOverview({
|
|
|
339
354
|
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
340
355
|
<CardHeader className="flex flex-col gap-4 p-6">
|
|
341
356
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
342
|
-
<
|
|
357
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
358
|
+
<CardTitle>Release health</CardTitle>
|
|
359
|
+
<InsightsScope
|
|
360
|
+
channel={input.channel}
|
|
361
|
+
platform={input.platform}
|
|
362
|
+
narrower={input.releaseId ? [`Release ${input.releaseId}`] : []}
|
|
363
|
+
/>
|
|
364
|
+
</div>
|
|
343
365
|
<div className="flex items-center gap-2">
|
|
344
366
|
<InsightsPeriodSelector
|
|
345
367
|
window={input.window}
|
|
@@ -419,6 +441,13 @@ export function InsightsOverview({
|
|
|
419
441
|
rate={bundle.rate}
|
|
420
442
|
crashes={bundle.crashes}
|
|
421
443
|
attempts={bundle.attempts}
|
|
444
|
+
// The live bundles no longer include it once it is
|
|
445
|
+
// disabled: keep comparing the same ones.
|
|
446
|
+
onRolledBack={
|
|
447
|
+
health.isDefault
|
|
448
|
+
? () => onReleasesChange(ids)
|
|
449
|
+
: undefined
|
|
450
|
+
}
|
|
422
451
|
/>
|
|
423
452
|
</AlertDescription>
|
|
424
453
|
</Alert>
|
|
@@ -500,11 +529,33 @@ export function InsightsOverview({
|
|
|
500
529
|
)}
|
|
501
530
|
</span>
|
|
502
531
|
<span className="text-xs break-words text-muted-foreground">
|
|
532
|
+
<PlatformIcon
|
|
533
|
+
platform={release.platform}
|
|
534
|
+
className="mr-1 inline size-3 align-[-1px]"
|
|
535
|
+
/>
|
|
536
|
+
<span className="sr-only">
|
|
537
|
+
{platformLabel(release.platform)}{" "}
|
|
538
|
+
</span>
|
|
503
539
|
Deployed{" "}
|
|
504
540
|
{formatDeployedAt(release.deployedAtMs)}
|
|
505
541
|
{release.targetAppVersion
|
|
506
542
|
? ` · ${release.targetAppVersion}`
|
|
507
543
|
: ""}
|
|
544
|
+
{release.enabled && !release.live ? (
|
|
545
|
+
<>
|
|
546
|
+
{" · "}
|
|
547
|
+
<span
|
|
548
|
+
className="inline-flex items-center gap-1 align-middle"
|
|
549
|
+
title="No device gets this bundle with the current delivery settings."
|
|
550
|
+
>
|
|
551
|
+
<CircleOff
|
|
552
|
+
aria-hidden="true"
|
|
553
|
+
className="size-3"
|
|
554
|
+
/>
|
|
555
|
+
Unreachable
|
|
556
|
+
</span>
|
|
557
|
+
</>
|
|
558
|
+
) : null}
|
|
508
559
|
</span>
|
|
509
560
|
</div>
|
|
510
561
|
</div>
|
|
@@ -577,51 +628,22 @@ export function InsightsOverview({
|
|
|
577
628
|
})}
|
|
578
629
|
</TableBody>
|
|
579
630
|
</Table>
|
|
580
|
-
{
|
|
581
|
-
<
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
`${candidate.releaseId} · ${formatDeployedAt(candidate.deployedAtMs)}`,
|
|
586
|
-
]),
|
|
587
|
-
)}
|
|
588
|
-
value={null}
|
|
631
|
+
{(candidates ?? []).length > 0 ? (
|
|
632
|
+
<ReleaseCombobox
|
|
633
|
+
scope={{ channel: input.channel, platform: input.platform }}
|
|
634
|
+
candidates={candidates ?? []}
|
|
635
|
+
excluded={ids}
|
|
589
636
|
disabled={full}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
full
|
|
601
|
-
? `Up to ${MAX_ADOPTION_RELEASES} bundles`
|
|
602
|
-
: "Add a bundle"
|
|
603
|
-
}
|
|
604
|
-
/>
|
|
605
|
-
</SelectTrigger>
|
|
606
|
-
<SelectContent>
|
|
607
|
-
<SelectGroup>
|
|
608
|
-
{addable.map((candidate) => (
|
|
609
|
-
<SelectItem
|
|
610
|
-
key={candidate.releaseId}
|
|
611
|
-
value={candidate.releaseId}
|
|
612
|
-
>
|
|
613
|
-
<span className="flex min-w-0 flex-col gap-0.5">
|
|
614
|
-
<BundleIdDisplay bundleId={candidate.releaseId} />{" "}
|
|
615
|
-
<span className="text-xs text-muted-foreground">
|
|
616
|
-
Deployed{" "}
|
|
617
|
-
{formatDeployedAt(candidate.deployedAtMs)}
|
|
618
|
-
</span>
|
|
619
|
-
</span>
|
|
620
|
-
</SelectItem>
|
|
621
|
-
))}
|
|
622
|
-
</SelectGroup>
|
|
623
|
-
</SelectContent>
|
|
624
|
-
</Select>
|
|
637
|
+
label="Add a bundle"
|
|
638
|
+
placeholder={
|
|
639
|
+
full
|
|
640
|
+
? `Up to ${MAX_ADOPTION_RELEASES} bundles`
|
|
641
|
+
: "Add a bundle by ID or app version…"
|
|
642
|
+
}
|
|
643
|
+
onPick={(release) =>
|
|
644
|
+
onReleasesChange([...ids, release.releaseId])
|
|
645
|
+
}
|
|
646
|
+
/>
|
|
625
647
|
) : null}
|
|
626
648
|
</>
|
|
627
649
|
) : null}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Fragment } from "react";
|
|
2
|
+
|
|
3
|
+
import { PlatformIcon } from "@/components/PlatformIcon";
|
|
4
|
+
import type { HealthPlatform } from "@/lib/release-adoption";
|
|
5
|
+
|
|
6
|
+
export const platformLabel = (platform: HealthPlatform) =>
|
|
7
|
+
platform === "all" ? "All platforms" : platform === "ios" ? "iOS" : "Android";
|
|
8
|
+
|
|
9
|
+
/** What a report reads, as the filters set it: its channel, platform, and any narrower filter. */
|
|
10
|
+
export function InsightsScope({
|
|
11
|
+
channel,
|
|
12
|
+
platform,
|
|
13
|
+
narrower = [],
|
|
14
|
+
}: {
|
|
15
|
+
readonly channel: string;
|
|
16
|
+
readonly platform: HealthPlatform;
|
|
17
|
+
readonly narrower?: readonly string[];
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<p className="flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5 text-xs text-muted-foreground">
|
|
21
|
+
<span className="max-w-full truncate font-medium text-foreground">
|
|
22
|
+
{channel}
|
|
23
|
+
</span>
|
|
24
|
+
<span aria-hidden="true">·</span>
|
|
25
|
+
<span className="inline-flex items-center gap-1">
|
|
26
|
+
{platform === "all" ? null : (
|
|
27
|
+
<PlatformIcon platform={platform} className="size-3" />
|
|
28
|
+
)}
|
|
29
|
+
{platformLabel(platform)}
|
|
30
|
+
</span>
|
|
31
|
+
{narrower.map((filter) => (
|
|
32
|
+
<Fragment key={filter}>
|
|
33
|
+
<span aria-hidden="true">·</span>
|
|
34
|
+
<span className="max-w-full truncate">{filter}</span>
|
|
35
|
+
</Fragment>
|
|
36
|
+
))}
|
|
37
|
+
</p>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -140,8 +140,8 @@ export function BundleMovementSummary({
|
|
|
140
140
|
className="min-w-0 rounded-sm hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
141
141
|
to="/insights"
|
|
142
142
|
search={{
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
platform: input.platform,
|
|
144
|
+
channel: input.channel,
|
|
145
145
|
releaseId: input.releaseId,
|
|
146
146
|
bundleWindow: adoptionWindow(input.releaseId),
|
|
147
147
|
}}
|
|
@@ -241,8 +241,8 @@ export function BundleInsightsSummary({
|
|
|
241
241
|
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
242
242
|
to="/insights"
|
|
243
243
|
search={{
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
platform: input.platform,
|
|
245
|
+
channel: input.channel,
|
|
246
246
|
releaseId: input.releaseId,
|
|
247
247
|
bundleWindow: adoptionWindow(input.releaseId),
|
|
248
248
|
healthChart: "adoption",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { isUUIDv7 } from "@hot-updater/protocol";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { CircleOff } from "lucide-react";
|
|
4
|
+
import { type ReactNode, useState } from "react";
|
|
5
|
+
|
|
6
|
+
import { BundleIdDisplay } from "@/components/BundleIdDisplay";
|
|
7
|
+
import { PlatformIcon } from "@/components/PlatformIcon";
|
|
8
|
+
import { Badge } from "@/components/ui/badge";
|
|
9
|
+
import {
|
|
10
|
+
Combobox,
|
|
11
|
+
ComboboxCollection,
|
|
12
|
+
ComboboxContent,
|
|
13
|
+
ComboboxEmpty,
|
|
14
|
+
ComboboxGroup,
|
|
15
|
+
ComboboxInput,
|
|
16
|
+
ComboboxItem,
|
|
17
|
+
ComboboxLabel,
|
|
18
|
+
ComboboxList,
|
|
19
|
+
} from "@/components/ui/combobox";
|
|
20
|
+
import type {
|
|
21
|
+
AdoptionRelease,
|
|
22
|
+
AdoptionReleasesInput,
|
|
23
|
+
} from "@/lib/release-adoption";
|
|
24
|
+
import { getAdoptionReleaseRpc } from "@/lib/release-adoption-rpc";
|
|
25
|
+
|
|
26
|
+
import { formatDeployedAt } from "./ComparedBundlesChart";
|
|
27
|
+
import { platformLabel } from "./InsightsScope";
|
|
28
|
+
|
|
29
|
+
type ReleaseGroup = {
|
|
30
|
+
readonly value: string;
|
|
31
|
+
readonly items: readonly AdoptionRelease[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Whether a bundle deployment matches what was typed: its IDs or target app version. */
|
|
35
|
+
const matches = (release: AdoptionRelease, typed: string) =>
|
|
36
|
+
typed === "" ||
|
|
37
|
+
release.releaseId.includes(typed) ||
|
|
38
|
+
release.bundleId.toLowerCase().includes(typed) ||
|
|
39
|
+
(release.targetAppVersion ?? "").toLowerCase().includes(typed);
|
|
40
|
+
|
|
41
|
+
/** A bundle deployment as a picker names it: ID, platform, deployment, target, and state. */
|
|
42
|
+
export function ReleaseOption({
|
|
43
|
+
release,
|
|
44
|
+
}: {
|
|
45
|
+
readonly release: AdoptionRelease;
|
|
46
|
+
}) {
|
|
47
|
+
return (
|
|
48
|
+
<span className="flex min-w-0 flex-col gap-0.5">
|
|
49
|
+
<span className="flex min-w-0 items-center gap-1.5">
|
|
50
|
+
<PlatformIcon platform={release.platform} className="size-3 shrink-0" />
|
|
51
|
+
<span className="sr-only">{platformLabel(release.platform)}</span>
|
|
52
|
+
<BundleIdDisplay bundleId={release.releaseId} className="min-w-0" />
|
|
53
|
+
</span>
|
|
54
|
+
<span className="text-xs text-muted-foreground">
|
|
55
|
+
Deployed {formatDeployedAt(release.deployedAtMs)}
|
|
56
|
+
{release.targetAppVersion ? ` · ${release.targetAppVersion}` : ""}
|
|
57
|
+
{release.enabled ? null : (
|
|
58
|
+
<>
|
|
59
|
+
{" · "}
|
|
60
|
+
<Badge variant="outline">Disabled</Badge>
|
|
61
|
+
</>
|
|
62
|
+
)}
|
|
63
|
+
{release.enabled && !release.live ? (
|
|
64
|
+
<>
|
|
65
|
+
{" · "}
|
|
66
|
+
<span className="inline-flex items-center gap-1 align-middle">
|
|
67
|
+
<CircleOff aria-hidden="true" className="size-3" />
|
|
68
|
+
Unreachable
|
|
69
|
+
</span>
|
|
70
|
+
</>
|
|
71
|
+
) : null}
|
|
72
|
+
</span>
|
|
73
|
+
</span>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Picks one of a channel's bundle deployments: live ones first, searchable
|
|
79
|
+
* by ID or target app version. A full release ID the list leaves out is
|
|
80
|
+
* looked up in the scope, with the read Release health uses for it.
|
|
81
|
+
*/
|
|
82
|
+
export function ReleaseCombobox({
|
|
83
|
+
scope,
|
|
84
|
+
candidates,
|
|
85
|
+
excluded = [],
|
|
86
|
+
onPick,
|
|
87
|
+
disabled = false,
|
|
88
|
+
label,
|
|
89
|
+
placeholder,
|
|
90
|
+
trigger,
|
|
91
|
+
}: {
|
|
92
|
+
readonly scope: AdoptionReleasesInput;
|
|
93
|
+
readonly candidates: readonly AdoptionRelease[];
|
|
94
|
+
readonly excluded?: readonly string[];
|
|
95
|
+
readonly onPick: (release: AdoptionRelease) => void;
|
|
96
|
+
readonly disabled?: boolean;
|
|
97
|
+
/** The input's accessible name. */
|
|
98
|
+
readonly label: string;
|
|
99
|
+
readonly placeholder: string;
|
|
100
|
+
/** A button that opens the list; the search input then sits in the list. */
|
|
101
|
+
readonly trigger?: ReactNode;
|
|
102
|
+
}) {
|
|
103
|
+
const [query, setQuery] = useState("");
|
|
104
|
+
const typed = query.trim().toLowerCase();
|
|
105
|
+
const offered = candidates.filter(
|
|
106
|
+
(release) =>
|
|
107
|
+
!excluded.includes(release.releaseId) && matches(release, typed),
|
|
108
|
+
);
|
|
109
|
+
const lookupId =
|
|
110
|
+
offered.length === 0 &&
|
|
111
|
+
isUUIDv7(typed) &&
|
|
112
|
+
!candidates.some((release) => release.releaseId === typed)
|
|
113
|
+
? typed
|
|
114
|
+
: undefined;
|
|
115
|
+
const lookup = useQuery({
|
|
116
|
+
queryKey: ["insights", "adoption-release", scope, lookupId, false],
|
|
117
|
+
queryFn: () =>
|
|
118
|
+
getAdoptionReleaseRpc({ data: { ...scope, releaseId: lookupId! } }),
|
|
119
|
+
enabled: lookupId !== undefined,
|
|
120
|
+
staleTime: Number.POSITIVE_INFINITY,
|
|
121
|
+
});
|
|
122
|
+
const found =
|
|
123
|
+
lookupId !== undefined &&
|
|
124
|
+
lookup.data?.release &&
|
|
125
|
+
!excluded.includes(lookup.data.release.releaseId)
|
|
126
|
+
? [lookup.data.release]
|
|
127
|
+
: [];
|
|
128
|
+
const groups: ReleaseGroup[] = [
|
|
129
|
+
{ value: "Live", items: offered.filter((release) => release.live) },
|
|
130
|
+
{ value: "Not live", items: offered.filter((release) => !release.live) },
|
|
131
|
+
{ value: "Found", items: found },
|
|
132
|
+
].filter((group) => group.items.length > 0);
|
|
133
|
+
const input = (
|
|
134
|
+
<ComboboxInput
|
|
135
|
+
aria-label={label}
|
|
136
|
+
className={
|
|
137
|
+
trigger
|
|
138
|
+
? undefined
|
|
139
|
+
: "h-11 w-full sm:h-9 sm:w-96 [&_input]:h-full [&_input]:text-base sm:[&_input]:text-xs"
|
|
140
|
+
}
|
|
141
|
+
disabled={disabled}
|
|
142
|
+
placeholder={placeholder}
|
|
143
|
+
showTrigger={!trigger}
|
|
144
|
+
maxLength={1024}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Combobox
|
|
150
|
+
items={groups}
|
|
151
|
+
filter={null}
|
|
152
|
+
value={null}
|
|
153
|
+
inputValue={query}
|
|
154
|
+
onInputValueChange={setQuery}
|
|
155
|
+
itemToStringLabel={(release: AdoptionRelease) => release.releaseId}
|
|
156
|
+
onValueChange={(release: AdoptionRelease | null) => {
|
|
157
|
+
if (release === null) return;
|
|
158
|
+
setQuery("");
|
|
159
|
+
onPick(release);
|
|
160
|
+
}}
|
|
161
|
+
disabled={disabled}
|
|
162
|
+
>
|
|
163
|
+
{trigger ?? input}
|
|
164
|
+
<ComboboxContent className={trigger ? "w-96" : undefined}>
|
|
165
|
+
{trigger ? input : null}
|
|
166
|
+
<ComboboxEmpty>
|
|
167
|
+
{lookupId === undefined
|
|
168
|
+
? "No matching bundles."
|
|
169
|
+
: lookup.isPending
|
|
170
|
+
? "Looking up the release…"
|
|
171
|
+
: lookup.isError
|
|
172
|
+
? "Couldn't look up the release."
|
|
173
|
+
: `No bundle with this ID in ${scope.channel} · ${platformLabel(scope.platform)}.`}
|
|
174
|
+
</ComboboxEmpty>
|
|
175
|
+
<ComboboxList>
|
|
176
|
+
{(group: ReleaseGroup) => (
|
|
177
|
+
<ComboboxGroup key={group.value} items={group.items}>
|
|
178
|
+
<ComboboxLabel>{group.value}</ComboboxLabel>
|
|
179
|
+
<ComboboxCollection>
|
|
180
|
+
{(release: AdoptionRelease) => (
|
|
181
|
+
<ComboboxItem
|
|
182
|
+
className="min-h-11 pr-8 sm:min-h-8"
|
|
183
|
+
key={release.releaseId}
|
|
184
|
+
value={release}
|
|
185
|
+
>
|
|
186
|
+
<ReleaseOption release={release} />
|
|
187
|
+
</ComboboxItem>
|
|
188
|
+
)}
|
|
189
|
+
</ComboboxCollection>
|
|
190
|
+
</ComboboxGroup>
|
|
191
|
+
)}
|
|
192
|
+
</ComboboxList>
|
|
193
|
+
</ComboboxContent>
|
|
194
|
+
</Combobox>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
@@ -33,11 +33,14 @@ export function RollbackButton({
|
|
|
33
33
|
rate,
|
|
34
34
|
crashes,
|
|
35
35
|
attempts,
|
|
36
|
+
onRolledBack,
|
|
36
37
|
}: {
|
|
37
38
|
readonly release: AdoptionRelease;
|
|
38
39
|
readonly rate: number;
|
|
39
40
|
readonly crashes: number;
|
|
40
41
|
readonly attempts: number;
|
|
42
|
+
/** Runs once the release is disabled, before the bundle list reloads. */
|
|
43
|
+
readonly onRolledBack?: () => void;
|
|
41
44
|
}) {
|
|
42
45
|
const [open, setOpen] = useState(false);
|
|
43
46
|
const [error, setError] = useState("");
|
|
@@ -56,6 +59,7 @@ export function RollbackButton({
|
|
|
56
59
|
try {
|
|
57
60
|
await preflight.mutateAsync(input);
|
|
58
61
|
await update.mutateAsync(input);
|
|
62
|
+
onRolledBack?.();
|
|
59
63
|
await queryClient.invalidateQueries({
|
|
60
64
|
queryKey: ["insights", "adoption-releases"],
|
|
61
65
|
});
|
|
@@ -324,10 +324,13 @@ function FailuresReport({
|
|
|
324
324
|
*/
|
|
325
325
|
export function UpdateFailures({
|
|
326
326
|
errors,
|
|
327
|
+
scope,
|
|
327
328
|
query,
|
|
328
329
|
onRefresh,
|
|
329
330
|
}: {
|
|
330
331
|
readonly errors?: ReactNode;
|
|
332
|
+
/** What the report reads, under its title. */
|
|
333
|
+
readonly scope?: ReactNode;
|
|
331
334
|
readonly query: {
|
|
332
335
|
readonly data: UpdateFailuresReport | undefined;
|
|
333
336
|
readonly error: Error | null;
|
|
@@ -341,7 +344,10 @@ export function UpdateFailures({
|
|
|
341
344
|
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
342
345
|
<CardHeader className="flex flex-col gap-1.5 p-4 sm:p-6">
|
|
343
346
|
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
344
|
-
<
|
|
347
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
348
|
+
<CardTitle>Update failures</CardTitle>
|
|
349
|
+
{scope}
|
|
350
|
+
</div>
|
|
345
351
|
<Button
|
|
346
352
|
aria-label="Refresh update failures"
|
|
347
353
|
variant="ghost"
|
|
@@ -14,10 +14,12 @@ export const getUpdateFailuresRpc = createServerFn({ method: "GET" })
|
|
|
14
14
|
import("./server/updateFailures"),
|
|
15
15
|
import("./server/runtime.server"),
|
|
16
16
|
]);
|
|
17
|
-
const { runtime } = await prepareConfig();
|
|
17
|
+
const { runtime, core } = await prepareConfig();
|
|
18
18
|
return getUpdateFailuresReport(
|
|
19
19
|
await requireFeature(runtime, "insights"),
|
|
20
20
|
data,
|
|
21
|
+
Date.now(),
|
|
22
|
+
core,
|
|
21
23
|
);
|
|
22
24
|
});
|
|
23
25
|
|
|
@@ -10,7 +10,8 @@ import type { InsightsEventRow } from "./insights-view";
|
|
|
10
10
|
export type { InsightsFailureBreakdown };
|
|
11
11
|
|
|
12
12
|
export type UpdateFailuresInput = {
|
|
13
|
-
|
|
13
|
+
/** One platform's failures, or both platforms' together. */
|
|
14
|
+
readonly platform: "all" | "ios" | "android";
|
|
14
15
|
readonly channel: string;
|
|
15
16
|
/** A release's failures; the channel's without it. */
|
|
16
17
|
readonly releaseId?: string;
|
|
@@ -34,7 +35,9 @@ export function readUpdateFailuresInput(
|
|
|
34
35
|
): UpdateFailuresInput {
|
|
35
36
|
if (
|
|
36
37
|
!input ||
|
|
37
|
-
(input.platform !== "
|
|
38
|
+
(input.platform !== "all" &&
|
|
39
|
+
input.platform !== "ios" &&
|
|
40
|
+
input.platform !== "android") ||
|
|
38
41
|
typeof input.channel !== "string" ||
|
|
39
42
|
!input.channel.trim() ||
|
|
40
43
|
input.channel.length > 1_024 ||
|
|
@@ -8,6 +8,11 @@ export type RecoveryInput = {
|
|
|
8
8
|
readonly releaseId?: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
/** The Insights page's Release health and Update failures scope: one platform or both. */
|
|
12
|
+
export type ReleaseHealthInput = Omit<RecoveryInput, "platform"> & {
|
|
13
|
+
readonly platform: "all" | "ios" | "android";
|
|
14
|
+
};
|
|
15
|
+
|
|
11
16
|
export const recoveryWindows = {
|
|
12
17
|
"24h": { durationMs: 86_400_000, intervalMs: 3_600_000 },
|
|
13
18
|
"7d": { durationMs: 7 * 86_400_000, intervalMs: 6 * 3_600_000 },
|
|
@@ -17,42 +17,38 @@ const readHealthChart = (value: unknown): HealthChart | undefined =>
|
|
|
17
17
|
const readUsageWindow = (value: unknown): UsageWindow | undefined =>
|
|
18
18
|
value === "12m" ? value : readWindow(value);
|
|
19
19
|
|
|
20
|
+
/** The channel Insights reads when none is chosen: the SDK's and the CLI's default. */
|
|
21
|
+
export const DEFAULT_INSIGHTS_CHANNEL = "production";
|
|
22
|
+
|
|
20
23
|
export type InsightsSearch = {
|
|
24
|
+
/** Every report's platform; both when omitted. */
|
|
21
25
|
platform?: AppUsageScope["platform"];
|
|
26
|
+
/** Every report's channel; production when omitted. */
|
|
22
27
|
channel?: string;
|
|
23
28
|
appVersion?: string;
|
|
24
29
|
window?: UsageWindow;
|
|
25
30
|
bundleWindow?: InsightsWindow;
|
|
26
|
-
healthPlatform?: "ios" | "android";
|
|
27
|
-
healthChannel?: string;
|
|
28
31
|
releaseId?: string;
|
|
29
32
|
healthChart?: HealthChart;
|
|
30
33
|
/**
|
|
31
34
|
* Release health's compared bundles, comma-separated release IDs; omitted,
|
|
32
|
-
* the focused release and the one before it, or the
|
|
35
|
+
* the focused release and the one before it, or the live ones.
|
|
33
36
|
*/
|
|
34
37
|
bundles?: string;
|
|
35
38
|
};
|
|
36
39
|
|
|
40
|
+
const readPlatform = (value: unknown) =>
|
|
41
|
+
value === "all" || value === "ios" || value === "android" ? value : undefined;
|
|
42
|
+
|
|
37
43
|
export function validateInsightsSearch(
|
|
38
44
|
search: Record<string, unknown>,
|
|
39
45
|
): InsightsSearch {
|
|
40
46
|
return {
|
|
41
|
-
platform:
|
|
42
|
-
search.platform === "all" ||
|
|
43
|
-
search.platform === "ios" ||
|
|
44
|
-
search.platform === "android"
|
|
45
|
-
? search.platform
|
|
46
|
-
: undefined,
|
|
47
|
+
platform: readPlatform(search.platform),
|
|
47
48
|
channel: readText(search.channel),
|
|
48
49
|
appVersion: readText(search.appVersion),
|
|
49
50
|
window: readUsageWindow(search.window),
|
|
50
51
|
bundleWindow: readWindow(search.bundleWindow),
|
|
51
|
-
healthPlatform:
|
|
52
|
-
search.healthPlatform === "ios" || search.healthPlatform === "android"
|
|
53
|
-
? search.healthPlatform
|
|
54
|
-
: undefined,
|
|
55
|
-
healthChannel: readText(search.healthChannel),
|
|
56
52
|
releaseId: readText(search.releaseId),
|
|
57
53
|
healthChart: readHealthChart(search.healthChart),
|
|
58
54
|
bundles: readText(search.bundles),
|