@hot-updater/console 1.0.0-rc.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/ConsoleFeatureUnavailable-jB4Loglh.js +1 -0
- package/.output/public/assets/EventDetails-B6yUsyNA.js +1 -0
- package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
- package/.output/public/assets/InsightsInfo-BNO0WznZ.js +1 -0
- package/.output/public/assets/InsightsPageHeader-B892oVml.js +1 -0
- package/.output/public/assets/NotFoundPage-DIqCpWa3.js +1 -0
- package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
- package/.output/public/assets/api-DCM0wr6O.js +1 -0
- package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
- package/.output/public/assets/api-keys-DVTL_--f.js +1 -0
- package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
- package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
- package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
- package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
- package/.output/public/assets/console-features-api-BH5m_PtQ.js +1 -0
- package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
- package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
- package/.output/public/assets/empty-CuT2UhFp.js +1 -0
- package/.output/public/assets/field-BBDpjIqu.js +1 -0
- package/.output/public/assets/index-6o51GkFe.js +11 -0
- package/.output/public/assets/input-group-ja_30qaF.js +1 -0
- package/.output/public/assets/insights-CmraDzQh.js +1 -0
- package/.output/public/assets/insights-CxZ2GCYV.js +2 -0
- package/.output/public/assets/insights-api-BigAU9sZ.js +1 -0
- package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
- package/.output/public/assets/insights-vNRCrfnS.js +42 -0
- package/.output/public/assets/insights_.distribution-ChOerfJt.js +1 -0
- package/.output/public/assets/insights_.distribution-D-e4iga8.js +1 -0
- package/.output/public/assets/insights_.distribution-DZtkibX1.js +2 -0
- package/.output/public/assets/installations-BazSJXE0.js +2 -0
- package/.output/public/assets/installations-ClHTqdAO.js +1 -0
- package/.output/public/assets/installations-DpKsBuHi.js +1 -0
- package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
- package/.output/public/assets/list-CgrYiadM.js +1 -0
- package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
- package/.output/public/assets/remote-config-BWwHOkCK.js +1 -0
- package/.output/public/assets/remote-config-Br56BODb.js +6 -0
- package/.output/public/assets/remote-config-CXOi0LRu.js +1 -0
- package/.output/public/assets/routes-B88Erkpk.js +2 -0
- package/.output/public/assets/routes-CfzsAleC.js +1 -0
- package/.output/public/assets/search-DaTDPW25.js +1 -0
- package/.output/public/assets/select-BZZBXMkr.js +1 -0
- package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
- package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
- package/.output/public/assets/styles-DTGEAJFv.css +2 -0
- package/.output/public/assets/switch-DZ1TPwx-.js +1 -0
- package/.output/public/assets/table-Gm4qRWwE.js +1 -0
- package/.output/public/assets/tabs-C7FOEjFC.js +1 -0
- package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
- package/.output/public/assets/useCompositeListItem-9E4pj_CH.js +1 -0
- package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
- package/.output/server/__23tanstack-start-server-fn-resolver-BpRlos5Q.mjs +170 -0
- package/.output/server/_libs/@base-ui/react+[...].mjs +4389 -4207
- 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 +221 -9
- package/.output/server/_libs/next-themes.mjs +1 -1
- package/.output/server/_libs/react-redux+[...].mjs +1 -1
- package/.output/server/_libs/recharts+[...].mjs +483 -901
- 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 +2010 -309
- package/.output/server/_libs/tanstack__react-query.mjs +448 -21
- package/.output/server/_libs/verkit.mjs +102 -1
- package/.output/server/_ssr/BundleIdDisplay-x94o9rlU.mjs +13 -0
- package/.output/server/_ssr/ConsoleFeatureUnavailable-ldW8v8-q.mjs +58 -0
- package/.output/server/_ssr/EventDetails-Q8kfj9Nr.mjs +344 -0
- package/.output/server/_ssr/{HashValueDisplay-CyMmr0O9.mjs → HashValueDisplay-n1GholGS.mjs} +3 -3
- package/.output/server/_ssr/InsightsInfo-DuJFatQO.mjs +75 -0
- package/.output/server/_ssr/InsightsPageHeader-BSUX3Ye9.mjs +145 -0
- package/.output/server/_ssr/NotFoundPage-DZzOOzS3.mjs +37 -0
- package/.output/server/_ssr/{dialog-CKvQ4VIE.mjs → alert-dialog-C43Doa0I.mjs} +6 -83
- package/.output/server/_ssr/{api-keys-CzZ7Ont4.mjs → api-keys-B4sYm8dM.mjs} +48 -9
- package/.output/server/_ssr/api-keys-CEnM9fqG.mjs +5 -0
- package/.output/server/_ssr/api-keys-rpc-BIFuUgMt.mjs +52 -0
- package/.output/server/_ssr/api-oZjRn9tP.mjs +186 -0
- package/.output/server/_ssr/api-rpc-BNiFRRK8.mjs +240 -0
- package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
- package/.output/server/_ssr/{auth-rpc-DH6Bhwcz.mjs → auth-rpc-DnZqYxyq.mjs} +6 -6
- package/.output/server/_ssr/auth.server-DFpyxRDd.mjs +5374 -0
- package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
- package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
- package/.output/server/_ssr/config.server-DskXCAyI.mjs +35 -0
- package/.output/server/_ssr/console-access-BJfAXknd.mjs +17 -0
- package/.output/server/_ssr/console-features-Oz0q20jJ.mjs +61 -0
- package/.output/server/_ssr/console-features-api-C9kiKvaV.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-P0GL7Z-R.mjs +19 -0
- package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
- package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
- package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
- package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
- package/.output/server/_ssr/databaseRows-CPphvbag.mjs +73 -0
- package/.output/server/_ssr/dialog-BbYDMjdC.mjs +84 -0
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-CbQbBIw-.mjs} +140 -7
- package/.output/server/_ssr/{downloadBundle-CI7JUKvC.mjs → downloadBundle-CZysjmNW.mjs} +10 -10
- package/.output/server/_ssr/empty-CVLGfxuu.mjs +60 -0
- package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
- package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
- package/.output/server/_ssr/field-asw83X0v.mjs +189 -0
- package/.output/server/_ssr/getBundleChildren-Cv9iz1As.mjs +22 -0
- package/.output/server/_ssr/{input-group-BrGSjz4S.mjs → input-group-CuYJHosb.mjs} +2 -2
- package/.output/server/_ssr/insights-B55FG5fm.mjs +14 -0
- package/.output/server/_ssr/insights-CWnHIFUV.mjs +5 -0
- package/.output/server/_ssr/insights-CpLpedFZ.mjs +2315 -0
- package/.output/server/_ssr/insights-api-sjws23WF.mjs +122 -0
- package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
- package/.output/server/_ssr/insights-failures-rpc-BvbWvqNf.mjs +36 -0
- package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
- package/.output/server/_ssr/insights-recovery-BWQhiZaF.mjs +28 -0
- package/.output/server/_ssr/insights-recovery-rpc-GiYXdkaY.mjs +32 -0
- package/.output/server/_ssr/{insights-rpc-DDKmIrpV.mjs → insights-rpc-DP_8Rmz4.mjs} +22 -14
- package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-BiolgB8e.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-KStL0zOx.mjs +41 -0
- package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-CVrnbP3R.mjs +7 -0
- package/.output/server/_ssr/insights_.distribution-D2HaNTAA.mjs +16 -0
- package/.output/server/_ssr/{insights_.distribution-cIsU-kHG.mjs → insights_.distribution-DN9pR422.mjs} +50 -22
- package/.output/server/_ssr/installations-BHmbbnH8.mjs +5 -0
- package/.output/server/_ssr/{installations-C5anhyP0.mjs → installations-BSCmFq8Q.mjs} +279 -367
- package/.output/server/_ssr/installations-DD1g__Nc.mjs +69 -0
- package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
- package/.output/server/_ssr/plugins-BaD1slHx.mjs +2086 -0
- package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
- package/.output/server/_ssr/release-adoption-rpc-BsstqxUp.mjs +41 -0
- package/.output/server/_ssr/releaseAdoption-CtOFkwli.mjs +114 -0
- package/.output/server/_ssr/releaseManagement-DFMUnDF5.mjs +11 -0
- package/.output/server/_ssr/remote-config-C2Ee8lXO.mjs +5 -0
- package/.output/server/_ssr/remote-config-Dv1ccMo-.mjs +17 -0
- package/.output/server/_ssr/remote-config-of0lGQH8.mjs +2648 -0
- package/.output/server/_ssr/remote-config-rpc-BzX31mUS.mjs +115 -0
- package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
- package/.output/server/_ssr/{router-BuanSsRI.mjs → router-D8NuoJ_I.mjs} +107 -134
- package/.output/server/_ssr/{routes-DY-vqU6R.mjs → routes-DgvnFqDy.mjs} +195 -429
- package/.output/server/_ssr/routes-V8sDybGf.mjs +409 -0
- package/.output/server/_ssr/runtime.server-De0QVWc9.mjs +104 -0
- package/.output/server/_ssr/schema-BiAVyEj2.mjs +361 -0
- package/.output/server/_ssr/{select-DD2szs3Y.mjs → select-t3iXRwc9.mjs} +3 -3
- package/.output/server/_ssr/server-h4VxnExB.mjs +8325 -0
- package/.output/server/_ssr/{createSsrRpc-CdlfejmI.mjs → sidebar-DJcErBMo.mjs} +107 -47
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
- package/.output/server/_ssr/switch-E-btwMGt.mjs +43 -0
- package/.output/server/_ssr/{table-CTj6n-4F.mjs → table-37Dd23TN.mjs} +2 -2
- package/.output/server/_ssr/tabs-DEFnTm95.mjs +45 -0
- package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C88s9jWF.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-55AGsehh.mjs +139 -0
- package/.output/server/index.mjs +309 -211
- package/LICENSE +13 -14
- package/README.md +80 -71
- 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 +69 -74
- package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
- package/src/components/NotFoundPage.tsx +7 -7
- package/src/components/features/FeatureSlots.tsx +103 -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 +8 -6
- package/src/components/features/insights/AppUsage.tsx +41 -14
- package/src/components/features/insights/BuiltinBundle.tsx +36 -0
- package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
- package/src/components/features/insights/EstimatedCount.tsx +18 -0
- package/src/components/features/insights/EventDetails.tsx +390 -26
- package/src/components/features/insights/EventHistoryCard.tsx +111 -25
- package/src/components/features/insights/EventHistoryList.tsx +1 -1
- package/src/components/features/insights/FailureReports.tsx +407 -0
- package/src/components/features/insights/InsightsControls.tsx +240 -142
- package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
- package/src/components/features/insights/InsightsOverview.tsx +601 -403
- package/src/components/features/insights/InsightsPageHeader.tsx +19 -14
- package/src/components/features/insights/InsightsPeriodSelector.tsx +67 -12
- package/src/components/features/insights/InstallationHistoryCard.tsx +65 -10
- package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
- package/src/components/features/insights/ReleaseActivity.tsx +308 -0
- package/src/components/features/insights/ReportingDevicesSummary.tsx +13 -13
- package/src/components/features/insights/RollbackButton.tsx +118 -0
- package/src/components/features/insights/UpdateFailures.tsx +382 -0
- package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +27 -13
- package/src/components/features/remote-config/ConditionDialog.tsx +592 -0
- package/src/components/features/remote-config/ConditionsCard.tsx +247 -0
- package/src/components/features/remote-config/ParameterDialog.tsx +426 -0
- package/src/components/features/remote-config/ParametersCard.tsx +254 -0
- package/src/components/features/remote-config/PreviewCard.tsx +284 -0
- package/src/components/features/remote-config/PublishDialog.tsx +325 -0
- package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
- package/src/components/features/remote-config/RuleSummary.tsx +21 -0
- package/src/components/features/remote-config/ValueInput.tsx +151 -0
- package/src/components/features/remote-config/VersionsCard.tsx +384 -0
- package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
- package/src/components/features/remote-config/useDateTimeText.ts +16 -0
- package/src/components/ui/sidebar.tsx +22 -29
- 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 +125 -288
- package/src/lib/api.ts +15 -134
- package/src/lib/bundle-activity.ts +24 -4
- 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 +98 -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 +130 -0
- package/src/lib/insights-recovery-rpc.ts +13 -17
- package/src/lib/insights-recovery.ts +12 -36
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +50 -4
- package/src/lib/insights-usage-rpc.ts +15 -9
- package/src/lib/insights-usage.ts +41 -3
- package/src/lib/insights-view.ts +34 -5
- package/src/lib/release-adoption-api.ts +214 -0
- package/src/lib/release-adoption-rpc.ts +49 -0
- package/src/lib/release-adoption.ts +164 -0
- package/src/lib/remote-config-api.ts +119 -0
- package/src/lib/remote-config-draft.ts +666 -0
- package/src/lib/remote-config-rpc.ts +196 -0
- package/src/lib/scroll-restoration.ts +21 -0
- package/src/lib/server/auth.server.ts +13 -5
- package/src/lib/server/bundleActivity.ts +27 -28
- package/src/lib/server/config.server.ts +21 -76
- package/src/lib/server/console-feature-apis.ts +70 -0
- package/src/lib/server/console-runtime.server.ts +82 -38
- package/src/lib/server/downloadBundle.ts +14 -15
- package/src/lib/server/failureReports.ts +50 -0
- package/src/lib/server/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsUsage.ts +52 -113
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseAdoption.ts +151 -0
- package/src/lib/server/releaseReachability.ts +8 -8
- package/src/lib/server/remoteConfig.ts +75 -0
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +66 -0
- package/src/lib/use-dialog-target.ts +27 -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 +7 -1
- package/src/routes/api/bundles/$bundleId/download.ts +2 -5
- package/src/routes/api-keys.tsx +5 -2
- package/src/routes/index.tsx +210 -152
- package/src/routes/insights.tsx +91 -23
- package/src/routes/insights_.distribution.tsx +50 -9
- package/src/routes/installations.tsx +97 -40
- package/src/routes/remote-config.tsx +42 -0
- package/src/styles.css +9 -0
- package/src/vite-internal.ts +15 -3
- package/src/vite.ts +2 -0
- package/.output/public/assets/HashValueDisplay-BpTc3Oyg.js +0 -1
- package/.output/public/assets/InsightsPageHeader-DC2eEst8.js +0 -1
- package/.output/public/assets/api-CdUyUqzR.js +0 -42
- package/.output/public/assets/api-keys-64PM9PwW.js +0 -1
- package/.output/public/assets/api-keys-api-BeaJnvY5.js +0 -1
- package/.output/public/assets/arrow-left-04Rh8Zgp.js +0 -1
- package/.output/public/assets/card-CYHs5FZv.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-DwyMsVFW.js +0 -1
- package/.output/public/assets/createServerFn-DU71xucx.js +0 -3
- package/.output/public/assets/dialog-IVMkBRCq.js +0 -1
- package/.output/public/assets/dist-BR4GzeEu.js +0 -1
- package/.output/public/assets/field-B3CYDpjg.js +0 -1
- package/.output/public/assets/index-Cb_D1D6N.js +0 -10
- package/.output/public/assets/info-C3y9HNoU.js +0 -1
- package/.output/public/assets/input-group-nLZylhWF.js +0 -1
- package/.output/public/assets/insights-Bm9v8uO3.js +0 -2
- package/.output/public/assets/insights-DS3iO7Up.js +0 -1
- package/.output/public/assets/insights-search-Byj3g3Mp.js +0 -1
- package/.output/public/assets/insights-usage-rpc-BaGR3lgq.js +0 -1
- package/.output/public/assets/insights_.distribution-B1i6cjlQ.js +0 -1
- package/.output/public/assets/insights_.distribution-C1Wf27eB.js +0 -2
- package/.output/public/assets/installations-CkFW4fV9.js +0 -2
- package/.output/public/assets/installations-DxTaIcHD.js +0 -1
- package/.output/public/assets/lazyRouteComponent-BapzjYnb.js +0 -1
- package/.output/public/assets/list-Dqt53fWZ.js +0 -1
- package/.output/public/assets/loader-circle-CUKfUqow.js +0 -1
- package/.output/public/assets/preload-helper-ClBMtM1g.js +0 -1
- package/.output/public/assets/refresh-cw-CNE_2PnD.js +0 -1
- package/.output/public/assets/routes-Cgtvspjg.js +0 -2
- package/.output/public/assets/routes-O2r2UUN8.js +0 -1
- package/.output/public/assets/shield-check-BFcJ46kz.js +0 -1
- package/.output/public/assets/signing-BgspC-xP.js +0 -1
- package/.output/public/assets/styles-DnrWuBwd.css +0 -2
- package/.output/public/assets/table-BuWQgxJJ.js +0 -1
- package/.output/public/assets/useMutation-CSV9NCUl.js +0 -1
- package/.output/public/assets/useOpenInteractionType-D5kduCTD.js +0 -1
- package/.output/public/assets/valueToPercent-BAtcbSLk.js +0 -1
- package/.output/server/__23tanstack-start-server-fn-resolver-DBRojHaG.mjs +0 -150
- package/.output/server/_ssr/InsightsPageHeader-mtxVuZKK.mjs +0 -64
- package/.output/server/_ssr/api-BfCgYlj5.mjs +0 -332
- package/.output/server/_ssr/api-keys-api-C3oeq01R.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-CdPFdGOx.mjs +0 -136
- package/.output/server/_ssr/api-rpc-B2E1ZIG5.mjs +0 -1399
- package/.output/server/_ssr/auth.server-CmicHh0J.mjs +0 -47
- package/.output/server/_ssr/bundle-signing-rpc-Di7-I6fE.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-Uqt1OF49.mjs +0 -12
- package/.output/server/_ssr/bundleActivity-pBrPSG01.mjs +0 -28
- package/.output/server/_ssr/card-Bemj0dpY.mjs +0 -108
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-B8WsT3s0.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-8X-jq7Z7.mjs +0 -66
- package/.output/server/_ssr/field-BrQ2u0fT.mjs +0 -74
- package/.output/server/_ssr/getBundleChildren-DoL8l_AS.mjs +0 -50
- package/.output/server/_ssr/insights-BwQ-wF2a.mjs +0 -10
- package/.output/server/_ssr/insights-gr1I_dJU.mjs +0 -938
- package/.output/server/_ssr/insights-recovery-Cv6RSyeR.mjs +0 -21
- package/.output/server/_ssr/insights-recovery-rpc-DOzxfYfv.mjs +0 -37
- package/.output/server/_ssr/insights-search-1czJMZgk.mjs +0 -21
- package/.output/server/_ssr/insights-usage-rpc-4ImOq_0p.mjs +0 -184
- package/.output/server/_ssr/insights-usage-rpc-BUruODjE.mjs +0 -24
- package/.output/server/_ssr/insightsContract-B09K_Px1.mjs +0 -303
- package/.output/server/_ssr/insightsHistory-Ci4rYAgl.mjs +0 -41
- package/.output/server/_ssr/insightsRecovery-CaghWL2t.mjs +0 -113
- package/.output/server/_ssr/insightsUsage-DNIj6jxF.mjs +0 -71
- package/.output/server/_ssr/insights_.distribution-VmHuikRE.mjs +0 -10
- package/.output/server/_ssr/installations-HXMF_kKf.mjs +0 -29
- package/.output/server/_ssr/routes-Dru3IRj0.mjs +0 -37
- package/.output/server/_ssr/runtime.server-CsiKFp_Z.mjs +0 -436
- package/.output/server/_ssr/server-DJm72Kj-.mjs +0 -7468
- package/.output/server/_ssr/signing-H8Lg4lPe.mjs +0 -133
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-DsFcTLw_.mjs +0 -125
- package/src/components/features/bundles/BundleActivityChart.tsx +0 -97
- package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -126
- 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/lib/server/insightsHistory.ts +0 -52
- package/src/lib/server/insightsRecovery.ts +0 -187
- package/src/routes/signing.tsx +0 -37
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InsightsFailureBreakdown,
|
|
3
|
+
InsightsUpdateFailures,
|
|
4
|
+
} from "@hot-updater/server/plugins";
|
|
5
|
+
|
|
6
|
+
import { recoveryWindows } from "./insights-recovery";
|
|
7
|
+
import type { InsightsWindow } from "./insights-rpc";
|
|
8
|
+
import type { InsightsEventRow } from "./insights-view";
|
|
9
|
+
|
|
10
|
+
export type { InsightsFailureBreakdown };
|
|
11
|
+
|
|
12
|
+
export type UpdateFailuresInput = {
|
|
13
|
+
readonly platform: "ios" | "android";
|
|
14
|
+
readonly channel: string;
|
|
15
|
+
/** A release's failures; the channel's without it. */
|
|
16
|
+
readonly releaseId?: string;
|
|
17
|
+
/** The period that ends with the current hour; a release's since its first report without it. */
|
|
18
|
+
readonly window?: InsightsWindow;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type UpdateFailuresReport = InsightsUpdateFailures & {
|
|
22
|
+
/** Where the period starts, or null since the release's first report. */
|
|
23
|
+
readonly startMs: number | null;
|
|
24
|
+
readonly endMs: number;
|
|
25
|
+
readonly previous?: {
|
|
26
|
+
readonly attemptRate: number | null;
|
|
27
|
+
readonly checkRate: number | null;
|
|
28
|
+
readonly complete: boolean;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function readUpdateFailuresInput(
|
|
33
|
+
input: UpdateFailuresInput,
|
|
34
|
+
): UpdateFailuresInput {
|
|
35
|
+
if (
|
|
36
|
+
!input ||
|
|
37
|
+
(input.platform !== "ios" && input.platform !== "android") ||
|
|
38
|
+
typeof input.channel !== "string" ||
|
|
39
|
+
!input.channel.trim() ||
|
|
40
|
+
input.channel.length > 1_024 ||
|
|
41
|
+
(input.releaseId !== undefined &&
|
|
42
|
+
(typeof input.releaseId !== "string" ||
|
|
43
|
+
!input.releaseId.trim() ||
|
|
44
|
+
input.releaseId.length > 36)) ||
|
|
45
|
+
(input.window === undefined
|
|
46
|
+
? input.releaseId === undefined
|
|
47
|
+
: !Object.hasOwn(recoveryWindows, input.window))
|
|
48
|
+
) {
|
|
49
|
+
throw new Error("Choose a platform, channel, and time window.");
|
|
50
|
+
}
|
|
51
|
+
return input;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The failure rate of update attempts: failed download and install reports,
|
|
56
|
+
* over those plus download reports. Null without either.
|
|
57
|
+
*/
|
|
58
|
+
export const failureRate = ({
|
|
59
|
+
failedUpdates,
|
|
60
|
+
downloads,
|
|
61
|
+
}: Pick<InsightsUpdateFailures, "failedUpdates" | "downloads">):
|
|
62
|
+
| number
|
|
63
|
+
| null =>
|
|
64
|
+
failedUpdates + downloads === 0
|
|
65
|
+
? null
|
|
66
|
+
: failedUpdates / (failedUpdates + downloads);
|
|
67
|
+
|
|
68
|
+
/** Installations whose update check failed, over the active ones; estimates, so at most 100%. */
|
|
69
|
+
export const checkFailureRate = ({
|
|
70
|
+
failedInstallations,
|
|
71
|
+
activeInstallations,
|
|
72
|
+
}: NonNullable<InsightsUpdateFailures["checks"]>): number | null =>
|
|
73
|
+
activeInstallations === 0
|
|
74
|
+
? null
|
|
75
|
+
: Math.min(1, failedInstallations / activeInstallations);
|
|
76
|
+
|
|
77
|
+
export const formatRate = (rate: number | null): string =>
|
|
78
|
+
rate === null ? "—" : `${(rate * 100).toFixed(2)}%`;
|
|
79
|
+
|
|
80
|
+
const STAGES: Readonly<Record<string, string>> = {
|
|
81
|
+
check: "Update check",
|
|
82
|
+
download: "Download",
|
|
83
|
+
install: "Install",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const REASONS: Readonly<Record<string, string>> = {
|
|
87
|
+
network: "Network",
|
|
88
|
+
http: "HTTP error",
|
|
89
|
+
invalid_response: "Invalid response",
|
|
90
|
+
hash_mismatch: "Hash mismatch",
|
|
91
|
+
signature: "Signature",
|
|
92
|
+
patch: "Patch",
|
|
93
|
+
extract: "Extract",
|
|
94
|
+
storage: "Storage",
|
|
95
|
+
unknown: "No reason reported",
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const failureStageLabel = (stage: string): string =>
|
|
99
|
+
STAGES[stage] ?? "Unknown stage";
|
|
100
|
+
|
|
101
|
+
export const failureReasonLabel = (reason: string): string =>
|
|
102
|
+
REASONS[reason] ?? "Unknown reason";
|
|
103
|
+
|
|
104
|
+
/** What else the client knew: its resource, HTTP status, origin code, and transport. */
|
|
105
|
+
export const failureDetailParts = (detail: {
|
|
106
|
+
readonly resource?: string | null;
|
|
107
|
+
readonly httpStatus?: number | null;
|
|
108
|
+
readonly originCode?: string | null;
|
|
109
|
+
readonly transport?: string | null;
|
|
110
|
+
}): string[] => [
|
|
111
|
+
...(detail.httpStatus == null ? [] : [`HTTP ${detail.httpStatus}`]),
|
|
112
|
+
...(detail.originCode == null ? [] : [detail.originCode]),
|
|
113
|
+
...(detail.transport == null ? [] : [detail.transport]),
|
|
114
|
+
...(detail.resource == null ? [] : [detail.resource]),
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
/** An update failure as one line: where, why, and what else the client knew. */
|
|
118
|
+
export const describeFailure = (
|
|
119
|
+
failure: NonNullable<InsightsEventRow["failure"]>,
|
|
120
|
+
): string => {
|
|
121
|
+
const message =
|
|
122
|
+
failure.errorMessage ||
|
|
123
|
+
(failure.reason === "unknown"
|
|
124
|
+
? "The client did not report a detailed cause"
|
|
125
|
+
: failureReasonLabel(failure.reason));
|
|
126
|
+
return [
|
|
127
|
+
`${failureStageLabel(failure.stage)} failed: ${message}`,
|
|
128
|
+
...failureDetailParts(failure),
|
|
129
|
+
].join(" · ");
|
|
130
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createServerFn } from "@tanstack/react-start";
|
|
2
2
|
|
|
3
3
|
import type { BundleActivityInput } from "./bundle-activity";
|
|
4
|
+
import { consoleAccess } from "./console-access";
|
|
4
5
|
import { readRecoveryInput } from "./insights-recovery";
|
|
5
6
|
|
|
6
7
|
export function readBundleActivityInput(
|
|
@@ -16,23 +17,18 @@ export function readBundleActivityInput(
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export const getBundleActivityRpc = createServerFn({ method: "GET" })
|
|
20
|
+
.middleware([consoleAccess])
|
|
19
21
|
.validator(readBundleActivityInput)
|
|
20
22
|
.handler(async ({ data }) => {
|
|
21
|
-
const [{ prepareConfig }, { getBundleActivity }] =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const [{ prepareConfig }, { getRecoveryReport }] = await Promise.all([
|
|
33
|
-
import("./server/config.server"),
|
|
34
|
-
import("./server/insightsRecovery"),
|
|
35
|
-
]);
|
|
36
|
-
const { config } = await prepareConfig();
|
|
37
|
-
return getRecoveryReport(config.database.models.insights, data);
|
|
23
|
+
const [{ prepareConfig }, { getBundleActivity }, { requireFeature }] =
|
|
24
|
+
await Promise.all([
|
|
25
|
+
import("./server/config.server"),
|
|
26
|
+
import("./server/bundleActivity"),
|
|
27
|
+
import("./server/runtime.server"),
|
|
28
|
+
]);
|
|
29
|
+
const { runtime } = await prepareConfig();
|
|
30
|
+
return getBundleActivity(
|
|
31
|
+
await requireFeature(runtime, "insightsAnalytics"),
|
|
32
|
+
data,
|
|
33
|
+
);
|
|
38
34
|
});
|
|
@@ -1,60 +1,36 @@
|
|
|
1
1
|
import type { InsightsWindow } from "./insights-rpc";
|
|
2
2
|
|
|
3
|
+
/** Release health's scope: its channel, platform, period, and focused release. */
|
|
3
4
|
export type RecoveryInput = {
|
|
4
|
-
readonly platform: "
|
|
5
|
+
readonly platform: "ios" | "android";
|
|
5
6
|
readonly channel: string;
|
|
6
|
-
readonly appVersion?: string;
|
|
7
7
|
readonly window: InsightsWindow;
|
|
8
8
|
readonly releaseId?: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export type RecoveryPoint = {
|
|
12
|
-
readonly startMs: number;
|
|
13
|
-
readonly active: number | null;
|
|
14
|
-
readonly recoveredInstallations: number;
|
|
15
|
-
readonly applied: number;
|
|
16
|
-
readonly recovered: number;
|
|
17
|
-
readonly rate: number | null;
|
|
18
|
-
readonly spike: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type RecoverySeries = {
|
|
22
|
-
readonly releaseId: string;
|
|
23
|
-
readonly firstAppliedAtMs: number | null;
|
|
24
|
-
readonly activeInstallations: number;
|
|
25
|
-
readonly recoveredInstallations: number;
|
|
26
|
-
readonly points: readonly RecoveryPoint[];
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type RecoveryReport = {
|
|
30
|
-
readonly sinceMs: number;
|
|
31
|
-
readonly beforeReceivedAtMs: number;
|
|
32
|
-
readonly intervalMs: number;
|
|
33
|
-
readonly truncated: boolean;
|
|
34
|
-
readonly unattributedInstallations: number;
|
|
35
|
-
readonly series: readonly RecoverySeries[];
|
|
36
|
-
};
|
|
37
|
-
|
|
38
11
|
export const recoveryWindows = {
|
|
39
12
|
"24h": { durationMs: 86_400_000, intervalMs: 3_600_000 },
|
|
40
13
|
"7d": { durationMs: 7 * 86_400_000, intervalMs: 6 * 3_600_000 },
|
|
41
14
|
"30d": { durationMs: 30 * 86_400_000, intervalMs: 86_400_000 },
|
|
42
15
|
} as const;
|
|
43
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Where a period ends: the end of the current UTC hour, as the server's
|
|
19
|
+
* reporting overview ends. The counters keep whole hours, so this is the
|
|
20
|
+
* latest end that still counts a report received just now; every Insights
|
|
21
|
+
* card ends here, so their numbers cover the same hours.
|
|
22
|
+
*/
|
|
23
|
+
export const insightsPeriodEnd = (now: number): number =>
|
|
24
|
+
Math.ceil(now / 3_600_000) * 3_600_000;
|
|
25
|
+
|
|
44
26
|
export function readRecoveryInput(input: RecoveryInput): RecoveryInput {
|
|
45
27
|
if (
|
|
46
28
|
!input ||
|
|
47
|
-
(input.platform !== "
|
|
48
|
-
input.platform !== "ios" &&
|
|
49
|
-
input.platform !== "android") ||
|
|
29
|
+
(input.platform !== "ios" && input.platform !== "android") ||
|
|
50
30
|
typeof input.channel !== "string" ||
|
|
51
31
|
!input.channel.trim() ||
|
|
52
32
|
input.channel.length > 1_024 ||
|
|
53
33
|
!Object.hasOwn(recoveryWindows, input.window) ||
|
|
54
|
-
(input.appVersion !== undefined &&
|
|
55
|
-
(typeof input.appVersion !== "string" ||
|
|
56
|
-
!input.appVersion.trim() ||
|
|
57
|
-
input.appVersion.length > 1_024)) ||
|
|
58
34
|
(input.releaseId !== undefined &&
|
|
59
35
|
(typeof input.releaseId !== "string" ||
|
|
60
36
|
!input.releaseId.trim() ||
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** How long the server's `insights()` keeps rows, in days. */
|
|
2
|
+
export interface InsightsRetentionDays {
|
|
3
|
+
/** Raw events and hourly totals. */
|
|
4
|
+
readonly rawDays: number;
|
|
5
|
+
/** Daily totals, distributions, and each installation's latest report. */
|
|
6
|
+
readonly dailyDays: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** `insights()`'s defaults, for a server that does not report its periods. */
|
|
10
|
+
export const DEFAULT_INSIGHTS_RETENTION: InsightsRetentionDays = {
|
|
11
|
+
rawDays: 90,
|
|
12
|
+
dailyDays: 400,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const formatDays = (days: number): string =>
|
|
16
|
+
`${days.toLocaleString("en")} ${days === 1 ? "day" : "days"}`;
|
|
17
|
+
|
|
18
|
+
/** Whether daily totals cover App usage's 12-month period: 52 weeks. */
|
|
19
|
+
export const keepsTwelveMonths = ({
|
|
20
|
+
dailyDays,
|
|
21
|
+
}: InsightsRetentionDays): boolean => dailyDays >= 364;
|
package/src/lib/insights-rpc.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
ReportingOverview,
|
|
3
2
|
InsightsScope,
|
|
3
|
+
ReportingOverview,
|
|
4
4
|
InsightsEventPageInput,
|
|
5
5
|
InsightsInstallationEventPageInput,
|
|
6
6
|
ActiveInstallationWindow,
|
|
7
|
-
|
|
8
|
-
} from "@hot-updater/server";
|
|
7
|
+
} from "@hot-updater/server/plugins";
|
|
9
8
|
import { createServerFn } from "@tanstack/react-start";
|
|
10
9
|
|
|
10
|
+
import { consoleAccess } from "./console-access";
|
|
11
11
|
import type {
|
|
12
12
|
InsightsInstallationViewRow,
|
|
13
13
|
InsightsViewPage,
|
|
@@ -30,10 +30,14 @@ type InstallationsPageInput = {
|
|
|
30
30
|
readonly limit: number;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const {
|
|
36
|
-
|
|
33
|
+
/** The Insights reads, once the console's access check and the feature guard pass. */
|
|
34
|
+
const insightsReads = async () => {
|
|
35
|
+
const [{ prepareConfig }, { requireFeature }] = await Promise.all([
|
|
36
|
+
import("./server/config.server"),
|
|
37
|
+
import("./server/runtime.server"),
|
|
38
|
+
]);
|
|
39
|
+
const { runtime } = await prepareConfig();
|
|
40
|
+
return requireFeature(runtime, "insights");
|
|
37
41
|
};
|
|
38
42
|
|
|
39
43
|
const readOverview = (input: InsightsOverviewInput) => input;
|
|
@@ -44,27 +48,41 @@ const readInstallationPage = (input: InstallationsPageInput) => input;
|
|
|
44
48
|
const readInstallId = (input: { readonly installId: string }) => input;
|
|
45
49
|
|
|
46
50
|
export const getReportingInstallationsRpc = createServerFn({ method: "GET" })
|
|
51
|
+
.middleware([consoleAccess])
|
|
47
52
|
.validator(readOverview)
|
|
48
|
-
.handler(async ({ data }) =>
|
|
53
|
+
.handler(async ({ data }) =>
|
|
54
|
+
(await insightsReads()).getReportingOverview(data),
|
|
55
|
+
);
|
|
49
56
|
|
|
50
57
|
export const listInsightsEventsRpc = createServerFn({ method: "GET" })
|
|
58
|
+
.middleware([consoleAccess])
|
|
51
59
|
.validator(readEventsPage)
|
|
52
|
-
.handler(async ({ data }) => (await
|
|
60
|
+
.handler(async ({ data }) => (await insightsReads()).listEvents(data));
|
|
53
61
|
|
|
54
62
|
export const listInsightsInstallationEventsRpc = createServerFn({
|
|
55
63
|
method: "GET",
|
|
56
64
|
})
|
|
65
|
+
.middleware([consoleAccess])
|
|
57
66
|
.validator(readInstallationEventsPage)
|
|
58
|
-
.handler(async ({ data }) =>
|
|
67
|
+
.handler(async ({ data }) =>
|
|
68
|
+
(await insightsReads()).listInstallationEvents(data),
|
|
69
|
+
);
|
|
59
70
|
|
|
60
71
|
export const getInsightsInstallationRpc = createServerFn({ method: "GET" })
|
|
72
|
+
.middleware([consoleAccess])
|
|
61
73
|
.validator(readInstallId)
|
|
62
|
-
.handler(async ({ data }) => (await
|
|
74
|
+
.handler(async ({ data }) => (await insightsReads()).getInstallation(data));
|
|
75
|
+
|
|
76
|
+
/** How long the server's `insights()` keeps rows. */
|
|
77
|
+
export const getInsightsRetentionRpc = createServerFn({ method: "GET" })
|
|
78
|
+
.middleware([consoleAccess])
|
|
79
|
+
.handler(async () => (await insightsReads()).getRetention());
|
|
63
80
|
|
|
64
81
|
export const findInsightsInstallationsRpc = createServerFn({ method: "GET" })
|
|
82
|
+
.middleware([consoleAccess])
|
|
65
83
|
.validator(readInstallationPage)
|
|
66
84
|
.handler(async ({ data }) => {
|
|
67
|
-
const insights = await
|
|
85
|
+
const insights = await insightsReads();
|
|
68
86
|
const install = data.cursor
|
|
69
87
|
? null
|
|
70
88
|
: await insights.getInstallation({ installId: data.identity });
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { extractTimestampFromUUIDv7 } from "@hot-updater/plugin-core";
|
|
2
|
+
import { isUUIDv7 } from "@hot-updater/protocol";
|
|
3
|
+
|
|
1
4
|
import type { InsightsWindow } from "./insights-api";
|
|
2
|
-
import type {
|
|
5
|
+
import type { AppUsageScope, UsageWindow } from "./insights-usage";
|
|
3
6
|
|
|
4
7
|
const readText = (value: unknown) =>
|
|
5
8
|
typeof value === "string" && value.trim().length > 0 && value.length <= 1024
|
|
@@ -7,13 +10,28 @@ const readText = (value: unknown) =>
|
|
|
7
10
|
: undefined;
|
|
8
11
|
const readWindow = (value: unknown): InsightsWindow | undefined =>
|
|
9
12
|
value === "24h" || value === "7d" || value === "30d" ? value : undefined;
|
|
13
|
+
/** Release health's chart: whether bundles are launched, or crash. */
|
|
14
|
+
export type HealthChart = "adoption" | "crashes";
|
|
15
|
+
const readHealthChart = (value: unknown): HealthChart | undefined =>
|
|
16
|
+
value === "adoption" || value === "crashes" ? value : undefined;
|
|
17
|
+
const readUsageWindow = (value: unknown): UsageWindow | undefined =>
|
|
18
|
+
value === "12m" ? value : readWindow(value);
|
|
10
19
|
|
|
11
20
|
export type InsightsSearch = {
|
|
12
|
-
platform?:
|
|
21
|
+
platform?: AppUsageScope["platform"];
|
|
13
22
|
channel?: string;
|
|
14
23
|
appVersion?: string;
|
|
15
|
-
window?:
|
|
24
|
+
window?: UsageWindow;
|
|
16
25
|
bundleWindow?: InsightsWindow;
|
|
26
|
+
healthPlatform?: "ios" | "android";
|
|
27
|
+
healthChannel?: string;
|
|
28
|
+
releaseId?: string;
|
|
29
|
+
healthChart?: HealthChart;
|
|
30
|
+
/**
|
|
31
|
+
* Release health's compared bundles, comma-separated release IDs; omitted,
|
|
32
|
+
* the focused release and the one before it, or the newest two.
|
|
33
|
+
*/
|
|
34
|
+
bundles?: string;
|
|
17
35
|
};
|
|
18
36
|
|
|
19
37
|
export function validateInsightsSearch(
|
|
@@ -28,8 +46,16 @@ export function validateInsightsSearch(
|
|
|
28
46
|
: undefined,
|
|
29
47
|
channel: readText(search.channel),
|
|
30
48
|
appVersion: readText(search.appVersion),
|
|
31
|
-
window:
|
|
49
|
+
window: readUsageWindow(search.window),
|
|
32
50
|
bundleWindow: readWindow(search.bundleWindow),
|
|
51
|
+
healthPlatform:
|
|
52
|
+
search.healthPlatform === "ios" || search.healthPlatform === "android"
|
|
53
|
+
? search.healthPlatform
|
|
54
|
+
: undefined,
|
|
55
|
+
healthChannel: readText(search.healthChannel),
|
|
56
|
+
releaseId: readText(search.releaseId),
|
|
57
|
+
healthChart: readHealthChart(search.healthChart),
|
|
58
|
+
bundles: readText(search.bundles),
|
|
33
59
|
};
|
|
34
60
|
}
|
|
35
61
|
|
|
@@ -45,3 +71,23 @@ export function validateDistributionSearch(search: Record<string, unknown>) {
|
|
|
45
71
|
: undefined,
|
|
46
72
|
};
|
|
47
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The shortest Release health period that still covers a release's first
|
|
77
|
+
* hours: 24h within a day of its deployment, 7d within a week, else 30d.
|
|
78
|
+
*/
|
|
79
|
+
export function adoptionWindow(releaseId: string, now = Date.now()) {
|
|
80
|
+
if (!isUUIDv7(releaseId)) return "7d" as const;
|
|
81
|
+
const age = now - extractTimestampFromUUIDv7(releaseId);
|
|
82
|
+
return age <= 86_400_000
|
|
83
|
+
? ("24h" as const)
|
|
84
|
+
: age <= 7 * 86_400_000
|
|
85
|
+
? ("7d" as const)
|
|
86
|
+
: ("30d" as const);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Release health's compared bundles from the `bundles` search value. */
|
|
90
|
+
export const comparedBundleIds = (value: string | undefined) =>
|
|
91
|
+
value === undefined
|
|
92
|
+
? undefined
|
|
93
|
+
: [...new Set(value.split(",").filter((id) => id.length > 0))];
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createServerFn } from "@tanstack/react-start";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import type
|
|
3
|
+
import { consoleAccess } from "./console-access";
|
|
4
|
+
import { readAppUsageInput, type AppUsageInput } from "./insights-usage";
|
|
5
5
|
|
|
6
6
|
export const getAppUsageReportRpc = createServerFn({ method: "GET" })
|
|
7
|
+
.middleware([consoleAccess])
|
|
7
8
|
.validator((input: AppUsageInput) => {
|
|
8
|
-
|
|
9
|
+
readAppUsageInput(input);
|
|
9
10
|
return {
|
|
10
11
|
platform: input.platform,
|
|
11
12
|
channel: input.channel,
|
|
@@ -14,10 +15,15 @@ export const getAppUsageReportRpc = createServerFn({ method: "GET" })
|
|
|
14
15
|
};
|
|
15
16
|
})
|
|
16
17
|
.handler(async ({ data }) => {
|
|
17
|
-
const [{ prepareConfig }, { getAppUsageReport }] =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
const [{ prepareConfig }, { getAppUsageReport }, { requireFeature }] =
|
|
19
|
+
await Promise.all([
|
|
20
|
+
import("./server/config.server"),
|
|
21
|
+
import("./server/insightsUsage"),
|
|
22
|
+
import("./server/runtime.server"),
|
|
23
|
+
]);
|
|
24
|
+
const { runtime } = await prepareConfig();
|
|
25
|
+
return getAppUsageReport(
|
|
26
|
+
await requireFeature(runtime, "insightsAnalytics"),
|
|
27
|
+
data,
|
|
28
|
+
);
|
|
23
29
|
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export type AppUsageInput = {
|
|
2
|
+
readonly platform: "all" | "ios" | "android";
|
|
3
|
+
readonly channel: string;
|
|
4
|
+
readonly appVersion?: string;
|
|
5
|
+
readonly window: UsageWindow;
|
|
6
|
+
};
|
|
4
7
|
export type AppUsageScope = Omit<AppUsageInput, "window">;
|
|
5
8
|
|
|
6
9
|
export type UsageDistribution = {
|
|
@@ -12,6 +15,8 @@ export type BundleDistribution = {
|
|
|
12
15
|
readonly appVersion: string;
|
|
13
16
|
readonly platform: "ios" | "android";
|
|
14
17
|
readonly releaseId: string | null;
|
|
18
|
+
/** The native build's built-in bundle, when the installations run it. */
|
|
19
|
+
readonly builtinBundleId: string | null;
|
|
15
20
|
readonly installations: number;
|
|
16
21
|
};
|
|
17
22
|
|
|
@@ -19,6 +24,12 @@ export type AppUsageReport = {
|
|
|
19
24
|
readonly bundleDistribution: readonly BundleDistribution[];
|
|
20
25
|
readonly activeInstallations: number;
|
|
21
26
|
readonly sinceMs: number;
|
|
27
|
+
/**
|
|
28
|
+
* Where the distribution starts: the start of the UTC day that contains the
|
|
29
|
+
* period's start. Latest reports are counted by UTC day, so the
|
|
30
|
+
* distribution covers every UTC day the period touches.
|
|
31
|
+
*/
|
|
32
|
+
readonly distributionSinceMs: number;
|
|
22
33
|
readonly beforeReceivedAtMs: number;
|
|
23
34
|
readonly intervalMs: number;
|
|
24
35
|
readonly truncated: boolean;
|
|
@@ -35,4 +46,31 @@ export const usageMetrics = {
|
|
|
35
46
|
"24h": { label: "DAU", period: "24 hours", interval: "hour" },
|
|
36
47
|
"7d": { label: "WAU", period: "7 days", interval: "6 hours" },
|
|
37
48
|
"30d": { label: "MAU", period: "30 days", interval: "day" },
|
|
49
|
+
"12m": { label: "YAU", period: "12 months", interval: "week" },
|
|
38
50
|
} as const;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* An App usage period. Hourly counts are kept 90 days, so the 12-month
|
|
54
|
+
* period counts whole UTC days, from the daily counts kept 13 months.
|
|
55
|
+
*/
|
|
56
|
+
export type UsageWindow = keyof typeof usageMetrics;
|
|
57
|
+
|
|
58
|
+
export function readAppUsageInput(input: AppUsageInput): AppUsageInput {
|
|
59
|
+
if (
|
|
60
|
+
!input ||
|
|
61
|
+
(input.platform !== "all" &&
|
|
62
|
+
input.platform !== "ios" &&
|
|
63
|
+
input.platform !== "android") ||
|
|
64
|
+
typeof input.channel !== "string" ||
|
|
65
|
+
!input.channel.trim() ||
|
|
66
|
+
input.channel.length > 1_024 ||
|
|
67
|
+
!Object.hasOwn(usageMetrics, input.window) ||
|
|
68
|
+
(input.appVersion !== undefined &&
|
|
69
|
+
(typeof input.appVersion !== "string" ||
|
|
70
|
+
!input.appVersion.trim() ||
|
|
71
|
+
input.appVersion.length > 1_024))
|
|
72
|
+
) {
|
|
73
|
+
throw new Error("Choose App usage filters and a time window.");
|
|
74
|
+
}
|
|
75
|
+
return input;
|
|
76
|
+
}
|
package/src/lib/insights-view.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
EventHistoryRow,
|
|
3
|
+
InstallationRow,
|
|
4
|
+
} from "@hot-updater/server/plugins";
|
|
2
5
|
|
|
3
6
|
export type InsightsEventRow = EventHistoryRow;
|
|
4
7
|
export type InsightsInstallationViewRow = InstallationRow;
|
|
@@ -8,8 +11,34 @@ export type InsightsViewPage<TRow> = {
|
|
|
8
11
|
readonly nextCursor: string | null;
|
|
9
12
|
};
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
const DAY_MS = 86_400_000;
|
|
15
|
+
|
|
16
|
+
/** The time ranges an event list reads, shortest first. */
|
|
17
|
+
export const eventRanges = {
|
|
18
|
+
"24h": { period: "24 hours", durationMs: DAY_MS },
|
|
19
|
+
"7d": { period: "7 days", durationMs: 7 * DAY_MS },
|
|
20
|
+
"30d": { period: "30 days", durationMs: 30 * DAY_MS },
|
|
21
|
+
"90d": { period: "90 days", durationMs: 90 * DAY_MS },
|
|
15
22
|
} as const;
|
|
23
|
+
|
|
24
|
+
export type EventRange = keyof typeof eventRanges;
|
|
25
|
+
|
|
26
|
+
export const EVENT_RANGES = Object.keys(eventRanges) as readonly EventRange[];
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_EVENT_RANGE: EventRange = "7d";
|
|
29
|
+
|
|
30
|
+
/** The next longer range, or undefined for the longest. */
|
|
31
|
+
export const widerEventRange = (range: EventRange): EventRange | undefined =>
|
|
32
|
+
EVENT_RANGES[EVENT_RANGES.indexOf(range) + 1];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The receipt interval [sinceMs, beforeReceivedAtMs) a range covers. The end
|
|
36
|
+
* stays fixed while the list pages, so every page reads the same interval.
|
|
37
|
+
*/
|
|
38
|
+
export const eventRangeBounds = (
|
|
39
|
+
range: EventRange,
|
|
40
|
+
beforeReceivedAtMs: number,
|
|
41
|
+
) => ({
|
|
42
|
+
beforeReceivedAtMs,
|
|
43
|
+
sinceMs: Math.max(0, beforeReceivedAtMs - eventRanges[range].durationMs),
|
|
44
|
+
});
|