@hot-updater/console 1.0.0-rc.3 → 1.0.0-rc.30
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/ConsoleFeatureUnavailable-C2A-IEUj.js +1 -0
- package/.output/public/assets/EventDetails-Cmr-5qyj.js +1 -0
- package/.output/public/assets/InsightsInfo-BSBCWe3-.js +1 -0
- package/.output/public/assets/InsightsPageHeader-CD2fNN6p.js +1 -0
- package/.output/public/assets/NotFoundPage-jCpnR_sf.js +1 -0
- package/.output/public/assets/api-M5QUCPtH.js +1 -0
- package/.output/public/assets/api-keys-C07wGYuv.js +1 -0
- package/.output/public/assets/api-keys-EE1NZMDt.js +1 -0
- package/.output/public/assets/arrow-left-DD46PnTE.js +1 -0
- package/.output/public/assets/card-CYvj5tZx.js +1 -0
- package/.output/public/assets/console-features-api-Bk59EJ3s.js +1 -0
- package/.output/public/assets/dialog-JCXymiUi.js +1 -0
- package/.output/public/assets/dist-D6GJh2KA.js +1 -0
- package/.output/public/assets/empty-XdPINXx1.js +1 -0
- package/.output/public/assets/field-DzzNtTQg.js +1 -0
- package/.output/public/assets/index-GU56hAuf.js +11 -0
- package/.output/public/assets/input-group-BYLaHtwW.js +1 -0
- package/.output/public/assets/insights-BszMgsRK.js +42 -0
- package/.output/public/assets/insights-DQsl_Td9.js +1 -0
- package/.output/public/assets/insights-DpOzhVW7.js +2 -0
- package/.output/public/assets/insights-recovery-rpc-CTHkGU7B.js +1 -0
- package/.output/public/assets/insights-usage-rpc-D2B6f_Xe.js +1 -0
- package/.output/public/assets/insights_.distribution-DTT6zAk9.js +2 -0
- package/.output/public/assets/insights_.distribution-Dmk5sx7P.js +1 -0
- package/.output/public/assets/insights_.distribution-x-J185Zc.js +1 -0
- package/.output/public/assets/installations-2Z3CVLeT.js +2 -0
- package/.output/public/assets/installations-BF2P7U-4.js +1 -0
- package/.output/public/assets/installations-BcENidrS.js +1 -0
- package/.output/public/assets/loader-circle-OFX0xgRO.js +1 -0
- package/.output/public/assets/mutation-T1TB7pc4.js +1 -0
- package/.output/public/assets/preload-helper-I1Qxz43W.js +1 -0
- package/.output/public/assets/refresh-cw-Dq9bXycn.js +1 -0
- package/.output/public/assets/routes-BiY8Stye.js +2 -0
- package/.output/public/assets/routes-DwBZ3XAM.js +1 -0
- package/.output/public/assets/sidebar-CZeTS8e7.js +3 -0
- package/.output/public/assets/styles-BHmFL3Ss.css +2 -0
- package/.output/public/assets/table-BkWY5miS.js +1 -0
- package/.output/public/assets/useCompositeListItem-CnQYUiF9.js +1 -0
- package/.output/public/assets/useMutation-CowW32bj.js +1 -0
- package/.output/public/assets/valueToPercent-Bg4L42av.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-EMel9KYs.mjs → __23tanstack-start-server-fn-resolver-BhCfmV4w.mjs} +52 -48
- package/.output/server/_libs/@base-ui/react+[...].mjs +6891 -3364
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
- package/.output/server/_libs/lucide-react.mjs +64 -31
- package/.output/server/_libs/next-themes.mjs +1 -1
- package/.output/server/_libs/react-redux+[...].mjs +1 -1
- package/.output/server/_libs/recharts+[...].mjs +494 -780
- package/.output/server/_libs/sonner.mjs +1 -1
- package/.output/server/_libs/tanstack__history.mjs +68 -41
- package/.output/server/_libs/tanstack__query-core.mjs +1772 -310
- package/.output/server/_libs/tanstack__react-query.mjs +266 -21
- package/.output/server/_libs/verkit.mjs +99 -1
- package/.output/server/_ssr/ConsoleFeatureUnavailable-CxmgKN3V.mjs +58 -0
- package/.output/server/_ssr/EventDetails-BZ63i9RG.mjs +289 -0
- package/.output/server/_ssr/InsightsInfo-Dzb4MOKu.mjs +72 -0
- package/.output/server/_ssr/InsightsPageHeader-vA0_nYCk.mjs +161 -0
- package/.output/server/_ssr/NotFoundPage-B5W9MyXy.mjs +37 -0
- package/.output/server/_ssr/api-DeEqnrrM.mjs +203 -0
- package/.output/server/_ssr/api-keys-BgGUDfDY.mjs +5 -0
- package/.output/server/_ssr/api-keys-rpc-BQxVQR3u.mjs +52 -0
- package/.output/server/_ssr/{api-keys-BBl84_lN.mjs → api-keys-s5hSUx3p.mjs} +48 -8
- package/.output/server/_ssr/api-rpc-Cu2b-52B.mjs +296 -0
- package/.output/server/_ssr/{auth-rpc-gaibR719.mjs → auth-rpc-C5nwIq5q.mjs} +6 -6
- package/.output/server/_ssr/auth.server-BEWisUh1.mjs +5712 -0
- package/.output/server/_ssr/bundleActivity-DebSrpG0.mjs +19 -0
- package/.output/server/_ssr/card-BrzHgIMV.mjs +49 -0
- package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
- package/.output/server/_ssr/config.server-CTZ2j8hg.mjs +36 -0
- package/.output/server/_ssr/console-access-CzZK7n93.mjs +17 -0
- package/.output/server/_ssr/console-features-BsjUyRwC.mjs +55 -0
- package/.output/server/_ssr/console-features-api-D87zBT2j.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-CT9CgHjn.mjs +19 -0
- package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-BdBf_ezN.mjs} +67 -56
- package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
- package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Bhxciy7Z.mjs} +28 -5
- package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
- package/.output/server/_ssr/{dialog-DNtKYR5K.mjs → dialog-Com7blkc.mjs} +2 -2
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
- package/.output/server/_ssr/{downloadBundle-A9zYC2go.mjs → downloadBundle-J1h1ilVu.mjs} +10 -10
- package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
- package/.output/server/_ssr/empty-xB4Oxqe5.mjs +49 -0
- package/.output/server/_ssr/failureReports-C72EOpNj.mjs +33 -0
- package/.output/server/_ssr/{table-QQ9cnWf2.mjs → field-DHadIunv.mjs} +62 -50
- package/.output/server/_ssr/getBundleChildren-h_jo7zPh.mjs +22 -0
- package/.output/server/_ssr/{input-group-Oj4kUnE4.mjs → input-group-BoLVd6aM.mjs} +52 -3
- package/.output/server/_ssr/insights-C9yYMr6S.mjs +5 -0
- package/.output/server/_ssr/insights-D5Vr1-LU.mjs +14 -0
- package/.output/server/_ssr/insights-api-CwoqDPIC.mjs +122 -0
- package/.output/server/_ssr/insights-errors-F-7hOApm.mjs +34 -0
- package/.output/server/_ssr/insights-failures-BZaGNNij.mjs +47 -0
- package/.output/server/_ssr/insights-failures-rpc-Cn0h5GQN.mjs +36 -0
- package/.output/server/_ssr/insights-k-z2boTq.mjs +2140 -0
- package/.output/server/_ssr/{insights-recovery-DSFM-YTL.mjs → insights-recovery-Dpk6Mfb9.mjs} +9 -2
- package/.output/server/_ssr/insights-recovery-rpc-8zC6yDQG.mjs +20 -0
- package/.output/server/_ssr/insights-recovery-rpc-f1GkDalD.mjs +46 -0
- package/.output/server/_ssr/{insights-rpc-DBf0ouen.mjs → insights-rpc-BMZYat_c.mjs} +22 -14
- package/.output/server/_ssr/insights-usage-CLUWjUFz.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-C_rbTlzm.mjs +41 -0
- package/.output/server/_ssr/insights-usage-rpc-xA0701F_.mjs +29 -0
- package/.output/server/_ssr/insightsRecovery-9_N8XFIa.mjs +106 -0
- package/.output/server/_ssr/insightsUsage-BcqsRERl.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-BD8N3-RC.mjs +334 -0
- package/.output/server/_ssr/insights_.distribution-BW9aXDcX.mjs +5 -0
- package/.output/server/_ssr/insights_.distribution-DJJYtovL.mjs +14 -0
- package/.output/server/_ssr/installations-B5Mvy3PT.mjs +5 -0
- package/.output/server/_ssr/installations-COslBIxT.mjs +69 -0
- package/.output/server/_ssr/{installations-DQyJN-pZ.mjs → installations-CPTYrBnY.mjs} +428 -97
- package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
- package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
- package/.output/server/_ssr/{router-C8J4nYEG.mjs → router-ChZfGLQb.mjs} +134 -138
- package/.output/server/_ssr/{routes-NlTRKPML.mjs → routes-BzIV6_XR.mjs} +174 -389
- package/.output/server/_ssr/routes-CwFJq8fZ.mjs +362 -0
- package/.output/server/_ssr/runtime.server-0kKViz4u.mjs +1597 -0
- package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
- package/.output/server/_ssr/select-1btAqVz9.mjs +107 -0
- package/.output/server/_ssr/server-ET4AuHDX.mjs +8330 -0
- package/.output/server/_ssr/{createSsrRpc-4rr4IJh3.mjs → sidebar-1gxSH3tz.mjs} +107 -47
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
- package/.output/server/_ssr/table-CpDxIWGg.mjs +53 -0
- package/.output/server/_ssr/updateFailures-BI2k_oIa.mjs +40 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-DEx0iQr9.mjs +122 -0
- package/.output/server/index.mjs +253 -169
- package/LICENSE +13 -14
- package/README.md +82 -72
- package/lib/index.d.mts +23 -11
- package/lib/vite-internal.mjs +13 -3
- package/lib/vite.d.mts +2 -0
- package/package.json +14 -14
- package/src/components/AppSidebar.tsx +100 -71
- package/src/components/ConsoleAccessPage.tsx +5 -1
- package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
- package/src/components/NotFoundPage.tsx +7 -7
- package/src/components/features/FeatureSlots.tsx +99 -0
- package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
- package/src/components/features/bundles/BundleMetadata.tsx +8 -5
- package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
- package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
- package/src/components/features/insights/AppUsage.tsx +370 -0
- package/src/components/features/insights/BundleDistributionChart.tsx +253 -0
- package/src/components/features/insights/EstimatedCount.tsx +18 -0
- package/src/components/features/insights/EventDetails.tsx +294 -37
- package/src/components/features/insights/EventHistoryCard.tsx +112 -26
- package/src/components/features/insights/EventHistoryList.tsx +2 -2
- package/src/components/features/insights/FailureReports.tsx +394 -0
- package/src/components/features/insights/InsightsControls.tsx +265 -102
- package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
- package/src/components/features/insights/InsightsInfo.tsx +48 -0
- package/src/components/features/insights/InsightsOverview.tsx +257 -173
- package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
- package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
- package/src/components/features/insights/InstallationHistoryCard.tsx +58 -10
- package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
- package/src/components/features/insights/ReleaseActivity.tsx +293 -0
- package/src/components/features/insights/ReleaseAdoptionChart.tsx +325 -0
- package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
- package/src/components/features/insights/UpdateFailures.tsx +451 -0
- package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +30 -20
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/scroll-area.tsx +52 -0
- package/src/components/ui/sidebar.tsx +22 -29
- package/src/components/ui/tabs.tsx +81 -0
- package/src/index.ts +26 -19
- package/src/lib/api-keys-api.ts +0 -28
- package/src/lib/api-keys-rpc.ts +27 -38
- package/src/lib/api-rpc.ts +189 -266
- package/src/lib/api.ts +19 -102
- package/src/lib/bundle-activity.ts +31 -0
- package/src/lib/bundle-distribution.ts +81 -0
- package/src/lib/console-access.ts +20 -0
- package/src/lib/console-features-api.ts +68 -0
- package/src/lib/console-features-rpc.ts +13 -0
- package/src/lib/console-features.ts +91 -0
- package/src/lib/insights-api.ts +56 -2
- package/src/lib/insights-errors.ts +64 -0
- package/src/lib/insights-failures-rpc.ts +36 -0
- package/src/lib/insights-failures.ts +140 -0
- package/src/lib/insights-recovery-rpc.ts +43 -6
- package/src/lib/insights-recovery.ts +53 -17
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +83 -0
- package/src/lib/insights-usage-rpc.ts +29 -0
- package/src/lib/insights-usage.ts +74 -0
- package/src/lib/insights-view.ts +38 -2
- package/src/lib/scroll-restoration.ts +21 -0
- package/src/lib/server/adminInsights.ts +124 -0
- package/src/lib/server/auth.server.ts +13 -5
- package/src/lib/server/bundleActivity.ts +37 -0
- package/src/lib/server/config.server.ts +21 -74
- package/src/lib/server/console-feature-apis.ts +59 -0
- package/src/lib/server/console-runtime.server.ts +82 -38
- package/src/lib/server/downloadBundle.ts +16 -15
- package/src/lib/server/failureReports.ts +49 -0
- package/src/lib/server/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsRecovery.ts +129 -138
- package/src/lib/server/insightsUsage.ts +63 -0
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseReachability.ts +8 -7
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +65 -0
- package/src/routeTree.gen.ts +44 -44
- package/src/router.tsx +3 -2
- package/src/routes/-installations-search.ts +32 -8
- package/src/routes/-releases-search.ts +76 -29
- package/src/routes/__root.tsx +10 -4
- package/src/routes/api/bundles/$bundleId/download.ts +2 -5
- package/src/routes/api-keys.tsx +5 -2
- package/src/routes/index.tsx +167 -104
- package/src/routes/insights.tsx +131 -61
- package/src/routes/insights_.distribution.tsx +398 -0
- package/src/routes/installations.tsx +98 -41
- package/src/vite-internal.ts +15 -3
- package/src/vite.ts +2 -0
- package/.output/public/assets/InsightsErrorAlert-1cg6LDJG.js +0 -1
- package/.output/public/assets/InsightsPageHeader-oNIg_lcd.js +0 -1
- package/.output/public/assets/InsightsRolloutCard-Zsv9gBjU.js +0 -42
- package/.output/public/assets/api-keys-ChJjsbP2.js +0 -1
- package/.output/public/assets/api-keys-api-LchP65vQ.js +0 -1
- package/.output/public/assets/card-iD7iiy4T.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-C0G0fQ3J.js +0 -1
- package/.output/public/assets/createServerFn-DHc2erub.js +0 -3
- package/.output/public/assets/dialog-DX6o8qCL.js +0 -1
- package/.output/public/assets/dist-BPrb_vuw.js +0 -1
- package/.output/public/assets/index-C6Nb14xG.js +0 -10
- package/.output/public/assets/input-group-Pa3SAbEI.js +0 -1
- package/.output/public/assets/insights-C1uKy3p2.js +0 -1
- package/.output/public/assets/installations-Bj5AzZ3T.js +0 -1
- package/.output/public/assets/installations-DDTw9DSK.js +0 -2
- package/.output/public/assets/lazyRouteComponent-BN0MS05o.js +0 -1
- package/.output/public/assets/link-BG06whwB.js +0 -1
- package/.output/public/assets/loader-circle-Bbp8LGVC.js +0 -1
- package/.output/public/assets/preload-helper-B_l5jfgx.js +0 -1
- package/.output/public/assets/refresh-cw-DZgiAOMk.js +0 -1
- package/.output/public/assets/routes-9E2okfpG.js +0 -1
- package/.output/public/assets/routes-B3JviaDV.js +0 -2
- package/.output/public/assets/shield-check-DoIiaaXU.js +0 -1
- package/.output/public/assets/signing-BKy9fIdB.js +0 -1
- package/.output/public/assets/styles-C5-su40i.css +0 -2
- package/.output/public/assets/table-bpgpQkPz.js +0 -1
- package/.output/public/assets/useOpenInteractionType-4dPacbwb.js +0 -1
- package/.output/public/assets/x-Cr_sPM8E.js +0 -1
- package/.output/server/_ssr/InsightsErrorAlert-DzDVdfgW.mjs +0 -169
- package/.output/server/_ssr/InsightsPageHeader-CZ2AaQGk.mjs +0 -372
- package/.output/server/_ssr/InsightsRolloutCard-DHB2iM4t.mjs +0 -693
- package/.output/server/_ssr/api-keys-api-KSH04dLB.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-DBmgnW5o.mjs +0 -136
- package/.output/server/_ssr/api-rpc-DX5B6b-L.mjs +0 -1399
- package/.output/server/_ssr/auth.server-DF_shCeq.mjs +0 -47
- package/.output/server/_ssr/bundle-signing-rpc-C01EKpb-.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-Co_zHdJx.mjs +0 -12
- package/.output/server/_ssr/card-gGh8F6kQ.mjs +0 -108
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-NIXXHk-e.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-C71v1GBr.mjs +0 -66
- package/.output/server/_ssr/getBundleChildren-CMGSGBWv.mjs +0 -50
- package/.output/server/_ssr/insights-oK2n5WBp.mjs +0 -406
- package/.output/server/_ssr/insights-recovery-rpc-CgeCAs2K.mjs +0 -16
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
- package/.output/server/_ssr/insightsRecovery-DFkXGFjd.mjs +0 -108
- package/.output/server/_ssr/installations-DBmYuVBi.mjs +0 -29
- package/.output/server/_ssr/routes-C2RIYhyl.mjs +0 -37
- package/.output/server/_ssr/runtime.server-DLZpzphL.mjs +0 -438
- package/.output/server/_ssr/server-hgrXpJQY.mjs +0 -7468
- package/.output/server/_ssr/signing-bfOvUMSJ.mjs +0 -133
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-BAb_a_eC.mjs +0 -97
- package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
- package/src/components/features/insights/InsightsRolloutCard.tsx +0 -505
- package/src/components/features/signing/BundleSigningPage.tsx +0 -139
- package/src/lib/bundle-signing-api.ts +0 -14
- package/src/lib/bundle-signing-rpc.ts +0 -16
- package/src/lib/server/bundle-signing.server.ts +0 -23
- package/src/lib/server/deleteBundle.ts +0 -145
- package/src/routes/signing.tsx +0 -37
|
@@ -1,203 +1,287 @@
|
|
|
1
1
|
import { Link } from "@tanstack/react-router";
|
|
2
|
-
import {
|
|
2
|
+
import { ListIcon, RotateCw } from "lucide-react";
|
|
3
|
+
import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts";
|
|
3
4
|
|
|
4
5
|
import { Button, buttonVariants } from "@/components/ui/button";
|
|
5
6
|
import {
|
|
6
7
|
Card,
|
|
7
8
|
CardContent,
|
|
8
|
-
CardDescription,
|
|
9
9
|
CardFooter,
|
|
10
10
|
CardHeader,
|
|
11
11
|
CardTitle,
|
|
12
12
|
} from "@/components/ui/card";
|
|
13
|
+
import {
|
|
14
|
+
ChartContainer,
|
|
15
|
+
ChartTooltip,
|
|
16
|
+
ChartTooltipContent,
|
|
17
|
+
} from "@/components/ui/chart";
|
|
18
|
+
import {
|
|
19
|
+
Empty,
|
|
20
|
+
EmptyDescription,
|
|
21
|
+
EmptyHeader,
|
|
22
|
+
EmptyTitle,
|
|
23
|
+
} from "@/components/ui/empty";
|
|
13
24
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
25
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
26
|
+
import type { RecoveryInput, RecoveryReport } from "@/lib/insights-recovery";
|
|
27
|
+
import type { HealthChart } from "@/lib/insights-search";
|
|
28
|
+
import { cn } from "@/lib/utils";
|
|
16
29
|
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
30
|
+
import { BundleDistributionChart } from "./BundleDistributionChart";
|
|
31
|
+
import { EstimatedCount } from "./EstimatedCount";
|
|
19
32
|
import { InsightsErrorAlert } from "./InsightsErrorAlert";
|
|
33
|
+
import { InsightsInfo } from "./InsightsInfo";
|
|
34
|
+
import { InsightsPeriodSelector } from "./InsightsPeriodSelector";
|
|
35
|
+
import { ReleaseAdoptionChart } from "./ReleaseAdoptionChart";
|
|
20
36
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
readonly active: ReportingInstallations;
|
|
27
|
-
readonly onOutcomeSelect: (selection: InsightsReportSelection) => void;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const windowCopy = {
|
|
31
|
-
"24h": "the last 24 hours",
|
|
32
|
-
"7d": "the last 7 days",
|
|
33
|
-
"30d": "the last 30 days",
|
|
34
|
-
} as const;
|
|
35
|
-
|
|
36
|
-
export function InsightsOverview(props: InsightsOverviewProps) {
|
|
37
|
-
const dateTimeFormat = useInsightsTimeFormat();
|
|
38
|
-
|
|
39
|
-
if (props.status === "loading") {
|
|
40
|
-
return (
|
|
41
|
-
<Card aria-label="Loading reporting installations" className="shadow-sm">
|
|
42
|
-
<CardHeader className="px-4 pt-4 pb-2 sm:px-6 sm:pt-6">
|
|
43
|
-
<Skeleton className="h-4 w-40" />
|
|
44
|
-
<Skeleton className="h-3 w-full max-w-md" />
|
|
45
|
-
</CardHeader>
|
|
46
|
-
<CardContent className="px-4 pb-6 sm:px-6">
|
|
47
|
-
<Skeleton className="h-12 w-28" />
|
|
48
|
-
</CardContent>
|
|
49
|
-
<CardFooter className="border-t px-4 py-3 sm:px-6">
|
|
50
|
-
<Skeleton className="h-8 w-full max-w-sm" />
|
|
51
|
-
</CardFooter>
|
|
52
|
-
</Card>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
37
|
+
const dates = new Intl.DateTimeFormat("en", {
|
|
38
|
+
month: "short",
|
|
39
|
+
day: "numeric",
|
|
40
|
+
timeZone: "UTC",
|
|
41
|
+
});
|
|
55
42
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
}
|
|
43
|
+
const rate = (report: RecoveryReport): string => {
|
|
44
|
+
const attempts = report.activeDays + report.failedLaunches;
|
|
45
|
+
return attempts === 0
|
|
46
|
+
? "—"
|
|
47
|
+
: `${((report.failedLaunches / attempts) * 100).toFixed(2)}%`;
|
|
48
|
+
};
|
|
64
49
|
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
function ReleaseHealth({
|
|
51
|
+
report,
|
|
52
|
+
releaseId,
|
|
53
|
+
window,
|
|
54
|
+
chart,
|
|
55
|
+
onChartChange,
|
|
56
|
+
onReleaseChange,
|
|
57
|
+
onWindowChange,
|
|
58
|
+
}: {
|
|
59
|
+
readonly report: RecoveryReport;
|
|
60
|
+
readonly releaseId?: string;
|
|
61
|
+
readonly window: RecoveryInput["window"];
|
|
62
|
+
readonly onWindowChange: (window: RecoveryInput["window"]) => void;
|
|
63
|
+
readonly chart: HealthChart;
|
|
64
|
+
readonly onChartChange: (chart: HealthChart) => void;
|
|
65
|
+
readonly onReleaseChange: (releaseId: string) => void;
|
|
66
|
+
}) {
|
|
67
|
+
const attempts = report.activeDays + report.failedLaunches;
|
|
68
|
+
const activeInstallations = (
|
|
69
|
+
<EstimatedCount value={report.activeInstallations} />
|
|
70
|
+
);
|
|
71
|
+
const metrics = [
|
|
72
|
+
["Active installations", activeInstallations],
|
|
73
|
+
["Active days", report.activeDays.toLocaleString()],
|
|
74
|
+
["Failed launches", report.failedLaunches.toLocaleString()],
|
|
75
|
+
["Crash rate", attempts === 0 ? "— / No launch reports" : rate(report)],
|
|
76
|
+
] as const;
|
|
77
|
+
return (
|
|
78
|
+
<div className="flex flex-col gap-8">
|
|
79
|
+
<dl className="grid grid-cols-2 gap-5 lg:grid-cols-4">
|
|
80
|
+
{metrics.map(([label, value]) => (
|
|
81
|
+
<div key={label} className="flex flex-col gap-1">
|
|
82
|
+
<dt className="flex items-center gap-1 text-sm text-muted-foreground">
|
|
83
|
+
{label}
|
|
84
|
+
{label === "Active installations" ? (
|
|
85
|
+
<InsightsInfo label="About active installations">
|
|
86
|
+
Distinct installations that reported in this scope and period,
|
|
87
|
+
or that launched this release, estimated: typically within
|
|
88
|
+
about 3%.
|
|
89
|
+
</InsightsInfo>
|
|
90
|
+
) : label === "Active days" ? (
|
|
91
|
+
<InsightsInfo label="About active days">
|
|
92
|
+
Each installation counts once for each UTC day it launched,
|
|
93
|
+
and once more on a day an update applies or recovers. The
|
|
94
|
+
activity totals include these additional reports.
|
|
95
|
+
</InsightsInfo>
|
|
96
|
+
) : label === "Crash rate" ? (
|
|
97
|
+
<InsightsInfo label="About crash rate">
|
|
98
|
+
Reported OTA launch failures divided by active days plus
|
|
99
|
+
failed launches for the same scope and period.
|
|
100
|
+
</InsightsInfo>
|
|
101
|
+
) : null}
|
|
102
|
+
</dt>
|
|
103
|
+
<dd className="text-3xl font-semibold tracking-tight tabular-nums">
|
|
104
|
+
{value}
|
|
105
|
+
</dd>
|
|
106
|
+
</div>
|
|
107
|
+
))}
|
|
108
|
+
</dl>
|
|
109
|
+
<Tabs
|
|
110
|
+
value={chart}
|
|
111
|
+
onValueChange={(value) => onChartChange(value as HealthChart)}
|
|
112
|
+
className="gap-5"
|
|
113
|
+
>
|
|
114
|
+
<TabsList
|
|
115
|
+
aria-label="Release health chart"
|
|
116
|
+
className="min-h-11 sm:min-h-9"
|
|
117
|
+
>
|
|
118
|
+
<TabsTrigger value="share" className="px-3">
|
|
119
|
+
Bundle share
|
|
120
|
+
</TabsTrigger>
|
|
121
|
+
<TabsTrigger value="adoption" className="px-3">
|
|
122
|
+
Adoption
|
|
123
|
+
</TabsTrigger>
|
|
124
|
+
<TabsTrigger value="failures" className="px-3">
|
|
125
|
+
Launch failures
|
|
126
|
+
</TabsTrigger>
|
|
127
|
+
</TabsList>
|
|
128
|
+
<TabsContent value="share">
|
|
129
|
+
<BundleDistributionChart
|
|
130
|
+
history={report.distribution}
|
|
131
|
+
releaseId={releaseId}
|
|
132
|
+
/>
|
|
133
|
+
</TabsContent>
|
|
134
|
+
<TabsContent value="adoption">
|
|
135
|
+
<ReleaseAdoptionChart
|
|
136
|
+
report={report}
|
|
137
|
+
releaseId={releaseId}
|
|
138
|
+
window={window}
|
|
139
|
+
onReleaseChange={onReleaseChange}
|
|
140
|
+
onWindowChange={onWindowChange}
|
|
141
|
+
/>
|
|
142
|
+
</TabsContent>
|
|
143
|
+
<TabsContent value="failures">
|
|
144
|
+
{report.points.length === 0 ? (
|
|
145
|
+
<Empty className="min-h-64">
|
|
146
|
+
<EmptyHeader>
|
|
147
|
+
<EmptyTitle>No launch reports in this period.</EmptyTitle>
|
|
148
|
+
<EmptyDescription>
|
|
149
|
+
Choose a longer period or refresh after an app reports.
|
|
150
|
+
</EmptyDescription>
|
|
151
|
+
</EmptyHeader>
|
|
152
|
+
</Empty>
|
|
153
|
+
) : (
|
|
154
|
+
<ChartContainer
|
|
155
|
+
aria-label="Daily failed launches"
|
|
156
|
+
className="h-64 w-full aspect-auto"
|
|
157
|
+
config={{
|
|
158
|
+
failedLaunches: {
|
|
159
|
+
label: "Failed launches",
|
|
160
|
+
color: "var(--chart-1)",
|
|
161
|
+
},
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
<LineChart data={report.points} margin={{ left: -12, right: 12 }}>
|
|
165
|
+
<CartesianGrid vertical={false} />
|
|
166
|
+
<XAxis
|
|
167
|
+
axisLine={false}
|
|
168
|
+
dataKey="startMs"
|
|
169
|
+
tickFormatter={(value) => dates.format(value)}
|
|
170
|
+
tickLine={false}
|
|
171
|
+
/>
|
|
172
|
+
<YAxis
|
|
173
|
+
allowDecimals={false}
|
|
174
|
+
axisLine={false}
|
|
175
|
+
tickLine={false}
|
|
176
|
+
/>
|
|
177
|
+
<ChartTooltip
|
|
178
|
+
content={
|
|
179
|
+
<ChartTooltipContent
|
|
180
|
+
labelFormatter={(_, payload) =>
|
|
181
|
+
`${dates.format(payload[0]?.payload.startMs)} · UTC`
|
|
182
|
+
}
|
|
183
|
+
/>
|
|
184
|
+
}
|
|
185
|
+
/>
|
|
186
|
+
<Line
|
|
187
|
+
dataKey="failedLaunches"
|
|
188
|
+
stroke="var(--color-failedLaunches)"
|
|
189
|
+
strokeWidth={2}
|
|
190
|
+
isAnimationActive={false}
|
|
191
|
+
/>
|
|
192
|
+
</LineChart>
|
|
193
|
+
</ChartContainer>
|
|
194
|
+
)}
|
|
195
|
+
</TabsContent>
|
|
196
|
+
</Tabs>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
67
200
|
|
|
201
|
+
export function InsightsOverview({
|
|
202
|
+
input,
|
|
203
|
+
onWindowChange,
|
|
204
|
+
query,
|
|
205
|
+
onRefresh,
|
|
206
|
+
chart,
|
|
207
|
+
onChartChange,
|
|
208
|
+
onReleaseChange,
|
|
209
|
+
}: {
|
|
210
|
+
readonly input: RecoveryInput;
|
|
211
|
+
readonly chart: HealthChart;
|
|
212
|
+
readonly onChartChange: (chart: HealthChart) => void;
|
|
213
|
+
/** Chooses the Release health release, as the Release ID filter does. */
|
|
214
|
+
readonly onReleaseChange: (releaseId: string) => void;
|
|
215
|
+
readonly query: {
|
|
216
|
+
readonly data: RecoveryReport | undefined;
|
|
217
|
+
readonly error: Error | null;
|
|
218
|
+
readonly isPending: boolean;
|
|
219
|
+
readonly isFetching: boolean;
|
|
220
|
+
};
|
|
221
|
+
readonly onRefresh: () => void;
|
|
222
|
+
readonly onWindowChange: (window: RecoveryInput["window"]) => void;
|
|
223
|
+
}) {
|
|
68
224
|
return (
|
|
69
|
-
<section
|
|
70
|
-
aria-labelledby="reporting-installations-heading"
|
|
71
|
-
className="flex flex-col gap-4"
|
|
72
|
-
>
|
|
225
|
+
<section aria-label="Release health">
|
|
73
226
|
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
74
|
-
<CardHeader className="
|
|
75
|
-
<
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
227
|
+
<CardHeader className="flex flex-col gap-4 p-6">
|
|
228
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
229
|
+
<CardTitle>Release health</CardTitle>
|
|
230
|
+
<div className="flex items-center gap-2">
|
|
231
|
+
<InsightsPeriodSelector
|
|
232
|
+
window={input.window}
|
|
233
|
+
onWindowChange={onWindowChange}
|
|
234
|
+
/>
|
|
235
|
+
<Button
|
|
236
|
+
aria-label="Refresh release health"
|
|
237
|
+
variant="ghost"
|
|
238
|
+
size="icon-lg"
|
|
239
|
+
disabled={query.isFetching}
|
|
240
|
+
onClick={onRefresh}
|
|
241
|
+
>
|
|
242
|
+
<RotateCw
|
|
243
|
+
aria-hidden="true"
|
|
244
|
+
className={cn(query.isFetching && "motion-safe:animate-spin")}
|
|
245
|
+
/>
|
|
246
|
+
</Button>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
85
249
|
</CardHeader>
|
|
86
|
-
<CardContent className="px-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
250
|
+
<CardContent aria-busy={query.isFetching} className="px-6 pb-6">
|
|
251
|
+
{query.isPending ? (
|
|
252
|
+
<Skeleton aria-label="Loading release health" className="h-80" />
|
|
253
|
+
) : query.error ? (
|
|
254
|
+
<InsightsErrorAlert
|
|
255
|
+
error={query.error}
|
|
256
|
+
fallbackTitle="Release health unavailable"
|
|
257
|
+
/>
|
|
258
|
+
) : query.data ? (
|
|
259
|
+
<ReleaseHealth
|
|
260
|
+
report={query.data}
|
|
261
|
+
releaseId={input.releaseId}
|
|
262
|
+
window={input.window}
|
|
263
|
+
onWindowChange={onWindowChange}
|
|
264
|
+
chart={chart}
|
|
265
|
+
onChartChange={onChartChange}
|
|
266
|
+
onReleaseChange={onReleaseChange}
|
|
267
|
+
/>
|
|
268
|
+
) : null}
|
|
90
269
|
</CardContent>
|
|
91
|
-
<CardFooter className="flex
|
|
92
|
-
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
touch
|
|
98
|
-
value={active.reportingInstallations.measuredAtMs}
|
|
99
|
-
/>
|
|
100
|
-
</dd>
|
|
101
|
-
</dl>
|
|
270
|
+
<CardFooter className="flex items-center justify-between border-t px-6 py-5">
|
|
271
|
+
{query.data?.coverage.kind === "partial" ? (
|
|
272
|
+
<span className="text-xs text-muted-foreground">Partial</span>
|
|
273
|
+
) : (
|
|
274
|
+
<span />
|
|
275
|
+
)}
|
|
102
276
|
<Link
|
|
103
|
-
className={buttonVariants({
|
|
104
|
-
className: "h-11 px-3 lg:h-8",
|
|
105
|
-
variant: "outline",
|
|
106
|
-
})}
|
|
277
|
+
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
107
278
|
to="/installations"
|
|
108
279
|
>
|
|
109
|
-
|
|
110
|
-
|
|
280
|
+
<ListIcon aria-hidden="true" />
|
|
281
|
+
Event history
|
|
111
282
|
</Link>
|
|
112
283
|
</CardFooter>
|
|
113
284
|
</Card>
|
|
114
|
-
{bundle ? (
|
|
115
|
-
<Card className="min-w-0 overflow-hidden shadow-sm">
|
|
116
|
-
<CardHeader>
|
|
117
|
-
<CardTitle>Selected bundle</CardTitle>
|
|
118
|
-
<CardDescription className="break-all">
|
|
119
|
-
{bundle.bundleId}
|
|
120
|
-
</CardDescription>
|
|
121
|
-
</CardHeader>
|
|
122
|
-
<CardContent>
|
|
123
|
-
<dl className="flex flex-col gap-5">
|
|
124
|
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
125
|
-
<div>
|
|
126
|
-
<dt className="text-sm">Selected bundle installations</dt>
|
|
127
|
-
<dd className="mt-1 text-2xl font-semibold tabular-nums">
|
|
128
|
-
{bundle.reportingInstallations.count.toLocaleString()}
|
|
129
|
-
</dd>
|
|
130
|
-
</div>
|
|
131
|
-
<div className="text-xs text-muted-foreground">
|
|
132
|
-
Measured at{" "}
|
|
133
|
-
<EventTimestamp
|
|
134
|
-
formatter={dateTimeFormat}
|
|
135
|
-
touch
|
|
136
|
-
value={bundle.reportingInstallations.measuredAtMs}
|
|
137
|
-
/>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
{(
|
|
141
|
-
[
|
|
142
|
-
["applied", bundle.appliedReports],
|
|
143
|
-
["recovered", bundle.recoveredReports],
|
|
144
|
-
["adopted", bundle.adoptedReports],
|
|
145
|
-
] as const
|
|
146
|
-
).map(([outcome, measurement]) => (
|
|
147
|
-
<div
|
|
148
|
-
className="flex flex-wrap items-center justify-between gap-3"
|
|
149
|
-
key={outcome}
|
|
150
|
-
>
|
|
151
|
-
<div>
|
|
152
|
-
<dt className="text-sm">{outcomeLabels[outcome]}</dt>
|
|
153
|
-
<dd className="mt-1 text-2xl font-semibold tabular-nums">
|
|
154
|
-
{measurement.count.toLocaleString()}
|
|
155
|
-
</dd>
|
|
156
|
-
<div className="mt-1 text-xs text-muted-foreground">
|
|
157
|
-
Measured at{" "}
|
|
158
|
-
<EventTimestamp
|
|
159
|
-
formatter={dateTimeFormat}
|
|
160
|
-
touch
|
|
161
|
-
value={measurement.measuredAtMs}
|
|
162
|
-
/>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
<Button
|
|
166
|
-
className="h-11 lg:h-8"
|
|
167
|
-
variant="outline"
|
|
168
|
-
onClick={() =>
|
|
169
|
-
props.onOutcomeSelect({
|
|
170
|
-
bundle: {
|
|
171
|
-
platform: active.platform,
|
|
172
|
-
channel: active.channel,
|
|
173
|
-
bundleId: bundle.bundleId,
|
|
174
|
-
outcome,
|
|
175
|
-
},
|
|
176
|
-
sinceMs: active.sinceMs,
|
|
177
|
-
beforeReceivedAtMs: active.beforeReceivedAtMs,
|
|
178
|
-
})
|
|
179
|
-
}
|
|
180
|
-
>
|
|
181
|
-
View {outcome} reports
|
|
182
|
-
<ArrowUpRight aria-hidden="true" data-icon="inline-end" />
|
|
183
|
-
</Button>
|
|
184
|
-
</div>
|
|
185
|
-
))}
|
|
186
|
-
</dl>
|
|
187
|
-
</CardContent>
|
|
188
|
-
<CardFooter>
|
|
189
|
-
<p className="text-xs text-muted-foreground">
|
|
190
|
-
Installation counts are independent live measurements. Outcome
|
|
191
|
-
counts are accepted reports, not unique devices or update
|
|
192
|
-
attempts. Recovery reports identify the bundle recovered from.
|
|
193
|
-
</p>
|
|
194
|
-
</CardFooter>
|
|
195
|
-
</Card>
|
|
196
|
-
) : null}
|
|
197
|
-
<p className="text-xs text-muted-foreground">
|
|
198
|
-
Offline installations, opt-outs, and reports that did not reach the
|
|
199
|
-
server are absent.
|
|
200
|
-
</p>
|
|
201
285
|
</section>
|
|
202
286
|
);
|
|
203
287
|
}
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { Link } from "@tanstack/react-router";
|
|
2
|
-
import { ChartNoAxesCombined } from "lucide-react";
|
|
2
|
+
import { ChartNoAxesCombined, ListIcon } from "lucide-react";
|
|
3
3
|
|
|
4
4
|
import { buttonVariants } from "@/components/ui/button";
|
|
5
5
|
import { SidebarTrigger } from "@/components/ui/sidebar";
|
|
6
|
+
import { useConsoleFeature } from "@/lib/console-features-api";
|
|
7
|
+
import type { InsightsSearch } from "@/lib/insights-search";
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
6
9
|
|
|
7
10
|
export function InsightsPageHeader({
|
|
8
11
|
view,
|
|
12
|
+
overviewSearch,
|
|
9
13
|
}: {
|
|
10
|
-
readonly view: "overview" | "events";
|
|
14
|
+
readonly view: "overview" | "events" | "distribution";
|
|
15
|
+
readonly overviewSearch?: InsightsSearch;
|
|
11
16
|
}) {
|
|
17
|
+
// A self-hosted server's admin API serves events, not the overview.
|
|
18
|
+
const overview = useConsoleFeature("insightsAnalytics");
|
|
12
19
|
return (
|
|
13
20
|
<header className="sticky top-0 z-10 flex shrink-0 flex-wrap items-center gap-3 border-b bg-background px-3 py-3 sm:bg-card/70 sm:px-4 sm:backdrop-blur-sm">
|
|
14
21
|
<SidebarTrigger className="-ml-1 size-11 lg:size-7" />
|
|
@@ -19,28 +26,36 @@ export function InsightsPageHeader({
|
|
|
19
26
|
/>
|
|
20
27
|
<h1 className="text-sm font-medium">Insights</h1>
|
|
21
28
|
</div>
|
|
22
|
-
<nav aria-label="Insights views" className="ml-auto flex gap-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
className
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
<nav aria-label="Insights views" className="ml-auto flex gap-2">
|
|
30
|
+
{overview ? (
|
|
31
|
+
<Link
|
|
32
|
+
aria-current={view === "overview" ? "page" : undefined}
|
|
33
|
+
className={cn(
|
|
34
|
+
buttonVariants({
|
|
35
|
+
className: "h-11 px-3 lg:h-8 lg:px-2.5",
|
|
36
|
+
size: "lg",
|
|
37
|
+
variant: view === "overview" ? "secondary" : "ghost",
|
|
38
|
+
}),
|
|
39
|
+
)}
|
|
40
|
+
search={overviewSearch}
|
|
41
|
+
to="/insights"
|
|
42
|
+
>
|
|
43
|
+
Overview
|
|
44
|
+
</Link>
|
|
45
|
+
) : null}
|
|
34
46
|
<Link
|
|
35
47
|
aria-current={view === "events" ? "page" : undefined}
|
|
36
|
-
className={
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
className={cn(
|
|
49
|
+
buttonVariants({
|
|
50
|
+
className: "h-11 px-3 lg:h-8 lg:px-2.5",
|
|
51
|
+
size: "lg",
|
|
52
|
+
variant: view === "events" ? "secondary" : "ghost",
|
|
53
|
+
}),
|
|
54
|
+
)}
|
|
41
55
|
to="/installations"
|
|
42
56
|
>
|
|
43
|
-
|
|
57
|
+
<ListIcon aria-hidden="true" data-icon="inline-start" />
|
|
58
|
+
All events
|
|
44
59
|
</Link>
|
|
45
60
|
</nav>
|
|
46
61
|
</header>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
2
|
+
import type { InsightsWindow } from "@/lib/insights-api";
|
|
3
|
+
import { usageMetrics, type UsageWindow } from "@/lib/insights-usage";
|
|
4
|
+
|
|
5
|
+
/** A segmented control over fixed periods, each shown by its short value. */
|
|
6
|
+
export function PeriodSelector<TPeriod extends string>({
|
|
7
|
+
label,
|
|
8
|
+
onPeriodChange,
|
|
9
|
+
period,
|
|
10
|
+
periods,
|
|
11
|
+
}: {
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly onPeriodChange: (period: TPeriod) => void;
|
|
14
|
+
readonly period: TPeriod;
|
|
15
|
+
readonly periods: readonly {
|
|
16
|
+
readonly value: TPeriod;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
}[];
|
|
19
|
+
}) {
|
|
20
|
+
return (
|
|
21
|
+
<Tabs
|
|
22
|
+
value={period}
|
|
23
|
+
onValueChange={(value) => {
|
|
24
|
+
const next = periods.find((item) => item.value === value);
|
|
25
|
+
if (next) onPeriodChange(next.value);
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<TabsList aria-label={label} className="min-h-11 sm:min-h-9">
|
|
29
|
+
{periods.map(({ value, name }) => (
|
|
30
|
+
<TabsTrigger
|
|
31
|
+
key={value}
|
|
32
|
+
value={value}
|
|
33
|
+
aria-label={name}
|
|
34
|
+
className="min-w-12 px-3"
|
|
35
|
+
>
|
|
36
|
+
{value}
|
|
37
|
+
</TabsTrigger>
|
|
38
|
+
))}
|
|
39
|
+
</TabsList>
|
|
40
|
+
</Tabs>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const reportingPeriods = (["24h", "7d", "30d"] as const).map((value) => ({
|
|
45
|
+
value,
|
|
46
|
+
name: usageMetrics[value].period,
|
|
47
|
+
}));
|
|
48
|
+
const usagePeriods = (Object.keys(usageMetrics) as UsageWindow[]).map(
|
|
49
|
+
(value) => ({ value, name: usageMetrics[value].period }),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export function InsightsPeriodSelector({
|
|
53
|
+
window,
|
|
54
|
+
onWindowChange,
|
|
55
|
+
}: {
|
|
56
|
+
readonly window: InsightsWindow;
|
|
57
|
+
readonly onWindowChange: (window: InsightsWindow) => void;
|
|
58
|
+
}) {
|
|
59
|
+
return (
|
|
60
|
+
<PeriodSelector
|
|
61
|
+
label="Reporting period"
|
|
62
|
+
onPeriodChange={onWindowChange}
|
|
63
|
+
period={window}
|
|
64
|
+
periods={reportingPeriods}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** App usage's periods: the reporting periods, and 12 months where daily totals cover it. */
|
|
70
|
+
export function UsagePeriodSelector({
|
|
71
|
+
window,
|
|
72
|
+
onWindowChange,
|
|
73
|
+
twelveMonths = true,
|
|
74
|
+
}: {
|
|
75
|
+
readonly window: UsageWindow;
|
|
76
|
+
readonly onWindowChange: (window: UsageWindow) => void;
|
|
77
|
+
readonly twelveMonths?: boolean;
|
|
78
|
+
}) {
|
|
79
|
+
return (
|
|
80
|
+
<PeriodSelector
|
|
81
|
+
label="Reporting period"
|
|
82
|
+
onPeriodChange={onWindowChange}
|
|
83
|
+
period={window}
|
|
84
|
+
periods={usagePeriods.filter(
|
|
85
|
+
({ value }) => twelveMonths || value !== "12m",
|
|
86
|
+
)}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|