@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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/console",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.4",
|
|
5
5
|
"files": [
|
|
6
6
|
".output",
|
|
7
7
|
"lib",
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"tailwindcss": "^4.2.1",
|
|
59
59
|
"tw-animate-css": "^1.4.0",
|
|
60
60
|
"verkit": "0.4.0",
|
|
61
|
-
"@hot-updater/bsdiff": "1.1.
|
|
62
|
-
"@hot-updater/
|
|
63
|
-
"@hot-updater/
|
|
64
|
-
"@hot-updater/
|
|
61
|
+
"@hot-updater/bsdiff": "1.1.4",
|
|
62
|
+
"@hot-updater/plugin-core": "1.1.4",
|
|
63
|
+
"@hot-updater/protocol": "1.1.4",
|
|
64
|
+
"@hot-updater/server": "1.1.4"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@tanstack/devtools-vite": "^0.6.0",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
"vite-tsconfig-paths": "^6.1.1",
|
|
87
87
|
"vitest": "4.1.4",
|
|
88
88
|
"web-vitals": "^5.1.0",
|
|
89
|
-
"@hot-updater/cli-tools": "1.1.
|
|
90
|
-
"@hot-updater/mock": "1.1.
|
|
89
|
+
"@hot-updater/cli-tools": "1.1.4",
|
|
90
|
+
"@hot-updater/mock": "1.1.4"
|
|
91
91
|
},
|
|
92
92
|
"description": "React Native OTA solution for self-hosted",
|
|
93
93
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -42,12 +42,16 @@ import {
|
|
|
42
42
|
type InsightsRetentionDays,
|
|
43
43
|
keepsTwelveMonths,
|
|
44
44
|
} from "@/lib/insights-retention";
|
|
45
|
-
import
|
|
45
|
+
import {
|
|
46
|
+
DEFAULT_INSIGHTS_CHANNEL,
|
|
47
|
+
type InsightsSearch,
|
|
48
|
+
} from "@/lib/insights-search";
|
|
46
49
|
import type { AppUsageReport, UsageWindow } from "@/lib/insights-usage";
|
|
47
50
|
|
|
48
51
|
import { EstimatedCount } from "./EstimatedCount";
|
|
49
52
|
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
50
53
|
import { UsagePeriodSelector } from "./InsightsPeriodSelector";
|
|
54
|
+
import { InsightsScope } from "./InsightsScope";
|
|
51
55
|
import { ReportingDevicesSummary } from "./ReportingDevicesSummary";
|
|
52
56
|
|
|
53
57
|
const dates = new Intl.DateTimeFormat("en", {
|
|
@@ -98,7 +102,16 @@ export function AppUsage({
|
|
|
98
102
|
<div className="grid min-w-0 gap-6 xl:grid-cols-[minmax(0,2fr)_minmax(16rem,1fr)]">
|
|
99
103
|
<Card aria-label="App usage" role="region" className="min-w-0 shadow-sm">
|
|
100
104
|
<CardHeader className="flex-row flex-wrap items-center justify-between gap-4">
|
|
101
|
-
<
|
|
105
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
106
|
+
<CardTitle>App usage</CardTitle>
|
|
107
|
+
<InsightsScope
|
|
108
|
+
channel={search.channel ?? DEFAULT_INSIGHTS_CHANNEL}
|
|
109
|
+
platform={search.platform ?? "all"}
|
|
110
|
+
narrower={
|
|
111
|
+
search.appVersion ? [`App version ${search.appVersion}`] : []
|
|
112
|
+
}
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
102
115
|
<UsagePeriodSelector
|
|
103
116
|
window={window}
|
|
104
117
|
onWindowChange={onWindowChange}
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RotateCcw, X } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
2
3
|
|
|
3
4
|
import { PlatformIcon } from "@/components/PlatformIcon";
|
|
4
5
|
import { Button } from "@/components/ui/button";
|
|
5
|
-
import {
|
|
6
|
-
Card,
|
|
7
|
-
CardContent,
|
|
8
|
-
CardDescription,
|
|
9
|
-
CardHeader,
|
|
10
|
-
CardTitle,
|
|
11
|
-
} from "@/components/ui/card";
|
|
12
6
|
import {
|
|
13
7
|
Combobox,
|
|
14
8
|
ComboboxContent,
|
|
@@ -16,9 +10,8 @@ import {
|
|
|
16
10
|
ComboboxInput,
|
|
17
11
|
ComboboxItem,
|
|
18
12
|
ComboboxList,
|
|
13
|
+
ComboboxTrigger,
|
|
19
14
|
} from "@/components/ui/combobox";
|
|
20
|
-
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
|
|
21
|
-
import { Input } from "@/components/ui/input";
|
|
22
15
|
import {
|
|
23
16
|
Select,
|
|
24
17
|
SelectContent,
|
|
@@ -28,257 +21,282 @@ import {
|
|
|
28
21
|
SelectValue,
|
|
29
22
|
} from "@/components/ui/select";
|
|
30
23
|
import { useChannelsQuery } from "@/lib/api";
|
|
31
|
-
import
|
|
32
|
-
import type {
|
|
24
|
+
import { DEFAULT_INSIGHTS_CHANNEL } from "@/lib/insights-search";
|
|
25
|
+
import type { AdoptionRelease, HealthPlatform } from "@/lib/release-adoption";
|
|
26
|
+
|
|
27
|
+
import { platformLabel } from "./InsightsScope";
|
|
28
|
+
import { ReleaseCombobox } from "./ReleaseCombobox";
|
|
29
|
+
|
|
30
|
+
/** The Insights filters as the URL holds them. */
|
|
31
|
+
export type InsightsFilters = {
|
|
32
|
+
readonly channel: string;
|
|
33
|
+
readonly platform: HealthPlatform;
|
|
34
|
+
readonly appVersion?: string;
|
|
35
|
+
readonly releaseId?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const DEFAULT_FILTERS = {
|
|
39
|
+
channel: DEFAULT_INSIGHTS_CHANNEL,
|
|
40
|
+
platform: "all",
|
|
41
|
+
appVersion: undefined,
|
|
42
|
+
releaseId: undefined,
|
|
43
|
+
} as const satisfies InsightsFilters;
|
|
44
|
+
|
|
45
|
+
// One look for every filter; a filter narrower than its default is filled.
|
|
46
|
+
const CHIP =
|
|
47
|
+
"h-11 shrink-0 gap-1.5 px-3 text-sm data-[active=true]:border-primary/60 data-[active=true]:bg-primary/10 sm:h-8 sm:text-xs";
|
|
48
|
+
const SELECT_CHIP =
|
|
49
|
+
"shrink-0 gap-1.5 px-3 text-sm data-[active=true]:border-primary/60 data-[active=true]:bg-primary/10 data-[size=default]:h-11 sm:text-xs sm:data-[size=default]:h-8";
|
|
50
|
+
|
|
51
|
+
/** A filter with a button that returns it to its default when it is narrowed. */
|
|
52
|
+
function Clearable({
|
|
53
|
+
name,
|
|
54
|
+
active,
|
|
55
|
+
onClear,
|
|
56
|
+
children,
|
|
57
|
+
}: {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly active: boolean;
|
|
60
|
+
readonly onClear: () => void;
|
|
61
|
+
readonly children: ReactNode;
|
|
62
|
+
}) {
|
|
63
|
+
return (
|
|
64
|
+
<div className="flex shrink-0 items-center">
|
|
65
|
+
{children}
|
|
66
|
+
{active ? (
|
|
67
|
+
<Button
|
|
68
|
+
aria-label={`Clear ${name}`}
|
|
69
|
+
className="size-11 sm:size-8"
|
|
70
|
+
size="icon"
|
|
71
|
+
variant="ghost"
|
|
72
|
+
onClick={onClear}
|
|
73
|
+
>
|
|
74
|
+
<X aria-hidden="true" />
|
|
75
|
+
</Button>
|
|
76
|
+
) : null}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
33
80
|
|
|
81
|
+
/**
|
|
82
|
+
* The Insights filters, above every report. Each applies as it changes, so
|
|
83
|
+
* what they show is what the reports read; one narrower than its default is
|
|
84
|
+
* filled and can be cleared on its own, and Reset filters clears them all.
|
|
85
|
+
*/
|
|
34
86
|
export function InsightsControls({
|
|
35
|
-
|
|
36
|
-
scope,
|
|
37
|
-
releaseScope,
|
|
87
|
+
filters,
|
|
38
88
|
appVersions,
|
|
89
|
+
releases,
|
|
90
|
+
onFiltersChange,
|
|
39
91
|
}: {
|
|
40
|
-
readonly
|
|
41
|
-
readonly scope: AppUsageScope;
|
|
42
|
-
readonly releaseScope: Pick<
|
|
43
|
-
RecoveryInput,
|
|
44
|
-
"platform" | "channel" | "releaseId"
|
|
45
|
-
>;
|
|
46
|
-
}) => void;
|
|
47
|
-
readonly scope: AppUsageScope;
|
|
48
|
-
readonly releaseScope: Pick<
|
|
49
|
-
RecoveryInput,
|
|
50
|
-
"platform" | "channel" | "releaseId"
|
|
51
|
-
>;
|
|
92
|
+
readonly filters: InsightsFilters;
|
|
52
93
|
readonly appVersions: readonly string[];
|
|
94
|
+
/** The channel's bundle deployments a release filter offers. */
|
|
95
|
+
readonly releases: readonly AdoptionRelease[];
|
|
96
|
+
readonly onFiltersChange: (change: Partial<InsightsFilters>) => void;
|
|
53
97
|
}) {
|
|
54
|
-
const [platform, setPlatform] = useState(scope.platform);
|
|
55
|
-
const [channel, setChannel] = useState(scope.channel);
|
|
56
|
-
const [appVersion, setAppVersion] = useState(scope.appVersion ?? "");
|
|
57
|
-
const [releasePlatform, setReleasePlatform] = useState(releaseScope.platform);
|
|
58
|
-
const [releaseId, setReleaseId] = useState(releaseScope.releaseId ?? "");
|
|
59
98
|
const versions = [
|
|
60
|
-
...new Set([
|
|
99
|
+
...new Set([
|
|
100
|
+
...appVersions,
|
|
101
|
+
...(filters.appVersion ? [filters.appVersion] : []),
|
|
102
|
+
]),
|
|
61
103
|
];
|
|
62
104
|
const channelsQuery = useChannelsQuery();
|
|
63
105
|
const channels = channelsQuery.data?.map((item) => item.name) ?? [];
|
|
106
|
+
const narrowed = {
|
|
107
|
+
channel: filters.channel !== DEFAULT_FILTERS.channel,
|
|
108
|
+
platform: filters.platform !== DEFAULT_FILTERS.platform,
|
|
109
|
+
appVersion: filters.appVersion !== undefined,
|
|
110
|
+
releaseId: filters.releaseId !== undefined,
|
|
111
|
+
};
|
|
64
112
|
|
|
65
113
|
return (
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
...(appVersion ? { appVersion } : {}),
|
|
84
|
-
},
|
|
85
|
-
releaseScope: {
|
|
86
|
-
platform: releasePlatform,
|
|
87
|
-
channel,
|
|
88
|
-
...(releaseId.trim() ? { releaseId: releaseId.trim() } : {}),
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
}}
|
|
114
|
+
<div
|
|
115
|
+
aria-label="Insights filters"
|
|
116
|
+
className="flex shrink-0 items-center gap-2 overflow-x-auto border-b bg-background px-3 py-2 sm:flex-wrap sm:overflow-visible sm:px-4"
|
|
117
|
+
role="group"
|
|
118
|
+
>
|
|
119
|
+
<Combobox
|
|
120
|
+
items={channels}
|
|
121
|
+
value={filters.channel}
|
|
122
|
+
onValueChange={(value) => {
|
|
123
|
+
if (value !== null && value !== filters.channel)
|
|
124
|
+
onFiltersChange({ channel: value });
|
|
125
|
+
}}
|
|
126
|
+
>
|
|
127
|
+
<ComboboxTrigger
|
|
128
|
+
aria-label={`Channel: ${filters.channel}`}
|
|
129
|
+
data-active={narrowed.channel}
|
|
130
|
+
render={<Button className={CHIP} variant="outline" />}
|
|
92
131
|
>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
) : channelsQuery.isError ? (
|
|
114
|
-
<div className="flex flex-col items-center gap-2 p-2">
|
|
115
|
-
Couldn't load channels.
|
|
116
|
-
<Button
|
|
117
|
-
onClick={() => void channelsQuery.refetch()}
|
|
118
|
-
size="sm"
|
|
119
|
-
variant="outline"
|
|
120
|
-
>
|
|
121
|
-
Retry channels
|
|
122
|
-
</Button>
|
|
123
|
-
</div>
|
|
124
|
-
) : (
|
|
125
|
-
"No channels found."
|
|
126
|
-
)}
|
|
127
|
-
</ComboboxEmpty>
|
|
128
|
-
<ComboboxList>
|
|
129
|
-
{(item: string) => (
|
|
130
|
-
<ComboboxItem
|
|
131
|
-
className="min-h-11 pr-8 sm:min-h-8"
|
|
132
|
-
key={item}
|
|
133
|
-
value={item}
|
|
134
|
-
>
|
|
135
|
-
<span className="truncate">{item}</span>
|
|
136
|
-
</ComboboxItem>
|
|
137
|
-
)}
|
|
138
|
-
</ComboboxList>
|
|
139
|
-
</ComboboxContent>
|
|
140
|
-
</Combobox>
|
|
141
|
-
</Field>
|
|
142
|
-
<Field>
|
|
143
|
-
<FieldLabel htmlFor="insights-platform">
|
|
144
|
-
Usage platform
|
|
145
|
-
</FieldLabel>
|
|
146
|
-
<Select
|
|
147
|
-
items={{ all: "All platforms", ios: "iOS", android: "Android" }}
|
|
148
|
-
value={platform}
|
|
149
|
-
onValueChange={(value) => {
|
|
150
|
-
if (value === "all" || value === "ios" || value === "android")
|
|
151
|
-
setPlatform(value);
|
|
152
|
-
}}
|
|
153
|
-
>
|
|
154
|
-
<SelectTrigger
|
|
155
|
-
id="insights-platform"
|
|
156
|
-
className="min-h-11 w-full sm:min-h-9"
|
|
132
|
+
<span className="text-muted-foreground">Channel</span>
|
|
133
|
+
<span className="max-w-48 truncate">{filters.channel}</span>
|
|
134
|
+
</ComboboxTrigger>
|
|
135
|
+
<ComboboxContent className="w-64">
|
|
136
|
+
<ComboboxInput
|
|
137
|
+
aria-label="Search channels"
|
|
138
|
+
placeholder="Search channels…"
|
|
139
|
+
showTrigger={false}
|
|
140
|
+
maxLength={1024}
|
|
141
|
+
/>
|
|
142
|
+
<ComboboxEmpty>
|
|
143
|
+
{channelsQuery.isPending ? (
|
|
144
|
+
"Loading channels…"
|
|
145
|
+
) : channelsQuery.isError ? (
|
|
146
|
+
<div className="flex flex-col items-center gap-2 p-2">
|
|
147
|
+
Couldn't load channels.
|
|
148
|
+
<Button
|
|
149
|
+
onClick={() => void channelsQuery.refetch()}
|
|
150
|
+
size="sm"
|
|
151
|
+
variant="outline"
|
|
157
152
|
>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
<SelectItem value="all">All platforms</SelectItem>
|
|
172
|
-
<SelectItem value="ios">
|
|
173
|
-
<PlatformIcon platform="ios" className="size-3.5" />
|
|
174
|
-
iOS
|
|
175
|
-
</SelectItem>
|
|
176
|
-
<SelectItem value="android">
|
|
177
|
-
<PlatformIcon platform="android" className="size-3.5" />
|
|
178
|
-
Android
|
|
179
|
-
</SelectItem>
|
|
180
|
-
</SelectGroup>
|
|
181
|
-
</SelectContent>
|
|
182
|
-
</Select>
|
|
183
|
-
</Field>
|
|
184
|
-
<Field className="min-w-0">
|
|
185
|
-
<FieldLabel htmlFor="insights-app-version">
|
|
186
|
-
App version
|
|
187
|
-
</FieldLabel>
|
|
188
|
-
<Select
|
|
189
|
-
items={Object.fromEntries([
|
|
190
|
-
["all", "All versions"],
|
|
191
|
-
...versions.map((version) => [`version:${version}`, version]),
|
|
192
|
-
])}
|
|
193
|
-
value={appVersion ? `version:${appVersion}` : "all"}
|
|
194
|
-
onValueChange={(value) => {
|
|
195
|
-
if (value !== null)
|
|
196
|
-
setAppVersion(value === "all" ? "" : value.slice(8));
|
|
197
|
-
}}
|
|
153
|
+
Retry channels
|
|
154
|
+
</Button>
|
|
155
|
+
</div>
|
|
156
|
+
) : (
|
|
157
|
+
"No channels found."
|
|
158
|
+
)}
|
|
159
|
+
</ComboboxEmpty>
|
|
160
|
+
<ComboboxList>
|
|
161
|
+
{(item: string) => (
|
|
162
|
+
<ComboboxItem
|
|
163
|
+
className="min-h-11 pr-8 sm:min-h-8"
|
|
164
|
+
key={item}
|
|
165
|
+
value={item}
|
|
198
166
|
>
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<
|
|
230
|
-
|
|
231
|
-
className="
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
167
|
+
<span className="truncate">{item}</span>
|
|
168
|
+
</ComboboxItem>
|
|
169
|
+
)}
|
|
170
|
+
</ComboboxList>
|
|
171
|
+
</ComboboxContent>
|
|
172
|
+
</Combobox>
|
|
173
|
+
<Clearable
|
|
174
|
+
name="platform"
|
|
175
|
+
active={narrowed.platform}
|
|
176
|
+
onClear={() => onFiltersChange({ platform: DEFAULT_FILTERS.platform })}
|
|
177
|
+
>
|
|
178
|
+
<Select
|
|
179
|
+
items={{ all: "All platforms", ios: "iOS", android: "Android" }}
|
|
180
|
+
value={filters.platform}
|
|
181
|
+
onValueChange={(value) => {
|
|
182
|
+
if (
|
|
183
|
+
(value === "all" || value === "ios" || value === "android") &&
|
|
184
|
+
value !== filters.platform
|
|
185
|
+
)
|
|
186
|
+
onFiltersChange({ platform: value });
|
|
187
|
+
}}
|
|
188
|
+
>
|
|
189
|
+
<SelectTrigger
|
|
190
|
+
aria-label="Platform"
|
|
191
|
+
className={SELECT_CHIP}
|
|
192
|
+
data-active={narrowed.platform}
|
|
193
|
+
>
|
|
194
|
+
<span className="text-muted-foreground">Platform</span>
|
|
195
|
+
<SelectValue>
|
|
196
|
+
{filters.platform === "all" ? null : (
|
|
197
|
+
<PlatformIcon
|
|
198
|
+
platform={filters.platform}
|
|
199
|
+
className="size-3.5"
|
|
200
|
+
/>
|
|
201
|
+
)}
|
|
202
|
+
{platformLabel(filters.platform)}
|
|
203
|
+
</SelectValue>
|
|
204
|
+
</SelectTrigger>
|
|
205
|
+
<SelectContent>
|
|
206
|
+
<SelectGroup>
|
|
207
|
+
<SelectItem value="all">All platforms</SelectItem>
|
|
208
|
+
<SelectItem value="ios">
|
|
209
|
+
<PlatformIcon platform="ios" className="size-3.5" />
|
|
210
|
+
iOS
|
|
211
|
+
</SelectItem>
|
|
212
|
+
<SelectItem value="android">
|
|
213
|
+
<PlatformIcon platform="android" className="size-3.5" />
|
|
214
|
+
Android
|
|
215
|
+
</SelectItem>
|
|
216
|
+
</SelectGroup>
|
|
217
|
+
</SelectContent>
|
|
218
|
+
</Select>
|
|
219
|
+
</Clearable>
|
|
220
|
+
<Clearable
|
|
221
|
+
name="app version"
|
|
222
|
+
active={narrowed.appVersion}
|
|
223
|
+
onClear={() => onFiltersChange({ appVersion: undefined })}
|
|
224
|
+
>
|
|
225
|
+
<Select
|
|
226
|
+
items={Object.fromEntries([
|
|
227
|
+
["all", "All versions"],
|
|
228
|
+
...versions.map((version) => [`version:${version}`, version]),
|
|
229
|
+
])}
|
|
230
|
+
value={filters.appVersion ? `version:${filters.appVersion}` : "all"}
|
|
231
|
+
onValueChange={(value) => {
|
|
232
|
+
if (value === null) return;
|
|
233
|
+
const appVersion = value === "all" ? undefined : value.slice(8);
|
|
234
|
+
if (appVersion !== filters.appVersion)
|
|
235
|
+
onFiltersChange({ appVersion });
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
<SelectTrigger
|
|
239
|
+
aria-label="App version"
|
|
240
|
+
className={SELECT_CHIP}
|
|
241
|
+
data-active={narrowed.appVersion}
|
|
242
|
+
>
|
|
243
|
+
<span className="text-muted-foreground">App version</span>
|
|
244
|
+
<SelectValue />
|
|
245
|
+
</SelectTrigger>
|
|
246
|
+
<SelectContent>
|
|
247
|
+
<SelectGroup>
|
|
248
|
+
<SelectItem value="all">All versions</SelectItem>
|
|
249
|
+
{versions.map((version) => (
|
|
250
|
+
<SelectItem key={version} value={`version:${version}`}>
|
|
251
|
+
{version}
|
|
252
|
+
</SelectItem>
|
|
253
|
+
))}
|
|
254
|
+
</SelectGroup>
|
|
255
|
+
</SelectContent>
|
|
256
|
+
</Select>
|
|
257
|
+
</Clearable>
|
|
258
|
+
<Clearable
|
|
259
|
+
name="release"
|
|
260
|
+
active={narrowed.releaseId}
|
|
261
|
+
onClear={() => onFiltersChange({ releaseId: undefined })}
|
|
262
|
+
>
|
|
263
|
+
<ReleaseCombobox
|
|
264
|
+
scope={{ channel: filters.channel, platform: filters.platform }}
|
|
265
|
+
candidates={releases}
|
|
266
|
+
excluded={filters.releaseId ? [filters.releaseId] : []}
|
|
267
|
+
label="Search releases"
|
|
268
|
+
placeholder="Search by ID or app version…"
|
|
269
|
+
onPick={(release) =>
|
|
270
|
+
onFiltersChange({ releaseId: release.releaseId })
|
|
271
|
+
}
|
|
272
|
+
trigger={
|
|
273
|
+
<ComboboxTrigger
|
|
274
|
+
aria-label={`Release: ${filters.releaseId ?? "All"}`}
|
|
275
|
+
data-active={narrowed.releaseId}
|
|
276
|
+
render={<Button className={CHIP} variant="outline" />}
|
|
276
277
|
>
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
278
|
+
<span className="text-muted-foreground">Release</span>
|
|
279
|
+
{filters.releaseId ? (
|
|
280
|
+
<span className="max-w-36 truncate font-mono" translate="no">
|
|
281
|
+
{filters.releaseId}
|
|
282
|
+
</span>
|
|
283
|
+
) : (
|
|
284
|
+
"All"
|
|
285
|
+
)}
|
|
286
|
+
</ComboboxTrigger>
|
|
287
|
+
}
|
|
288
|
+
/>
|
|
289
|
+
</Clearable>
|
|
290
|
+
{Object.values(narrowed).some(Boolean) ? (
|
|
291
|
+
<Button
|
|
292
|
+
className="h-11 shrink-0 sm:h-8"
|
|
293
|
+
variant="ghost"
|
|
294
|
+
onClick={() => onFiltersChange(DEFAULT_FILTERS)}
|
|
295
|
+
>
|
|
296
|
+
<RotateCcw aria-hidden="true" data-icon="inline-start" />
|
|
297
|
+
Reset filters
|
|
298
|
+
</Button>
|
|
299
|
+
) : null}
|
|
300
|
+
</div>
|
|
283
301
|
);
|
|
284
302
|
}
|