@hot-updater/console 1.0.0-rc.4 → 1.0.0-rc.41

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.
Files changed (330) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-DRzeMGRM.js +1 -0
  3. package/.output/public/assets/EventDetails-SB5ygJzS.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-btYppp0B.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-Ba8psD8t.js +1 -0
  7. package/.output/public/assets/NotFoundPage-CyRG2eca.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DUgI6yOI.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-CmkNKOhS.js +1 -0
  12. package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
  13. package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
  14. package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
  15. package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
  16. package/.output/public/assets/console-features-api-BMsI3Bj8.js +1 -0
  17. package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
  18. package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
  19. package/.output/public/assets/empty-CuT2UhFp.js +1 -0
  20. package/.output/public/assets/field-BBDpjIqu.js +1 -0
  21. package/.output/public/assets/index-BEfQ2_7f.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-B0ib9qOE.js +2 -0
  24. package/.output/public/assets/insights-BIMJx-0p.js +1 -0
  25. package/.output/public/assets/insights-CndqcWEj.js +42 -0
  26. package/.output/public/assets/insights-api-B6Ewc-OG.js +1 -0
  27. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  28. package/.output/public/assets/insights_.distribution-B26PMy4k.js +2 -0
  29. package/.output/public/assets/insights_.distribution-BBDtqqYS.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DhHmYS8k.js +1 -0
  31. package/.output/public/assets/installations-C3cMuwRx.js +1 -0
  32. package/.output/public/assets/installations-C3yrjwDh.js +2 -0
  33. package/.output/public/assets/installations-D3edlN75.js +1 -0
  34. package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
  35. package/.output/public/assets/list-CgrYiadM.js +1 -0
  36. package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
  37. package/.output/public/assets/remote-config-0i-gDkeU.js +1 -0
  38. package/.output/public/assets/remote-config-An1ROIOQ.js +6 -0
  39. package/.output/public/assets/remote-config-CoeWgA9i.js +1 -0
  40. package/.output/public/assets/routes-DNoaJIQV.js +2 -0
  41. package/.output/public/assets/routes-cWBDtghZ.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-CZMQKT7F.js +1 -0
  44. package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
  45. package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
  46. package/.output/public/assets/styles-D8mxMvCB.css +2 -0
  47. package/.output/public/assets/switch-CU4-fupc.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-R1I8CRjR.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-BaPeC9ZO.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-DTAqlLRd.mjs +178 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +6450 -2741
  55. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
  56. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
  57. package/.output/server/_libs/lucide-react.mjs +235 -31
  58. package/.output/server/_libs/next-themes.mjs +1 -1
  59. package/.output/server/_libs/react-redux+[...].mjs +1 -1
  60. package/.output/server/_libs/recharts+[...].mjs +483 -901
  61. package/.output/server/_libs/sonner.mjs +1 -1
  62. package/.output/server/_libs/tanstack__history.mjs +68 -41
  63. package/.output/server/_libs/tanstack__query-core.mjs +2010 -309
  64. package/.output/server/_libs/tanstack__react-query.mjs +448 -21
  65. package/.output/server/_libs/verkit.mjs +102 -1
  66. package/.output/server/_ssr/BundleIdDisplay-BNoVW3Dv.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-BPFa-cmJ.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-9CeshfdR.mjs +344 -0
  69. package/.output/server/_ssr/HashValueDisplay-DXZlkcps.mjs +55 -0
  70. package/.output/server/_ssr/InsightsInfo-DLNdJQ0m.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-DZGVDgvv.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-mYHHg-2s.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-DUaIgpgd.mjs → alert-dialog-Chkim7vZ.mjs} +6 -83
  74. package/.output/server/_ssr/api-C-Il5-sa.mjs +203 -0
  75. package/.output/server/_ssr/api-keys-01FGqj4d.mjs +5 -0
  76. package/.output/server/_ssr/{api-keys-DJml_XoK.mjs → api-keys-B_f9h7ln.mjs} +48 -10
  77. package/.output/server/_ssr/api-keys-rpc-Cf4YCyFc.mjs +52 -0
  78. package/.output/server/_ssr/api-rpc-DRqwxxT_.mjs +296 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-D_hv52-J.mjs → auth-rpc-D8nFpU9C.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-COZoXu4i.mjs +5425 -0
  82. package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
  83. package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
  84. package/.output/server/_ssr/config.server-Dm2l7w0O.mjs +36 -0
  85. package/.output/server/_ssr/console-access--o9FXikU.mjs +17 -0
  86. package/.output/server/_ssr/console-features-BftSkf3f.mjs +60 -0
  87. package/.output/server/_ssr/console-features-api-CYclUQ4h.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-1WaAkPM_.mjs +19 -0
  89. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
  90. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
  91. package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
  92. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
  93. package/.output/server/_ssr/databaseRows-BUuNU-qt.mjs +74 -0
  94. package/.output/server/_ssr/dialog-mfSANqjC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-wmnTk7w6.mjs} +140 -6
  96. package/.output/server/_ssr/{downloadBundle-B3ert-pY.mjs → downloadBundle-DViuIa9U.mjs} +10 -10
  97. package/.output/server/_ssr/empty-BPayFad3.mjs +60 -0
  98. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
  99. package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
  100. package/.output/server/_ssr/field-DjZ5t7rH.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-CmjDg6Re.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-Bb8ueylQ.mjs → input-group-Dx-MEkNM.mjs} +3 -52
  103. package/.output/server/_ssr/insights-BopxyJjk.mjs +14 -0
  104. package/.output/server/_ssr/insights-Bvm03d61.mjs +5 -0
  105. package/.output/server/_ssr/insights-CrLAHSDy.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-Bg8ZV_h_.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-tyy5m9Cc.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/{insights-recovery-DSFM-YTL.mjs → insights-recovery-BWQhiZaF.mjs} +9 -2
  111. package/.output/server/_ssr/insights-recovery-rpc-CPJOiJAV.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-DrIQCjpD.mjs → insights-rpc-DoC3hla9.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-CFuTYp3T.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-O2ruP4iE.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-B3BVPD00.mjs +16 -0
  118. package/.output/server/_ssr/insights_.distribution-BMoisxRG.mjs +347 -0
  119. package/.output/server/_ssr/insights_.distribution-BiCcFyrG.mjs +7 -0
  120. package/.output/server/_ssr/{installations-YumqpNpa.mjs → installations-Cx3jIrwl.mjs} +280 -364
  121. package/.output/server/_ssr/installations-DTGPaKXx.mjs +69 -0
  122. package/.output/server/_ssr/installations-osiAuNKr.mjs +5 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  125. package/.output/server/_ssr/release-adoption-rpc-BVySDR0L.mjs +41 -0
  126. package/.output/server/_ssr/releaseAdoption-2PJPA09N.mjs +114 -0
  127. package/.output/server/_ssr/releaseManagement-BTCWEY8-.mjs +11 -0
  128. package/.output/server/_ssr/remote-config-CUoAxUMy.mjs +430 -0
  129. package/.output/server/_ssr/remote-config-Cc5P4J-r.mjs +17 -0
  130. package/.output/server/_ssr/remote-config-CjdxcZ9B.mjs +5 -0
  131. package/.output/server/_ssr/remote-config-D2GmbGwI.mjs +2594 -0
  132. package/.output/server/_ssr/remote-config-rpc-kjoZ9PRS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-iP95Vqi2.mjs → router-DwGVewL-.mjs} +141 -139
  135. package/.output/server/_ssr/{routes-ivsV2TDu.mjs → routes-C4yxv3JU.mjs} +192 -685
  136. package/.output/server/_ssr/routes-D3_aegLY.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-BoWf4QhU.mjs +1716 -0
  138. package/.output/server/_ssr/schema-DrOtkRVD.mjs +361 -0
  139. package/.output/server/_ssr/select-DkzM2miy.mjs +107 -0
  140. package/.output/server/_ssr/server-BW0gzB0p.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-NHF2PxuK.mjs → sidebar-DPcOijvL.mjs} +107 -47
  142. package/.output/server/_ssr/ssr.mjs +1 -1
  143. package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
  144. package/.output/server/_ssr/switch-BqJ2mAwC.mjs +17 -0
  145. package/.output/server/_ssr/{table-D1mL8fJu.mjs → table-R7RY92q9.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-trDcbRkT.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-ctA_XVZA.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-BwInaUTw.mjs +139 -0
  150. package/.output/server/index.mjs +315 -154
  151. package/LICENSE +13 -14
  152. package/README.md +82 -72
  153. package/lib/index.d.mts +23 -11
  154. package/lib/vite-internal.mjs +13 -3
  155. package/lib/vite.d.mts +2 -0
  156. package/package.json +14 -14
  157. package/src/components/AppSidebar.tsx +100 -71
  158. package/src/components/ConsoleAccessPage.tsx +5 -1
  159. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  160. package/src/components/NotFoundPage.tsx +7 -7
  161. package/src/components/features/FeatureSlots.tsx +103 -0
  162. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  163. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  164. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  165. package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
  166. package/src/components/features/insights/AppUsage.tsx +366 -0
  167. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  168. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  169. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  170. package/src/components/features/insights/EventDetails.tsx +400 -39
  171. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  172. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  173. package/src/components/features/insights/FailureReports.tsx +407 -0
  174. package/src/components/features/insights/InsightsControls.tsx +265 -88
  175. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  176. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  177. package/src/components/features/insights/InsightsOverview.tsx +604 -355
  178. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  179. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  180. package/src/components/features/insights/InstallationHistoryCard.tsx +66 -11
  181. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  182. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  183. package/src/components/features/insights/ReportingDevicesSummary.tsx +48 -0
  184. package/src/components/features/insights/RollbackButton.tsx +118 -0
  185. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  186. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  187. package/src/components/features/releases/ReleaseEditorSheet.tsx +26 -12
  188. package/src/components/features/remote-config/ConditionDialog.tsx +581 -0
  189. package/src/components/features/remote-config/ConditionsCard.tsx +244 -0
  190. package/src/components/features/remote-config/ParameterDialog.tsx +407 -0
  191. package/src/components/features/remote-config/ParametersCard.tsx +244 -0
  192. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  193. package/src/components/features/remote-config/PublishDialog.tsx +319 -0
  194. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  195. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  196. package/src/components/features/remote-config/ValueInput.tsx +148 -0
  197. package/src/components/features/remote-config/VersionsCard.tsx +361 -0
  198. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  199. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  200. package/src/components/ui/card.tsx +1 -1
  201. package/src/components/ui/scroll-area.tsx +52 -0
  202. package/src/components/ui/sidebar.tsx +22 -29
  203. package/src/components/ui/tabs.tsx +81 -0
  204. package/src/index.ts +26 -19
  205. package/src/lib/api-keys-api.ts +0 -28
  206. package/src/lib/api-keys-rpc.ts +27 -38
  207. package/src/lib/api-rpc.ts +189 -266
  208. package/src/lib/api.ts +19 -102
  209. package/src/lib/bundle-activity.ts +24 -4
  210. package/src/lib/console-access.ts +20 -0
  211. package/src/lib/console-features-api.ts +68 -0
  212. package/src/lib/console-features-rpc.ts +13 -0
  213. package/src/lib/console-features.ts +97 -0
  214. package/src/lib/insights-api.ts +56 -2
  215. package/src/lib/insights-errors.ts +64 -0
  216. package/src/lib/insights-failures-rpc.ts +36 -0
  217. package/src/lib/insights-failures.ts +130 -0
  218. package/src/lib/insights-recovery-rpc.ts +13 -17
  219. package/src/lib/insights-recovery.ts +10 -27
  220. package/src/lib/insights-retention.ts +21 -0
  221. package/src/lib/insights-rpc.ts +30 -12
  222. package/src/lib/insights-search.ts +93 -0
  223. package/src/lib/insights-usage-rpc.ts +29 -0
  224. package/src/lib/insights-usage.ts +76 -0
  225. package/src/lib/insights-view.ts +34 -5
  226. package/src/lib/release-adoption-api.ts +214 -0
  227. package/src/lib/release-adoption-rpc.ts +49 -0
  228. package/src/lib/release-adoption.ts +164 -0
  229. package/src/lib/remote-config-api.ts +119 -0
  230. package/src/lib/remote-config-draft.ts +652 -0
  231. package/src/lib/remote-config-rpc.ts +196 -0
  232. package/src/lib/scroll-restoration.ts +21 -0
  233. package/src/lib/server/adminInsights.ts +124 -0
  234. package/src/lib/server/auth.server.ts +13 -5
  235. package/src/lib/server/bundleActivity.ts +27 -28
  236. package/src/lib/server/config.server.ts +21 -74
  237. package/src/lib/server/console-feature-apis.ts +70 -0
  238. package/src/lib/server/console-runtime.server.ts +82 -38
  239. package/src/lib/server/downloadBundle.ts +16 -15
  240. package/src/lib/server/failureReports.ts +49 -0
  241. package/src/lib/server/getBundleChildren.ts +31 -122
  242. package/src/lib/server/insightsUsage.ts +63 -0
  243. package/src/lib/server/listReleases.ts +109 -44
  244. package/src/lib/server/releaseAdoption.ts +154 -0
  245. package/src/lib/server/releaseReachability.ts +8 -7
  246. package/src/lib/server/remoteConfig.ts +191 -0
  247. package/src/lib/server/runtime.server.ts +120 -31
  248. package/src/lib/server/updateFailures.ts +65 -0
  249. package/src/routeTree.gen.ts +65 -44
  250. package/src/router.tsx +3 -2
  251. package/src/routes/-installations-search.ts +32 -8
  252. package/src/routes/-releases-search.ts +76 -29
  253. package/src/routes/__root.tsx +10 -4
  254. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  255. package/src/routes/api-keys.tsx +5 -2
  256. package/src/routes/index.tsx +210 -152
  257. package/src/routes/insights.tsx +133 -18
  258. package/src/routes/insights_.distribution.tsx +422 -0
  259. package/src/routes/installations.tsx +98 -41
  260. package/src/routes/remote-config.tsx +42 -0
  261. package/src/styles.css +9 -0
  262. package/src/vite-internal.ts +15 -3
  263. package/src/vite.ts +2 -0
  264. package/.output/public/assets/InsightsPageHeader-C-Xc2MB9.js +0 -1
  265. package/.output/public/assets/api-keys-C1sgC6ko.js +0 -1
  266. package/.output/public/assets/api-keys-api-LchP65vQ.js +0 -1
  267. package/.output/public/assets/badge-BuMDU0Rf.js +0 -1
  268. package/.output/public/assets/card-BEvx_QKh.js +0 -1
  269. package/.output/public/assets/chart-no-axes-combined-C0G0fQ3J.js +0 -1
  270. package/.output/public/assets/composite-CHTH1Xhh.js +0 -1
  271. package/.output/public/assets/createServerFn-DHc2erub.js +0 -3
  272. package/.output/public/assets/dialog-dyZ1SphZ.js +0 -1
  273. package/.output/public/assets/dist-Bh1gaDfB.js +0 -1
  274. package/.output/public/assets/field-79SVH26s.js +0 -1
  275. package/.output/public/assets/index-BNkxg4jz.js +0 -10
  276. package/.output/public/assets/input-group-BbHfaBzQ.js +0 -1
  277. package/.output/public/assets/insights-D_7uSKfc.js +0 -1
  278. package/.output/public/assets/installations-BzoHWuXw.js +0 -1
  279. package/.output/public/assets/installations-DvjbUqd1.js +0 -2
  280. package/.output/public/assets/lazyRouteComponent-BN0MS05o.js +0 -1
  281. package/.output/public/assets/link-BG06whwB.js +0 -1
  282. package/.output/public/assets/loader-circle-Bbp8LGVC.js +0 -1
  283. package/.output/public/assets/preload-helper-uTix4PVD.js +0 -1
  284. package/.output/public/assets/refresh-cw-DZgiAOMk.js +0 -1
  285. package/.output/public/assets/routes-BFE-oRIT.js +0 -1
  286. package/.output/public/assets/routes-DaPMY31P.js +0 -2
  287. package/.output/public/assets/shield-check-DoIiaaXU.js +0 -1
  288. package/.output/public/assets/signing-BTRhaF5D.js +0 -1
  289. package/.output/public/assets/styles-C7U0oVke.css +0 -2
  290. package/.output/public/assets/table-BBEzJ7Xl.js +0 -1
  291. package/.output/public/assets/useCompositeListItem-BkPDaqyj.js +0 -42
  292. package/.output/server/__23tanstack-start-server-fn-resolver-Dr2ixDgQ.mjs +0 -146
  293. package/.output/server/_ssr/InsightsPageHeader-C5Has8Ry.mjs +0 -60
  294. package/.output/server/_ssr/api-keys-api-CrCZyvnl.mjs +0 -60
  295. package/.output/server/_ssr/api-keys-rpc-CFaNf-q0.mjs +0 -136
  296. package/.output/server/_ssr/api-rpc-B61KL3Eo.mjs +0 -1399
  297. package/.output/server/_ssr/auth.server-DF_shCeq.mjs +0 -47
  298. package/.output/server/_ssr/badge-BgsCP9Dp.mjs +0 -28
  299. package/.output/server/_ssr/bundle-signing-rpc-BBipZnFs.mjs +0 -16
  300. package/.output/server/_ssr/bundle-signing.server-Co_zHdJx.mjs +0 -12
  301. package/.output/server/_ssr/bundleActivity-D3_fIda2.mjs +0 -27
  302. package/.output/server/_ssr/card-DrbxCPtG.mjs +0 -86
  303. package/.output/server/_ssr/chart-qxa-_30a.mjs +0 -162
  304. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  305. package/.output/server/_ssr/config.server-CBr6PDj5.mjs +0 -368
  306. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  307. package/.output/server/_ssr/deleteBundle-BAq3JuGr.mjs +0 -66
  308. package/.output/server/_ssr/field-CqNUlqUq.mjs +0 -74
  309. package/.output/server/_ssr/getBundleChildren-COMt16m_.mjs +0 -50
  310. package/.output/server/_ssr/insights-B-DUbksX.mjs +0 -502
  311. package/.output/server/_ssr/insights-recovery-rpc-P71FH66n.mjs +0 -37
  312. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  313. package/.output/server/_ssr/insightsRecovery-CqcosG7B.mjs +0 -149
  314. package/.output/server/_ssr/installations-BQrppti7.mjs +0 -29
  315. package/.output/server/_ssr/routes-BDabsmc7.mjs +0 -37
  316. package/.output/server/_ssr/runtime.server-jmZOKIQM.mjs +0 -438
  317. package/.output/server/_ssr/server-p7yrVBfD.mjs +0 -7468
  318. package/.output/server/_ssr/signing-CxCbnFCt.mjs +0 -134
  319. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  320. package/.output/server/_tanstack-start-manifest_v-Cwpu0C9Q.mjs +0 -100
  321. package/src/components/features/bundles/BundleActivityChart.tsx +0 -95
  322. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -121
  323. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  324. package/src/lib/bundle-signing-api.ts +0 -14
  325. package/src/lib/bundle-signing-rpc.ts +0 -16
  326. package/src/lib/server/bundle-signing.server.ts +0 -23
  327. package/src/lib/server/deleteBundle.ts +0 -145
  328. package/src/lib/server/insightsHistory.ts +0 -52
  329. package/src/lib/server/insightsRecovery.ts +0 -186
  330. package/src/routes/signing.tsx +0 -37
@@ -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-1">
23
- <Link
24
- aria-current={view === "overview" ? "page" : undefined}
25
- className={buttonVariants({
26
- className: "h-11 px-3 lg:h-8 lg:px-2.5",
27
- size: "lg",
28
- variant: view === "overview" ? "secondary" : "ghost",
29
- })}
30
- to="/insights"
31
- >
32
- Overview
33
- </Link>
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={buttonVariants({
37
- className: "h-11 px-3 lg:h-8 lg:px-2.5",
38
- size: "lg",
39
- variant: view === "events" ? "secondary" : "ghost",
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
- Events
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
+ }
@@ -2,7 +2,13 @@ import { RefreshCw } from "lucide-react";
2
2
 
3
3
  import { HashValueDisplay } from "@/components/HashValueDisplay";
4
4
  import { Button } from "@/components/ui/button";
5
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardDescription,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from "@/components/ui/card";
6
12
  import { Separator } from "@/components/ui/separator";
7
13
  import { Skeleton } from "@/components/ui/skeleton";
8
14
  import {
@@ -13,33 +19,41 @@ import {
13
19
  TableHeader,
14
20
  TableRow,
15
21
  } from "@/components/ui/table";
22
+ import {
23
+ DEFAULT_INSIGHTS_RETENTION,
24
+ formatDays,
25
+ type InsightsRetentionDays,
26
+ } from "@/lib/insights-retention";
16
27
  import type {
17
28
  InsightsEventRow,
18
29
  InsightsInstallationViewRow,
19
30
  InsightsViewPage,
20
31
  } from "@/lib/insights-view";
21
32
 
33
+ import { BuiltinBundleBadge, isBuiltinBundle } from "./BuiltinBundle";
22
34
  import {
23
35
  EventBundleTransition,
24
36
  EventTimestamp,
25
- EventTypeBadge,
37
+ EventTypeDetails,
38
+ HttpResponseDetails,
26
39
  useInsightsTimeFormat,
27
40
  } from "./EventDetails";
28
41
  import { EventHistoryList } from "./EventHistoryList";
29
42
  import { InsightsErrorAlert } from "./InsightsErrorAlert";
30
43
  import { InsightsPagination } from "./InsightsPagination";
31
44
 
32
- const getUserLabel = (event: {
33
- readonly username: string | null;
34
- readonly userId: string | null;
35
- }) => event.userId ?? event.username ?? "—";
45
+ const getUserLabel = (event: { readonly userId: string | null }) =>
46
+ event.userId ?? "—";
36
47
 
48
+ /** A download or a failed update leaves the installation on the bundle it ran. */
37
49
  const getLastKnownBundleId = (
38
50
  event: InsightsEventRow | InsightsInstallationViewRow,
39
51
  ) =>
40
52
  "lastKnownBundleId" in event
41
53
  ? event.lastKnownBundleId
42
- : (event.toBundleId ?? event.fromBundleId);
54
+ : event.type === "UPDATE_DOWNLOADED" || event.type === "UPDATE_FAILED"
55
+ ? event.fromBundleId
56
+ : event.toBundleId;
43
57
 
44
58
  export function InstallationHistoryCard({
45
59
  error,
@@ -49,6 +63,7 @@ export function InstallationHistoryCard({
49
63
  onPrevious,
50
64
  onRefresh,
51
65
  pageNumber,
66
+ retention = DEFAULT_INSIGHTS_RETENTION,
52
67
  selectedEvent,
53
68
  selectedInstallId,
54
69
  }: {
@@ -59,6 +74,8 @@ export function InstallationHistoryCard({
59
74
  readonly onNext: () => void;
60
75
  readonly onPrevious: () => void;
61
76
  readonly pageNumber: number;
77
+ /** How long the server keeps Insights rows. */
78
+ readonly retention?: InsightsRetentionDays;
62
79
  readonly selectedEvent:
63
80
  | InsightsEventRow
64
81
  | InsightsInstallationViewRow
@@ -70,6 +87,15 @@ export function InstallationHistoryCard({
70
87
  ? getLastKnownBundleId(selectedEvent)
71
88
  : undefined;
72
89
 
90
+ const pendingBundleId =
91
+ selectedEvent && "pendingBundleId" in selectedEvent
92
+ ? selectedEvent.pendingBundleId
93
+ : selectedEvent &&
94
+ "type" in selectedEvent &&
95
+ selectedEvent.type === "UPDATE_DOWNLOADED"
96
+ ? selectedEvent.toBundleId
97
+ : null;
98
+
73
99
  return (
74
100
  <Card className="@container min-h-0 min-w-0 shadow-sm">
75
101
  <CardHeader className="p-4 sm:p-6">
@@ -82,6 +108,14 @@ export function InstallationHistoryCard({
82
108
  : "Select an installation"}
83
109
  </h2>
84
110
  </CardTitle>
111
+ {selectedInstallId !== undefined ? (
112
+ <CardDescription>
113
+ Bundle changes and update failures are kept for{" "}
114
+ {formatDays(retention.rawDays)}; the latest report, for{" "}
115
+ {formatDays(retention.dailyDays)} after the installation last
116
+ reports.
117
+ </CardDescription>
118
+ ) : null}
85
119
  </div>
86
120
  {onRefresh && selectedInstallId !== undefined ? (
87
121
  <Button
@@ -127,7 +161,7 @@ export function InstallationHistoryCard({
127
161
  <dt className="text-xs text-muted-foreground">
128
162
  Last known bundle
129
163
  </dt>
130
- <dd className="mt-1 font-mono text-xs">
164
+ <dd className="mt-1 flex min-w-0 flex-wrap items-center gap-2 font-mono text-xs">
131
165
  {lastKnownBundleId ? (
132
166
  <HashValueDisplay
133
167
  value={lastKnownBundleId}
@@ -136,9 +170,30 @@ export function InstallationHistoryCard({
136
170
  ) : (
137
171
  "—"
138
172
  )}
173
+ {isBuiltinBundle(
174
+ lastKnownBundleId,
175
+ selectedEvent.minBundleId,
176
+ ) ? (
177
+ <BuiltinBundleBadge />
178
+ ) : null}
139
179
  </dd>
140
180
  </div>
181
+ {pendingBundleId ? (
182
+ <div className="col-span-2 min-w-0">
183
+ <dt className="text-xs text-muted-foreground">
184
+ Downloaded · Not launched yet
185
+ </dt>
186
+ <dd className="mt-1 text-xs">
187
+ <HashValueDisplay value={pendingBundleId} />
188
+ </dd>
189
+ </div>
190
+ ) : null}
141
191
  </dl>
192
+ {selectedEvent.httpResponse ? (
193
+ <div className="mt-4">
194
+ <HttpResponseDetails event={selectedEvent} />
195
+ </div>
196
+ ) : null}
142
197
  </section>
143
198
  <Separator />
144
199
  </>
@@ -192,7 +247,7 @@ export function InstallationHistoryCard({
192
247
  />
193
248
  </TableCell>
194
249
  <TableCell>
195
- <EventTypeBadge type={event.type} />
250
+ <EventTypeDetails event={event} />
196
251
  </TableCell>
197
252
  <TableCell className="whitespace-normal text-xs">
198
253
  <div className="flex flex-col gap-2">
@@ -226,8 +281,8 @@ export function InstallationHistoryCard({
226
281
  ) : (
227
282
  <>
228
283
  <div className="p-6 text-sm text-muted-foreground">
229
- {pageNumber > 1 || history?.nextCursor
230
- ? "No bundle changes on this page."
284
+ {pageNumber > 1
285
+ ? "No older bundle changes."
231
286
  : "No bundle changes recorded yet."}
232
287
  </div>
233
288
  {history && (pageNumber > 1 || history.nextCursor) ? (
@@ -17,7 +17,7 @@ const getLastKnownBundleId = (event: InsightsInstallationViewRow) =>
17
17
  event.lastKnownBundleId;
18
18
 
19
19
  const getUserLabel = (event: InsightsInstallationViewRow) =>
20
- event.userId ?? event.username ?? "—";
20
+ event.userId ?? "—";
21
21
 
22
22
  export function InstallationMatchesCard({
23
23
  error,
@@ -0,0 +1,308 @@
1
+ import { Link } from "@tanstack/react-router";
2
+ import { ChartLine } from "lucide-react";
3
+
4
+ import { buttonVariants } from "@/components/ui/button";
5
+ import {
6
+ Card,
7
+ CardContent,
8
+ CardFooter,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from "@/components/ui/card";
12
+ import { Separator } from "@/components/ui/separator";
13
+ import { Skeleton } from "@/components/ui/skeleton";
14
+ import type {
15
+ BundleActivityInput,
16
+ BundleActivityReport,
17
+ } from "@/lib/bundle-activity";
18
+ import { useBundleActivityQuery } from "@/lib/bundle-activity";
19
+ import { useConsoleFeature } from "@/lib/console-features-api";
20
+ import { useUpdateFailuresQuery } from "@/lib/insights-api";
21
+ import { failureRate, formatRate } from "@/lib/insights-failures";
22
+ import { adoptionWindow } from "@/lib/insights-search";
23
+ import { cn } from "@/lib/utils";
24
+
25
+ import type { ReleaseColumn } from "../FeatureSlots";
26
+ import { InsightsInfo } from "./InsightsInfo";
27
+
28
+ const activityStats = [
29
+ { label: "Downloaded", field: "downloads", color: "text-primary/85" },
30
+ { label: "Launched", field: "launched", color: "text-success/85" },
31
+ { label: "Crashed", field: "failedLaunches", color: "text-warning/85" },
32
+ ] as const;
33
+
34
+ // As Release health rates crashes: over launches plus crashes, since an
35
+ // installation that crashes never reports running the bundle.
36
+ const crashRate = (report: BundleActivityReport): string => {
37
+ const attempts = report.launched + report.failedLaunches;
38
+ return attempts === 0
39
+ ? "—"
40
+ : `${((report.failedLaunches / attempts) * 100).toFixed(2)}%`;
41
+ };
42
+
43
+ /** Downloads that neither launched nor crashed yet: waiting for a restart, or passed over for a newer bundle. */
44
+ const notLaunched = (report: BundleActivityReport) =>
45
+ Math.max(0, report.downloads - report.launched - report.failedLaunches);
46
+
47
+ function ReleaseMetricsInfo() {
48
+ return (
49
+ <InsightsInfo label="About release insight metrics">
50
+ Downloaded: got this bundle. Launched: ran it. Crashed: crashed on it and
51
+ rolled back. Once every installation restarts, Downloaded = Launched +
52
+ Crashed.
53
+ </InsightsInfo>
54
+ );
55
+ }
56
+
57
+ export function BundleMovementSummary({
58
+ report,
59
+ loading = false,
60
+ input,
61
+ variant = "inline",
62
+ }: {
63
+ readonly report?: BundleActivityReport;
64
+ readonly loading?: boolean;
65
+ readonly input?: BundleActivityInput;
66
+ readonly variant?: "inline" | "card";
67
+ }) {
68
+ if (!report) {
69
+ return loading ? (
70
+ <Skeleton
71
+ aria-label="Loading release insights"
72
+ className={variant === "card" ? "h-16 w-full" : "h-4 w-40"}
73
+ />
74
+ ) : (
75
+ <span
76
+ aria-label="Release insights unavailable"
77
+ className="text-sm text-muted-foreground"
78
+ >
79
+ —
80
+ </span>
81
+ );
82
+ }
83
+ // A release of the built-in bundle comes with the app: nothing downloads it.
84
+ const builtIn = input?.builtIn === true;
85
+ const waiting = builtIn ? 0 : notLaunched(report);
86
+ const metrics = (
87
+ <dl
88
+ className={cn(
89
+ variant === "card"
90
+ ? "grid grid-cols-[1fr_1fr_auto] gap-4"
91
+ : "flex min-w-[220px] flex-wrap items-baseline gap-x-3 gap-y-1",
92
+ )}
93
+ >
94
+ {activityStats.map(({ label, field, color }) => (
95
+ <div
96
+ key={field}
97
+ className={cn(
98
+ "flex",
99
+ variant === "card"
100
+ ? "min-w-0 flex-col gap-1"
101
+ : "items-baseline gap-1.5 whitespace-nowrap",
102
+ )}
103
+ >
104
+ <dt className="text-xs text-muted-foreground">{label}</dt>
105
+ <dd
106
+ className={cn(
107
+ "flex flex-wrap items-baseline gap-x-1.5 tabular-nums",
108
+ variant === "card"
109
+ ? "text-xl font-semibold"
110
+ : "text-sm font-medium",
111
+ report[field] > 0 && !(field === "downloads" && builtIn)
112
+ ? color
113
+ : "text-muted-foreground",
114
+ )}
115
+ >
116
+ {field === "downloads" && builtIn ? (
117
+ <span title="The built-in bundle is never downloaded">—</span>
118
+ ) : (
119
+ report[field].toLocaleString()
120
+ )}
121
+ {field === "failedLaunches" ? (
122
+ <span className="text-xs font-normal text-muted-foreground">
123
+ ({crashRate(report)})
124
+ </span>
125
+ ) : null}
126
+ </dd>
127
+ </div>
128
+ ))}
129
+ </dl>
130
+ );
131
+ return (
132
+ <div
133
+ className={cn(
134
+ "flex",
135
+ variant === "card" ? "flex-col gap-2" : "items-center gap-1",
136
+ )}
137
+ >
138
+ {input ? (
139
+ <Link
140
+ className="min-w-0 rounded-sm hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
141
+ to="/insights"
142
+ search={{
143
+ healthPlatform: input.platform,
144
+ healthChannel: input.channel,
145
+ releaseId: input.releaseId,
146
+ bundleWindow: adoptionWindow(input.releaseId),
147
+ }}
148
+ >
149
+ {metrics}
150
+ </Link>
151
+ ) : (
152
+ metrics
153
+ )}
154
+ {variant === "card" && waiting > 0 ? (
155
+ <p className="text-xs text-muted-foreground">
156
+ {waiting.toLocaleString()} downloaded but not launched yet: waiting
157
+ for a restart, or passed over for a newer bundle.
158
+ </p>
159
+ ) : null}
160
+ {variant === "inline" ? <ReleaseMetricsInfo /> : null}
161
+ </div>
162
+ );
163
+ }
164
+
165
+ /** The release's failed downloads and installs since its first report, and their rate. */
166
+ export function BundleDownloadFailures({
167
+ input,
168
+ }: {
169
+ readonly input: BundleActivityInput;
170
+ }) {
171
+ const query = useUpdateFailuresQuery({
172
+ platform: input.platform,
173
+ channel: input.channel,
174
+ releaseId: input.releaseId,
175
+ });
176
+ const report = query.data;
177
+ return (
178
+ <div className="flex items-center gap-1">
179
+ {report ? (
180
+ <dl>
181
+ <div className="flex flex-wrap items-baseline gap-x-1.5">
182
+ <dt className="text-xs text-muted-foreground">Download failures</dt>
183
+ <dd
184
+ className={cn(
185
+ "flex items-baseline gap-1.5 text-sm font-medium tabular-nums",
186
+ report.failedUpdates > 0
187
+ ? "text-warning/85"
188
+ : "text-muted-foreground",
189
+ )}
190
+ >
191
+ {report.failedUpdates.toLocaleString()}
192
+ <span className="text-xs font-normal text-muted-foreground">
193
+ ({formatRate(failureRate(report))})
194
+ </span>
195
+ </dd>
196
+ </div>
197
+ </dl>
198
+ ) : query.isPending ? (
199
+ <Skeleton aria-label="Loading download failures" className="h-4 w-40" />
200
+ ) : (
201
+ <span
202
+ aria-label="Download failures unavailable"
203
+ className="text-sm text-muted-foreground"
204
+ >
205
+ —
206
+ </span>
207
+ )}
208
+ <InsightsInfo label="About download failures">
209
+ Failed downloads or installs of this bundle, at most one per
210
+ installation a day. Rate = failures ÷ (failures + downloads).
211
+ </InsightsInfo>
212
+ </div>
213
+ );
214
+ }
215
+
216
+ /** The bundle detail's Insights card, a section of the release editor: activity and download failures. */
217
+ export function BundleInsightsSummary({
218
+ input,
219
+ }: {
220
+ readonly input: BundleActivityInput;
221
+ }) {
222
+ const query = useBundleActivityQuery([input]);
223
+ return (
224
+ <Card>
225
+ <CardHeader className="flex-row items-center justify-between px-4 pt-4 pb-3">
226
+ <CardTitle className="text-sm font-medium">Insights</CardTitle>
227
+ <ReleaseMetricsInfo />
228
+ </CardHeader>
229
+ <CardContent className="flex flex-col gap-3 px-4 pb-4">
230
+ <BundleMovementSummary
231
+ variant="card"
232
+ input={input}
233
+ loading={query.isFetching}
234
+ report={query.data?.[input.releaseId]}
235
+ />
236
+ <Separator />
237
+ <BundleDownloadFailures input={input} />
238
+ </CardContent>
239
+ <CardFooter className="px-4 pb-4">
240
+ <Link
241
+ className={buttonVariants({ variant: "outline", size: "sm" })}
242
+ to="/insights"
243
+ search={{
244
+ healthPlatform: input.platform,
245
+ healthChannel: input.channel,
246
+ releaseId: input.releaseId,
247
+ bundleWindow: adoptionWindow(input.releaseId),
248
+ healthChart: "adoption",
249
+ }}
250
+ >
251
+ <ChartLine aria-hidden="true" data-icon="inline-start" />
252
+ View adoption
253
+ </Link>
254
+ </CardFooter>
255
+ </Card>
256
+ );
257
+ }
258
+
259
+ /**
260
+ * The release editor's Insights card where the console reads no release
261
+ * activity, as through a self-hosted server's admin API: download failures
262
+ * alone. With release activity, its card shows them.
263
+ */
264
+ export function ReleaseFailuresSection({
265
+ input,
266
+ }: {
267
+ readonly input: BundleActivityInput;
268
+ }) {
269
+ const activity = useConsoleFeature("insightsAnalytics");
270
+ if (activity) return null;
271
+ return (
272
+ <Card>
273
+ <CardHeader className="px-4 pt-4 pb-3">
274
+ <CardTitle className="text-sm font-medium">Insights</CardTitle>
275
+ </CardHeader>
276
+ <CardContent className="px-4 pb-4">
277
+ <BundleDownloadFailures input={input} />
278
+ </CardContent>
279
+ </Card>
280
+ );
281
+ }
282
+
283
+ /**
284
+ * One release's activity in the Bundles page's Insights column. Every cell
285
+ * asks for the page's releases under one query key, so the page makes one
286
+ * request.
287
+ */
288
+ function ReleaseActivityCell({
289
+ releaseId,
290
+ releases,
291
+ }: {
292
+ readonly releaseId: string;
293
+ readonly releases: readonly BundleActivityInput[];
294
+ }) {
295
+ const query = useBundleActivityQuery(releases);
296
+ return (
297
+ <BundleMovementSummary
298
+ input={releases.find((release) => release.releaseId === releaseId)}
299
+ loading={query.isFetching}
300
+ report={query.data?.[releaseId]}
301
+ />
302
+ );
303
+ }
304
+
305
+ /** The Bundles page's Insights column: each release's downloads, launches, and crashes. */
306
+ export const releaseActivityColumn: ReleaseColumn = {
307
+ Cell: ReleaseActivityCell,
308
+ };