@hot-updater/server 1.0.0-rc.16 → 1.0.0-rc.18
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/dist/adapters/drizzle.cjs +5 -5
- package/dist/adapters/drizzle.mjs +4 -4
- package/dist/adapters/kysely.cjs +3 -3
- package/dist/adapters/kysely.mjs +2 -2
- package/dist/adapters/mongodb.cjs +3 -3
- package/dist/adapters/mongodb.mjs +2 -2
- package/dist/adapters/mongodbAdapter.cjs +19 -1
- package/dist/adapters/mongodbAdapter.mjs +19 -1
- package/dist/adapters/prisma.cjs +5 -5
- package/dist/adapters/prisma.mjs +4 -4
- package/dist/assembly/assemblePlugins.cjs +47 -28
- package/dist/assembly/assemblePlugins.d.cts +3 -7
- package/dist/assembly/assemblePlugins.d.mts +3 -7
- package/dist/assembly/assemblePlugins.mjs +46 -27
- package/dist/assembly/clientPlugins.cjs +86 -0
- package/dist/assembly/clientPlugins.d.cts +16 -0
- package/dist/assembly/clientPlugins.d.mts +16 -0
- package/dist/assembly/clientPlugins.mjs +86 -0
- package/dist/assembly/configError.cjs +7 -0
- package/dist/assembly/configError.d.cts +7 -0
- package/dist/assembly/configError.d.mts +7 -0
- package/dist/assembly/configError.mjs +7 -0
- package/dist/assembly/databasePlugins.cjs +5 -1
- package/dist/assembly/databasePlugins.d.cts +5 -5
- package/dist/assembly/databasePlugins.d.mts +5 -5
- package/dist/assembly/databasePlugins.mjs +5 -1
- package/dist/assembly/pluginCli.cjs +125 -0
- package/dist/assembly/pluginCli.d.cts +59 -0
- package/dist/assembly/pluginCli.d.mts +59 -0
- package/dist/assembly/pluginCli.mjs +121 -0
- package/dist/createHotUpdaterCore.cjs +14 -16
- package/dist/createHotUpdaterCore.d.cts +10 -3
- package/dist/createHotUpdaterCore.d.mts +10 -3
- package/dist/createHotUpdaterCore.mjs +11 -13
- package/dist/database/aggregateBatching.cjs +626 -0
- package/dist/database/aggregateBatching.d.cts +10 -0
- package/dist/database/aggregateBatching.d.mts +10 -0
- package/dist/database/aggregateBatching.mjs +625 -0
- package/dist/{insights/errors.cjs → database/busy.cjs} +2 -15
- package/dist/database/busy.d.cts +12 -0
- package/dist/database/busy.d.mts +12 -0
- package/dist/{insights/errors.mjs → database/busy.mjs} +3 -14
- package/dist/database/database.cjs +22 -3
- package/dist/database/database.d.cts +15 -2
- package/dist/database/database.d.mts +15 -2
- package/dist/database/database.mjs +22 -3
- package/dist/database/definitions.d.cts +10 -0
- package/dist/database/definitions.d.mts +10 -0
- package/dist/database/engine.d.cts +2 -1
- package/dist/database/engine.d.mts +2 -1
- package/dist/database/engineAggregates.cjs +7 -4
- package/dist/database/engineAggregates.mjs +8 -5
- package/dist/database/engineReads.cjs +5 -3
- package/dist/database/engineReads.mjs +6 -4
- package/dist/database/engineTransaction.cjs +2 -6
- package/dist/database/engineTransaction.mjs +2 -6
- package/dist/database/fence.cjs +2 -1
- package/dist/database/fence.mjs +2 -1
- package/dist/database/index.cjs +17 -8
- package/dist/database/index.d.cts +5 -4
- package/dist/database/index.d.mts +5 -4
- package/dist/database/index.mjs +6 -5
- package/dist/database/kv/kvAdapter.cjs +15 -3
- package/dist/database/kv/kvAdapter.d.cts +8 -0
- package/dist/database/kv/kvAdapter.d.mts +8 -0
- package/dist/database/kv/kvAdapter.mjs +16 -4
- package/dist/database/resolveSchema.cjs +4 -1
- package/dist/database/resolveSchema.d.cts +1 -1
- package/dist/database/resolveSchema.d.mts +1 -1
- package/dist/database/resolveSchema.mjs +4 -1
- package/dist/database/retention.cjs +92 -0
- package/dist/database/retention.mjs +90 -0
- package/dist/database/schema.cjs +5 -2
- package/dist/database/schema.d.cts +30 -1
- package/dist/database/schema.d.mts +30 -1
- package/dist/database/schema.mjs +5 -2
- package/dist/database/sql/sqlAdapter.cjs +8 -0
- package/dist/database/sql/sqlAdapter.mjs +9 -1
- package/dist/database/sql/sqlSchema.cjs +56 -0
- package/dist/database/sql/sqlSchema.mjs +55 -1
- package/dist/db/coreDatabase.cjs +85 -0
- package/dist/db/coreDatabase.d.cts +57 -0
- package/dist/db/coreDatabase.d.mts +57 -0
- package/dist/db/coreDatabase.mjs +78 -0
- package/dist/db/index.cjs +11 -3
- package/dist/db/index.d.cts +7 -3
- package/dist/db/index.d.mts +7 -3
- package/dist/db/index.mjs +6 -3
- package/dist/db/types.d.cts +3 -3
- package/dist/db/types.d.mts +3 -3
- package/dist/diff.cjs +3 -0
- package/dist/diff.d.cts +2 -0
- package/dist/diff.d.mts +2 -0
- package/dist/diff.mjs +2 -0
- package/dist/handler.cjs +2 -14
- package/dist/handler.mjs +1 -13
- package/dist/handlerReleaseCatalogRoutes.cjs +21 -2
- package/dist/handlerReleaseCatalogRoutes.mjs +21 -2
- package/dist/index.cjs +2 -6
- package/dist/index.d.cts +4 -7
- package/dist/index.d.mts +4 -7
- package/dist/index.mjs +2 -4
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/{apiKeys.cjs → plugins/api-keys/apiKeys.cjs} +1 -1
- package/dist/{apiKeys.d.cts → plugins/api-keys/apiKeys.d.cts} +2 -2
- package/dist/{apiKeys.d.mts → plugins/api-keys/apiKeys.d.mts} +2 -2
- package/dist/{apiKeys.mjs → plugins/api-keys/apiKeys.mjs} +1 -1
- package/dist/plugins/api-keys/cli.cjs +115 -0
- package/dist/plugins/api-keys/cli.mjs +114 -0
- package/dist/plugins/api-keys/index.cjs +9 -6
- package/dist/plugins/api-keys/index.d.cts +4 -5
- package/dist/plugins/api-keys/index.d.mts +4 -5
- package/dist/plugins/api-keys/index.mjs +9 -7
- package/dist/plugins/api-keys/model.d.cts +20 -2
- package/dist/plugins/api-keys/model.d.mts +20 -2
- package/dist/plugins/definePlugin.cjs +1 -1
- package/dist/plugins/definePlugin.d.cts +130 -3
- package/dist/plugins/definePlugin.d.mts +130 -3
- package/dist/plugins/definePlugin.mjs +1 -1
- package/dist/plugins/index.cjs +25 -4
- package/dist/plugins/index.d.cts +7 -4
- package/dist/plugins/index.d.mts +7 -4
- package/dist/plugins/index.mjs +6 -4
- package/dist/plugins/insights/contract.cjs +193 -0
- package/dist/plugins/insights/contract.mjs +189 -0
- package/dist/{insights → plugins/insights}/domain.d.cts +60 -15
- package/dist/{insights → plugins/insights}/domain.d.mts +60 -15
- package/dist/plugins/insights/errors.cjs +15 -0
- package/dist/plugins/insights/errors.mjs +14 -0
- package/dist/{insights → plugins/insights}/eventInput.cjs +155 -22
- package/dist/{insights → plugins/insights}/eventInput.mjs +155 -22
- package/dist/plugins/insights/eventRow.cjs +49 -0
- package/dist/plugins/insights/eventRow.d.cts +64 -0
- package/dist/plugins/insights/eventRow.d.mts +64 -0
- package/dist/plugins/insights/eventRow.mjs +47 -0
- package/dist/plugins/insights/index.cjs +106 -26
- package/dist/plugins/insights/index.d.cts +130 -27
- package/dist/plugins/insights/index.d.mts +130 -27
- package/dist/plugins/insights/index.mjs +106 -29
- package/dist/plugins/insights/model.cjs +2 -2
- package/dist/plugins/insights/model.d.cts +1 -1
- package/dist/plugins/insights/model.d.mts +1 -1
- package/dist/plugins/insights/model.mjs +1 -1
- package/dist/plugins/insights/modelTypes.d.cts +176 -0
- package/dist/plugins/insights/modelTypes.d.mts +176 -0
- package/dist/plugins/insights/overview.cjs +109 -0
- package/dist/plugins/insights/overview.d.cts +15 -0
- package/dist/plugins/insights/overview.d.mts +15 -0
- package/dist/plugins/insights/overview.mjs +107 -0
- package/dist/{insights → plugins/insights}/provider.cjs +33 -14
- package/dist/{insights → plugins/insights}/provider.d.cts +2 -2
- package/dist/{insights → plugins/insights}/provider.d.mts +2 -2
- package/dist/{insights → plugins/insights}/provider.mjs +30 -11
- package/dist/{insights → plugins/insights}/queryInput.cjs +2 -2
- package/dist/{insights → plugins/insights}/queryInput.mjs +2 -2
- package/dist/plugins/insights/reads.cjs +298 -88
- package/dist/plugins/insights/reads.d.cts +61 -0
- package/dist/plugins/insights/reads.d.mts +61 -0
- package/dist/plugins/insights/reads.mjs +298 -90
- package/dist/plugins/insights/recordEvent.cjs +223 -39
- package/dist/plugins/insights/recordEvent.d.cts +9 -4
- package/dist/plugins/insights/recordEvent.d.mts +9 -4
- package/dist/plugins/insights/recordEvent.mjs +221 -37
- package/dist/{insights → plugins/insights}/routes.cjs +3 -30
- package/dist/{insights → plugins/insights}/routes.mjs +4 -28
- package/dist/plugins/insights/schema.cjs +182 -40
- package/dist/plugins/insights/schema.d.cts +446 -6
- package/dist/plugins/insights/schema.d.mts +446 -6
- package/dist/plugins/insights/schema.mjs +180 -41
- package/dist/plugins/insights/testing/eventRow.d.mts +64 -0
- package/dist/plugins/insights/testing/expectInsightsIndex.mjs +18 -0
- package/dist/plugins/insights/testing/fixtures.d.mts +8 -0
- package/dist/plugins/insights/testing/fixtures.mjs +25 -0
- package/dist/plugins/insights/testing/index.d.mts +24 -0
- package/dist/plugins/insights/testing/index.mjs +25 -0
- package/dist/plugins/insights/testing/insightsModelTests.mjs +714 -0
- package/dist/plugins/insights/testing/modelTypes.d.mts +176 -0
- package/dist/plugins/insights/testing/setupInsightsHttpTestSuite.d.mts +11 -0
- package/dist/plugins/insights/testing/setupInsightsHttpTestSuite.mjs +107 -0
- package/dist/plugins/insights/testing/setupInsightsModelTestSuite.d.mts +11 -0
- package/dist/plugins/insights/testing/setupInsightsModelTestSuite.mjs +31 -0
- package/dist/plugins/insights/testing/types.d.mts +26 -0
- package/dist/{insights → plugins/insights}/types.d.cts +3 -2
- package/dist/{insights → plugins/insights}/types.d.mts +3 -2
- package/package.json +18 -7
- package/src/adapters/drizzle.conformance.integration.spec.ts +2 -2
- package/src/adapters/drizzle.readBudgets.integration.spec.ts +6 -4
- package/src/adapters/drizzle.spec.ts +15 -7
- package/src/adapters/drizzle.ts +6 -6
- package/src/adapters/kysely.conformance.integration.spec.ts +2 -2
- package/src/adapters/kysely.readBudgets.integration.spec.ts +4 -2
- package/src/adapters/kysely.spec.ts +13 -9
- package/src/adapters/kysely.ts +2 -2
- package/src/adapters/mongodb.integration.spec.ts +98 -7
- package/src/adapters/mongodb.ts +2 -2
- package/src/adapters/mongodbAdapter.ts +14 -0
- package/src/adapters/prisma.conformance.integration.spec.ts +2 -2
- package/src/adapters/prisma.integration.spec.ts +16 -8
- package/src/adapters/prisma.readBudgets.integration.spec.ts +5 -2
- package/src/adapters/prisma.spec.ts +17 -9
- package/src/adapters/prisma.ts +6 -6
- package/src/adapters/prismaTestClients.ts +8 -1
- package/src/assembly/assemblePlugins.ts +73 -40
- package/src/assembly/assembly.spec.ts +63 -11
- package/src/assembly/assembly.types.spec.ts +50 -2
- package/src/assembly/clientPlugins.ts +113 -0
- package/src/assembly/configError.ts +4 -0
- package/src/assembly/databasePlugins.ts +11 -5
- package/src/assembly/pluginCli.spec.ts +254 -0
- package/src/assembly/pluginCli.ts +248 -0
- package/src/assembly/{builtInRoutes.spec.ts → pluginRoutes.spec.ts} +10 -37
- package/src/createHotUpdaterCore.ts +21 -17
- package/src/database/aggregateBatching.spec.ts +577 -0
- package/src/database/aggregateBatching.ts +803 -0
- package/src/{insights/errors.spec.ts → database/busy.spec.ts} +2 -5
- package/src/{insights/errors.ts → database/busy.ts} +1 -13
- package/src/database/database.ts +39 -8
- package/src/database/definitions.ts +4 -0
- package/src/database/engineAggregates.spec.ts +4 -7
- package/src/database/engineAggregates.ts +5 -9
- package/src/database/engineReads.ts +7 -5
- package/src/database/engineTransaction.ts +1 -7
- package/src/database/fence.spec.ts +5 -5
- package/src/database/fence.ts +4 -0
- package/src/database/index.ts +13 -7
- package/src/database/kv/kvAdapter.batching.integration.spec.ts +15 -0
- package/src/database/kv/kvAdapter.conformance.integration.spec.ts +2 -0
- package/src/database/kv/kvAdapter.spec.ts +128 -1
- package/src/database/kv/kvAdapter.ts +30 -1
- package/src/database/kv/kvTestStore.ts +28 -1
- package/src/database/resolveSchema.ts +4 -1
- package/src/database/retention.spec.ts +314 -0
- package/src/database/retention.ts +129 -0
- package/src/database/schema.ts +44 -0
- package/src/database/sql/sqlAdapter.integration.spec.ts +4 -7
- package/src/database/sql/sqlAdapter.readBudgets.integration.spec.ts +18 -4
- package/src/database/sql/sqlAdapter.spec.ts +91 -3
- package/src/database/sql/sqlAdapter.ts +20 -0
- package/src/database/sql/sqlSchema.ts +81 -0
- package/src/databaseTestSuite.spec.ts +9 -4
- package/src/db/coreDatabase.ts +153 -0
- package/src/db/engineSql.spec.ts +8 -7
- package/src/db/index.ts +19 -2
- package/src/db/pluginTables.spec.ts +57 -27
- package/src/db/types.ts +3 -3
- package/src/diff.ts +6 -0
- package/src/handler-standalone.integration.spec.ts +2 -3
- package/src/handler.ts +1 -32
- package/src/handlerReleaseCatalogRoutes.ts +26 -2
- package/src/index.ts +0 -10
- package/src/{apiKeys.integration.spec.ts → plugins/api-keys/apiKeys.integration.spec.ts} +4 -4
- package/src/{apiKeys.spec.ts → plugins/api-keys/apiKeys.spec.ts} +3 -3
- package/src/{apiKeys.ts → plugins/api-keys/apiKeys.ts} +1 -1
- package/src/plugins/api-keys/apiKeysPlugin.spec.ts +11 -15
- package/src/plugins/api-keys/cli.spec.ts +187 -0
- package/src/plugins/api-keys/cli.ts +140 -0
- package/src/plugins/api-keys/index.ts +48 -41
- package/src/plugins/api-keys/model.ts +21 -2
- package/src/plugins/definePlugin.ts +144 -2
- package/src/plugins/index.ts +20 -2
- package/src/plugins/insights/contract.spec.ts +285 -0
- package/src/plugins/insights/contract.ts +372 -0
- package/src/{insights → plugins/insights}/domain.ts +73 -15
- package/src/plugins/insights/errors.ts +11 -0
- package/src/{insights → plugins/insights}/eventInput.ts +189 -29
- package/src/plugins/insights/eventRow.ts +209 -0
- package/src/plugins/insights/index.ts +181 -45
- package/src/{insights → plugins/insights}/insights.integration.spec.ts +330 -34
- package/src/plugins/insights/insightsRollout.integration.spec.ts +1 -2
- package/src/plugins/insights/model.spec.ts +3 -5
- package/src/plugins/insights/model.ts +2 -3
- package/src/plugins/insights/modelTypes.ts +197 -0
- package/src/plugins/insights/overview.spec.ts +64 -0
- package/src/plugins/insights/overview.ts +214 -0
- package/src/{insights → plugins/insights}/provider.spec.ts +21 -10
- package/src/{insights → plugins/insights}/provider.ts +58 -21
- package/src/{insights → plugins/insights}/queryInput.ts +2 -2
- package/src/plugins/insights/reads.spec.ts +340 -52
- package/src/plugins/insights/reads.ts +453 -123
- package/src/plugins/insights/recordEvent.spec.ts +474 -20
- package/src/plugins/insights/recordEvent.ts +301 -63
- package/src/plugins/insights/retention.spec.ts +115 -0
- package/src/{insights → plugins/insights}/routes.ts +1 -31
- package/src/plugins/insights/schema.ts +356 -176
- package/src/plugins/insights/testing/expectInsightsIndex.spec.ts +42 -0
- package/src/plugins/insights/testing/expectInsightsIndex.ts +20 -0
- package/src/plugins/insights/testing/fixtures.ts +30 -0
- package/src/plugins/insights/testing/index.ts +45 -0
- package/src/plugins/insights/testing/insightsModelTests.ts +860 -0
- package/src/plugins/insights/testing/setupInsightsHttpTestSuite.ts +152 -0
- package/src/plugins/insights/testing/setupInsightsModelTestSuite.ts +36 -0
- package/src/plugins/insights/testing/types.ts +30 -0
- package/src/{insights → plugins/insights}/types.ts +1 -1
- package/src/readBudgets.testFixtures.ts +2 -2
- package/src/releaseCatalogRoutes.integration.spec.ts +7 -0
- package/src/retention.spec.ts +73 -0
- package/src/runtime.spec.ts +11 -12
- package/src/runtime.testFixtures.ts +7 -2
- package/src/testUtilsMutation.integration.spec.ts +2 -1
- package/src/testUtilsPackage.integration.spec.ts +7 -3
- package/dist/db/builtInDatabase.cjs +0 -104
- package/dist/db/builtInDatabase.d.cts +0 -38
- package/dist/db/builtInDatabase.d.mts +0 -38
- package/dist/db/builtInDatabase.mjs +0 -97
- package/dist/plugins/builtIn.cjs +0 -7
- package/dist/plugins/builtIn.mjs +0 -6
- package/src/db/builtInDatabase.ts +0 -149
- package/src/plugins/builtIn.ts +0 -5
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
compareInsightsText,
|
|
3
|
-
isInsightsMovementEvent,
|
|
4
|
-
isUUIDv7,
|
|
5
|
-
type InsightsBundleEventFilter,
|
|
6
|
-
type BundleEventRow,
|
|
7
|
-
type InsightsEventCursor,
|
|
8
|
-
type InsightsEventFilter,
|
|
9
|
-
type InsightsModel,
|
|
10
|
-
} from "@hot-updater/plugin-core";
|
|
1
|
+
import { isUUIDv7 } from "@hot-updater/plugin-core";
|
|
11
2
|
|
|
3
|
+
import { compareInsightsText, isInsightsMovementEvent } from "./contract";
|
|
12
4
|
import type {
|
|
13
5
|
ActiveInstallationWindow,
|
|
14
6
|
InsightsBundleSelection,
|
|
15
|
-
InsightsScope,
|
|
16
7
|
EventHistoryRow,
|
|
17
8
|
InstallationHistoryRow,
|
|
18
9
|
InstallationRow,
|
|
19
10
|
} from "./domain";
|
|
20
11
|
import { InsightsBadRequestError } from "./errors";
|
|
21
12
|
import { createBundleEventRow } from "./eventInput";
|
|
13
|
+
import type { BundleEventFailure, BundleEventRow } from "./eventRow";
|
|
14
|
+
import type {
|
|
15
|
+
InsightsBundleEventFilter,
|
|
16
|
+
InsightsEventCursor,
|
|
17
|
+
InsightsEventFilter,
|
|
18
|
+
InsightsModel,
|
|
19
|
+
InsightsScope,
|
|
20
|
+
} from "./modelTypes";
|
|
22
21
|
import type {
|
|
23
22
|
InsightsEventPageInput,
|
|
24
23
|
InsightsInstallationEventPageInput,
|
|
@@ -33,6 +32,7 @@ const MAX_IDENTITY_LENGTH = 255;
|
|
|
33
32
|
const MAX_CURSOR_LENGTH = 8 * 1_024;
|
|
34
33
|
|
|
35
34
|
const HOUR_MS = 60 * 60 * 1_000;
|
|
35
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
36
36
|
|
|
37
37
|
const WINDOW_MS: Record<ActiveInstallationWindow, number> = {
|
|
38
38
|
"24h": 24 * 60 * 60 * 1_000,
|
|
@@ -169,8 +169,8 @@ const bundleFilter = (
|
|
|
169
169
|
return { ...scope, type: "UPDATE_APPLIED", toBundleId: bundleId };
|
|
170
170
|
case "recovered":
|
|
171
171
|
return { ...scope, type: "RECOVERED", fromBundleId: bundleId };
|
|
172
|
-
case "
|
|
173
|
-
return { ...scope, type: "
|
|
172
|
+
case "failed":
|
|
173
|
+
return { ...scope, type: "UPDATE_FAILED", toBundleId: bundleId };
|
|
174
174
|
default:
|
|
175
175
|
throw new InsightsBadRequestError("Invalid Insights outcome.");
|
|
176
176
|
}
|
|
@@ -318,6 +318,41 @@ const assertEventRows = (
|
|
|
318
318
|
}
|
|
319
319
|
};
|
|
320
320
|
|
|
321
|
+
/** An `UPDATE_FAILED` row's failure, as the event lists show it. */
|
|
322
|
+
const toFailure = (
|
|
323
|
+
failure: BundleEventFailure,
|
|
324
|
+
): NonNullable<EventHistoryRow["failure"]> => ({
|
|
325
|
+
stage: failure.stage,
|
|
326
|
+
reason: failure.reason,
|
|
327
|
+
...(failure.resource === undefined ? {} : { resource: failure.resource }),
|
|
328
|
+
...(failure.http_status === undefined
|
|
329
|
+
? {}
|
|
330
|
+
: { httpStatus: failure.http_status }),
|
|
331
|
+
...(failure.transport === undefined ? {} : { transport: failure.transport }),
|
|
332
|
+
...(failure.origin_code === undefined
|
|
333
|
+
? {}
|
|
334
|
+
: { originCode: failure.origin_code }),
|
|
335
|
+
...(failure.previous_process_exit === undefined
|
|
336
|
+
? {}
|
|
337
|
+
: { previousProcessExit: failure.previous_process_exit }),
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
/** What a row's metadata adds for its type: a failure, a delivery, an exit reason. */
|
|
341
|
+
const eventDetails = ({ type, metadata }: BundleEventRow) => ({
|
|
342
|
+
...(type === "UPDATE_FAILED" && metadata.failure !== undefined
|
|
343
|
+
? { failure: toFailure(metadata.failure) }
|
|
344
|
+
: {}),
|
|
345
|
+
...(type === "UPDATE_DOWNLOADED" && metadata.delivery !== undefined
|
|
346
|
+
? { delivery: metadata.delivery }
|
|
347
|
+
: {}),
|
|
348
|
+
...(type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true
|
|
349
|
+
? { patchFallback: true as const }
|
|
350
|
+
: {}),
|
|
351
|
+
...(type === "RECOVERED" && metadata.previous_process_exit !== undefined
|
|
352
|
+
? { previousProcessExit: metadata.previous_process_exit }
|
|
353
|
+
: {}),
|
|
354
|
+
});
|
|
355
|
+
|
|
321
356
|
const toEventHistoryRow = (row: BundleEventRow): EventHistoryRow => ({
|
|
322
357
|
appVersion: row.app_version,
|
|
323
358
|
channel: row.channel,
|
|
@@ -330,7 +365,7 @@ const toEventHistoryRow = (row: BundleEventRow): EventHistoryRow => ({
|
|
|
330
365
|
toBundleId: row.to_bundle_id,
|
|
331
366
|
type: row.type,
|
|
332
367
|
userId: row.user_id,
|
|
333
|
-
|
|
368
|
+
...eventDetails(row),
|
|
334
369
|
});
|
|
335
370
|
|
|
336
371
|
const toInstallationRow = (row: BundleEventRow): InstallationRow => ({
|
|
@@ -346,7 +381,6 @@ const toInstallationRow = (row: BundleEventRow): InstallationRow => ({
|
|
|
346
381
|
platform: row.platform,
|
|
347
382
|
receivedAtMs: row.received_at_ms,
|
|
348
383
|
userId: row.user_id,
|
|
349
|
-
username: row.metadata.username,
|
|
350
384
|
});
|
|
351
385
|
|
|
352
386
|
interface EventBounds {
|
|
@@ -581,12 +615,15 @@ export const createInsightsProvider = (
|
|
|
581
615
|
"Invalid reporting installation window.",
|
|
582
616
|
);
|
|
583
617
|
}
|
|
584
|
-
//
|
|
585
|
-
//
|
|
618
|
+
// Ending with the current hour and starting with the UTC day the
|
|
619
|
+
// window reaches into: latest-event gauges count whole UTC days and
|
|
620
|
+
// outcome counters whole hours, so the counts read only maintained
|
|
621
|
+
// rows, never raw events.
|
|
586
622
|
const now = Date.now();
|
|
587
623
|
const beforeReceivedAtMs =
|
|
588
624
|
now - (now % HOUR_MS) + (now % HOUR_MS === 0 ? 0 : HOUR_MS);
|
|
589
|
-
const
|
|
625
|
+
const reach = Math.max(0, beforeReceivedAtMs - WINDOW_MS[window]);
|
|
626
|
+
const sinceMs = reach - (reach % DAY_MS);
|
|
590
627
|
const measure = async (count: Promise<number>) => {
|
|
591
628
|
const value = await count;
|
|
592
629
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
@@ -622,7 +659,7 @@ export const createInsightsProvider = (
|
|
|
622
659
|
downloadedReports,
|
|
623
660
|
appliedReports,
|
|
624
661
|
recoveredReports,
|
|
625
|
-
|
|
662
|
+
failedReports,
|
|
626
663
|
] = await Promise.all([
|
|
627
664
|
reporting,
|
|
628
665
|
measure(
|
|
@@ -646,7 +683,7 @@ export const createInsightsProvider = (
|
|
|
646
683
|
countOutcome("downloaded"),
|
|
647
684
|
countOutcome("applied"),
|
|
648
685
|
countOutcome("recovered"),
|
|
649
|
-
countOutcome("
|
|
686
|
+
countOutcome("failed"),
|
|
650
687
|
]);
|
|
651
688
|
return {
|
|
652
689
|
...scope,
|
|
@@ -660,7 +697,7 @@ export const createInsightsProvider = (
|
|
|
660
697
|
downloadedReports,
|
|
661
698
|
appliedReports,
|
|
662
699
|
recoveredReports,
|
|
663
|
-
|
|
700
|
+
failedReports,
|
|
664
701
|
},
|
|
665
702
|
};
|
|
666
703
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ActiveInstallationWindow,
|
|
3
3
|
InsightsBundleSelection,
|
|
4
|
-
InsightsScope,
|
|
5
4
|
} from "./domain";
|
|
6
5
|
import { InsightsBadRequestError } from "./errors";
|
|
6
|
+
import type { InsightsScope } from "./modelTypes";
|
|
7
7
|
import type {
|
|
8
8
|
InsightsEventPageInput,
|
|
9
9
|
InsightsUserInstallationPageInput,
|
|
@@ -88,7 +88,7 @@ export const parseEventPageInput = (
|
|
|
88
88
|
outcome !== "downloaded" &&
|
|
89
89
|
outcome !== "applied" &&
|
|
90
90
|
outcome !== "recovered" &&
|
|
91
|
-
outcome !== "
|
|
91
|
+
outcome !== "failed"
|
|
92
92
|
) {
|
|
93
93
|
throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
|
|
94
94
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type { BundleEventRow } from "@hot-updater/plugin-core";
|
|
2
1
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
addDistinct,
|
|
3
|
+
countDistinct,
|
|
5
4
|
createMemoryAdapter,
|
|
6
5
|
type DatabaseAdapter,
|
|
7
|
-
|
|
6
|
+
mergeDistinct,
|
|
8
7
|
} from "@hot-updater/plugin-core/internal";
|
|
9
8
|
import { createPluginTestHarness } from "@hot-updater/test-utils";
|
|
10
9
|
import { describe, expect, it } from "vitest";
|
|
11
10
|
|
|
12
11
|
import * as engine from "../../database";
|
|
13
|
-
import { InsightsBadRequestError } from "
|
|
14
|
-
import {
|
|
15
|
-
import type { InsightsEventPageInput } from "../../insights/types";
|
|
12
|
+
import { InsightsBadRequestError } from "./errors";
|
|
13
|
+
import type { BundleEventRow } from "./eventRow";
|
|
16
14
|
import { createInsightsModel, insights } from "./index";
|
|
15
|
+
import { createInsightsProvider } from "./provider";
|
|
16
|
+
import type { InsightsEventPageInput } from "./types";
|
|
17
17
|
|
|
18
18
|
const HOUR = 3_600_000;
|
|
19
19
|
const DAY = 86_400_000;
|
|
@@ -39,7 +39,6 @@ const event = (
|
|
|
39
39
|
app_version: "1.0.0",
|
|
40
40
|
channel: "production",
|
|
41
41
|
metadata: {
|
|
42
|
-
username: null,
|
|
43
42
|
cohort: "1",
|
|
44
43
|
update_strategy: "appVersion",
|
|
45
44
|
fingerprint_hash: null,
|
|
@@ -51,9 +50,7 @@ const event = (
|
|
|
51
50
|
|
|
52
51
|
/** The HLL estimate for these installs, as the stored sketches merge to. */
|
|
53
52
|
const distinct = (installs: readonly string[]) =>
|
|
54
|
-
|
|
55
|
-
mergeInsightsDistinct(installs.map((id) => addInsightsDistinct(null, id))),
|
|
56
|
-
);
|
|
53
|
+
countDistinct(mergeDistinct(installs.map((id) => addDistinct(null, id))));
|
|
57
54
|
const installs = Array.from({ length: 24 }, (_, n) => `install-${n + 1}`);
|
|
58
55
|
|
|
59
56
|
/** Rows each table returned, and the point reads it served. */
|
|
@@ -108,17 +105,14 @@ describe("insights latest events by bundle", () => {
|
|
|
108
105
|
types: ["UNCHANGED", "UPDATE_APPLIED", "RECOVERED"],
|
|
109
106
|
},
|
|
110
107
|
] as const;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}),
|
|
120
|
-
).resolves.toBe(4);
|
|
121
|
-
}
|
|
108
|
+
await expect(
|
|
109
|
+
harness.api.countLatestEvents({
|
|
110
|
+
platform: "ios",
|
|
111
|
+
channel: "production",
|
|
112
|
+
sinceMs: T0,
|
|
113
|
+
bundle,
|
|
114
|
+
}),
|
|
115
|
+
).resolves.toBe(4);
|
|
122
116
|
});
|
|
123
117
|
|
|
124
118
|
it("counts a head once when a from and a to predicate of one type both match it", async () => {
|
|
@@ -152,29 +146,48 @@ describe("insights latest events by bundle", () => {
|
|
|
152
146
|
sinceMs,
|
|
153
147
|
bundle,
|
|
154
148
|
});
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
149
|
+
// From A or to B: A→B, A→C, D→B, and B→B. The gauges alone say 5,
|
|
150
|
+
// counting A→B under both predicates.
|
|
151
|
+
await expect(
|
|
152
|
+
count(
|
|
153
|
+
[
|
|
154
|
+
{ field: "from_bundle_id", value: "A", types },
|
|
155
|
+
{ field: "to_bundle_id", value: "B", types },
|
|
156
|
+
],
|
|
157
|
+
T0,
|
|
158
|
+
),
|
|
159
|
+
).resolves.toBe(4);
|
|
160
|
+
// B→B matches from B and to B through one pair: A→B, D→B, B→B.
|
|
161
|
+
await expect(
|
|
162
|
+
count(
|
|
163
|
+
[
|
|
164
|
+
{ field: "from_bundle_id", value: "B", types },
|
|
165
|
+
{ field: "to_bundle_id", value: "B", types },
|
|
166
|
+
],
|
|
167
|
+
T0,
|
|
168
|
+
),
|
|
169
|
+
).resolves.toBe(3);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("counts whole UTC days only, and rejects a start inside one", async () => {
|
|
173
|
+
const harness = await setup();
|
|
174
|
+
await harness.api.recordEvent(event(1, { received_at_ms: halfPast }));
|
|
175
|
+
await harness.api.recordEvent(
|
|
176
|
+
event(2, { received_at_ms: T0 + DAY + HOUR }),
|
|
177
|
+
);
|
|
178
|
+
const since = (sinceMs: number) =>
|
|
179
|
+
harness.api.countLatestEvents({
|
|
180
|
+
platform: "ios",
|
|
181
|
+
channel: "production",
|
|
182
|
+
sinceMs,
|
|
183
|
+
});
|
|
184
|
+
await expect(since(T0)).resolves.toBe(2);
|
|
185
|
+
await expect(since(T0 + DAY)).resolves.toBe(1);
|
|
186
|
+
await expect(since(T0 + 2 * DAY)).resolves.toBe(0);
|
|
187
|
+
for (const sinceMs of [T0 + 1, T0 + HOUR]) {
|
|
188
|
+
await expect(since(sinceMs)).rejects.toMatchObject({
|
|
189
|
+
code: "invalid-query",
|
|
190
|
+
});
|
|
178
191
|
}
|
|
179
192
|
});
|
|
180
193
|
});
|
|
@@ -241,7 +254,7 @@ describe("insights read budgets", () => {
|
|
|
241
254
|
expect(user.tables).toEqual({ bundle_event_heads: 3 });
|
|
242
255
|
});
|
|
243
256
|
|
|
244
|
-
it("counts whole
|
|
257
|
+
it("counts whole hours and whole UTC days from aggregates alone", async () => {
|
|
245
258
|
const { api, read } = await setup();
|
|
246
259
|
const counted = await read(() =>
|
|
247
260
|
api.countEvents({
|
|
@@ -262,11 +275,11 @@ describe("insights read budgets", () => {
|
|
|
262
275
|
api.countLatestEvents({
|
|
263
276
|
platform: "ios",
|
|
264
277
|
channel: "production",
|
|
265
|
-
sinceMs: T0 +
|
|
278
|
+
sinceMs: T0 + DAY,
|
|
266
279
|
}),
|
|
267
280
|
);
|
|
268
|
-
//
|
|
269
|
-
expect(latest.result).toBe(
|
|
281
|
+
// install-1, whose head moved to day 2
|
|
282
|
+
expect(latest.result).toBe(1);
|
|
270
283
|
expect(Object.keys(latest.tables)).toEqual(["insights_distribution"]);
|
|
271
284
|
|
|
272
285
|
const byBundle = await read(() =>
|
|
@@ -301,7 +314,9 @@ describe("insights read budgets", () => {
|
|
|
301
314
|
launches: 25,
|
|
302
315
|
failedLaunches: 0,
|
|
303
316
|
});
|
|
304
|
-
expect(Object.keys(lifetime.tables)).toEqual([
|
|
317
|
+
expect(Object.keys(lifetime.tables)).toEqual([
|
|
318
|
+
"insights_overview_lifetime",
|
|
319
|
+
]);
|
|
305
320
|
|
|
306
321
|
const scope = await read(() =>
|
|
307
322
|
api.getReleaseActivity({
|
|
@@ -317,9 +332,10 @@ describe("insights read budgets", () => {
|
|
|
317
332
|
{ startMs: T0 + 2 * DAY, launches: 1, failedLaunches: 0 },
|
|
318
333
|
],
|
|
319
334
|
});
|
|
335
|
+
// Whole UTC days: the daily rollups alone.
|
|
320
336
|
expect(Object.keys(scope.tables).toSorted()).toEqual([
|
|
321
|
-
"
|
|
322
|
-
"
|
|
337
|
+
"insights_overview_daily",
|
|
338
|
+
"insights_sketches_daily",
|
|
323
339
|
]);
|
|
324
340
|
|
|
325
341
|
const usage = await read(() =>
|
|
@@ -349,9 +365,281 @@ describe("insights read budgets", () => {
|
|
|
349
365
|
});
|
|
350
366
|
expect(Object.keys(usage.tables).toSorted()).toEqual([
|
|
351
367
|
"insights_distribution",
|
|
368
|
+
"insights_sketches_daily",
|
|
369
|
+
]);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe("insights update failures", () => {
|
|
374
|
+
const setup = async () => {
|
|
375
|
+
const meter = metered(createMemoryAdapter());
|
|
376
|
+
const harness = await createPluginTestHarness(insights(), {
|
|
377
|
+
engine,
|
|
378
|
+
adapter: meter.adapter,
|
|
379
|
+
now: () => T0 + 30 * DAY,
|
|
380
|
+
});
|
|
381
|
+
const record = (
|
|
382
|
+
n: number,
|
|
383
|
+
install: number,
|
|
384
|
+
at: number,
|
|
385
|
+
overrides: Partial<BundleEventRow>,
|
|
386
|
+
metadata: Record<string, unknown> = {},
|
|
387
|
+
) =>
|
|
388
|
+
harness.api.recordEvent(
|
|
389
|
+
event(n, {
|
|
390
|
+
install_id: `install-${install}`,
|
|
391
|
+
received_at_ms: at,
|
|
392
|
+
...overrides,
|
|
393
|
+
metadata: { ...event(n).metadata, ...metadata },
|
|
394
|
+
} as Partial<BundleEventRow>),
|
|
395
|
+
);
|
|
396
|
+
const fail = (
|
|
397
|
+
n: number,
|
|
398
|
+
install: number,
|
|
399
|
+
at: number,
|
|
400
|
+
failure: Record<string, unknown>,
|
|
401
|
+
) =>
|
|
402
|
+
record(
|
|
403
|
+
n,
|
|
404
|
+
install,
|
|
405
|
+
at,
|
|
406
|
+
failure.stage === "check"
|
|
407
|
+
? {
|
|
408
|
+
type: "UPDATE_FAILED",
|
|
409
|
+
to_release_id: null,
|
|
410
|
+
to_bundle_id: "bundle-a",
|
|
411
|
+
}
|
|
412
|
+
: { type: "UPDATE_FAILED" },
|
|
413
|
+
{ failure },
|
|
414
|
+
);
|
|
415
|
+
const denied = { stage: "download", reason: "http", http_status: 403 };
|
|
416
|
+
// install-1 fails to download release-b on two days, install-2 once with
|
|
417
|
+
// another origin code, install-3 on the network, install-4 installing.
|
|
418
|
+
await fail(1, 1, T0 + HOUR, { ...denied, origin_code: "AccessDenied" });
|
|
419
|
+
await fail(2, 1, T0 + DAY + HOUR, {
|
|
420
|
+
...denied,
|
|
421
|
+
origin_code: "AccessDenied",
|
|
422
|
+
});
|
|
423
|
+
await fail(3, 2, T0 + 2 * HOUR, { ...denied, origin_code: "ExpiredToken" });
|
|
424
|
+
await fail(4, 3, T0 + 3 * HOUR, {
|
|
425
|
+
stage: "download",
|
|
426
|
+
reason: "network",
|
|
427
|
+
transport: "timeout",
|
|
428
|
+
});
|
|
429
|
+
await fail(5, 4, T0 + 4 * HOUR, {
|
|
430
|
+
stage: "install",
|
|
431
|
+
reason: "hash_mismatch",
|
|
432
|
+
});
|
|
433
|
+
// install-8's update check fails: its channel's, no release's.
|
|
434
|
+
await fail(6, 8, T0 + 5 * HOUR, { stage: "check", reason: "http" });
|
|
435
|
+
// Three downloads: two by patch, one that fell back from a patch.
|
|
436
|
+
for (const [n, install, metadata] of [
|
|
437
|
+
[7, 5, { delivery: "patch" }],
|
|
438
|
+
[8, 6, { delivery: "patch" }],
|
|
439
|
+
[9, 7, { delivery: "archive", patch_fallback: true }],
|
|
440
|
+
] as const) {
|
|
441
|
+
await record(
|
|
442
|
+
n,
|
|
443
|
+
install,
|
|
444
|
+
T0 + 6 * HOUR,
|
|
445
|
+
{ type: "UPDATE_DOWNLOADED" },
|
|
446
|
+
metadata,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
// Two recoveries from release-b, one with an exit reason.
|
|
450
|
+
const recovered = {
|
|
451
|
+
type: "RECOVERED",
|
|
452
|
+
from_release_id: "release-b",
|
|
453
|
+
from_bundle_id: "bundle-b",
|
|
454
|
+
to_release_id: "release-a",
|
|
455
|
+
to_bundle_id: "bundle-a",
|
|
456
|
+
} as const;
|
|
457
|
+
await record(10, 9, T0 + 7 * HOUR, recovered, {
|
|
458
|
+
previous_process_exit: "CRASH",
|
|
459
|
+
});
|
|
460
|
+
await record(11, 10, T0 + 7 * HOUR, recovered);
|
|
461
|
+
const read = async <T>(call: () => Promise<T>) => {
|
|
462
|
+
meter.rows.clear();
|
|
463
|
+
const measured = await harness.measureReads(call);
|
|
464
|
+
return {
|
|
465
|
+
...measured,
|
|
466
|
+
tables: Object.keys(Object.fromEntries(meter.rows)),
|
|
467
|
+
};
|
|
468
|
+
};
|
|
469
|
+
return { api: harness.api, read };
|
|
470
|
+
};
|
|
471
|
+
const scope = { platform: "ios", channel: "production" } as const;
|
|
472
|
+
|
|
473
|
+
it("reads a release's failures, installations, and breakdown over a window from aggregates only", async () => {
|
|
474
|
+
const { api, read } = await setup();
|
|
475
|
+
const failures = await read(() =>
|
|
476
|
+
api.getUpdateFailures({
|
|
477
|
+
...scope,
|
|
478
|
+
releaseId: "release-b",
|
|
479
|
+
timeRange: { start: T0, end: T0 + 2 * DAY },
|
|
480
|
+
}),
|
|
481
|
+
);
|
|
482
|
+
expect(failures.result).toEqual({
|
|
483
|
+
coverage: { kind: "complete", sinceMs: 0 },
|
|
484
|
+
measuredAtMs: T0 + 30 * DAY,
|
|
485
|
+
failedUpdates: 5,
|
|
486
|
+
failedInstallations: distinct([
|
|
487
|
+
"install-1",
|
|
488
|
+
"install-2",
|
|
489
|
+
"install-3",
|
|
490
|
+
"install-4",
|
|
491
|
+
]),
|
|
492
|
+
downloads: 3,
|
|
493
|
+
patchDownloads: 2,
|
|
494
|
+
patchFallbacks: 1,
|
|
495
|
+
breakdown: [
|
|
496
|
+
{
|
|
497
|
+
stage: "download",
|
|
498
|
+
reason: "http",
|
|
499
|
+
events: 3,
|
|
500
|
+
details: [
|
|
501
|
+
{
|
|
502
|
+
resource: null,
|
|
503
|
+
httpStatus: 403,
|
|
504
|
+
originCode: "AccessDenied",
|
|
505
|
+
transport: null,
|
|
506
|
+
events: 2,
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
resource: null,
|
|
510
|
+
httpStatus: 403,
|
|
511
|
+
originCode: "ExpiredToken",
|
|
512
|
+
transport: null,
|
|
513
|
+
events: 1,
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
stage: "download",
|
|
519
|
+
reason: "network",
|
|
520
|
+
events: 1,
|
|
521
|
+
details: [
|
|
522
|
+
{
|
|
523
|
+
resource: null,
|
|
524
|
+
httpStatus: null,
|
|
525
|
+
originCode: null,
|
|
526
|
+
transport: "timeout",
|
|
527
|
+
events: 1,
|
|
528
|
+
},
|
|
529
|
+
],
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
stage: "install",
|
|
533
|
+
reason: "hash_mismatch",
|
|
534
|
+
events: 1,
|
|
535
|
+
details: [
|
|
536
|
+
{
|
|
537
|
+
resource: null,
|
|
538
|
+
httpStatus: null,
|
|
539
|
+
originCode: null,
|
|
540
|
+
transport: null,
|
|
541
|
+
events: 1,
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
recoveries: {
|
|
547
|
+
failedLaunches: 2,
|
|
548
|
+
byExitReason: [{ exitReason: "CRASH", events: 1 }],
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
expect(failures.tables.toSorted()).toEqual([
|
|
552
|
+
"insights_failures",
|
|
553
|
+
"insights_overview",
|
|
352
554
|
"insights_sketches",
|
|
353
555
|
]);
|
|
354
556
|
});
|
|
557
|
+
|
|
558
|
+
it("reads a channel's failed checks against its active installations, from daily rows past 48 hours", async () => {
|
|
559
|
+
const { api, read } = await setup();
|
|
560
|
+
const failures = await read(() =>
|
|
561
|
+
api.getUpdateFailures({
|
|
562
|
+
...scope,
|
|
563
|
+
timeRange: { start: T0, end: T0 + 3 * DAY },
|
|
564
|
+
}),
|
|
565
|
+
);
|
|
566
|
+
expect(failures.result).toMatchObject({
|
|
567
|
+
failedUpdates: 5,
|
|
568
|
+
failedInstallations: distinct([
|
|
569
|
+
"install-1",
|
|
570
|
+
"install-2",
|
|
571
|
+
"install-3",
|
|
572
|
+
"install-4",
|
|
573
|
+
]),
|
|
574
|
+
downloads: 3,
|
|
575
|
+
// A failure is no activity: the downloads' and recoveries' installations.
|
|
576
|
+
checks: {
|
|
577
|
+
failures: 1,
|
|
578
|
+
failedInstallations: distinct(["install-8"]),
|
|
579
|
+
activeInstallations: distinct([
|
|
580
|
+
"install-5",
|
|
581
|
+
"install-6",
|
|
582
|
+
"install-7",
|
|
583
|
+
"install-9",
|
|
584
|
+
"install-10",
|
|
585
|
+
]),
|
|
586
|
+
},
|
|
587
|
+
recoveries: { failedLaunches: 2 },
|
|
588
|
+
});
|
|
589
|
+
expect(
|
|
590
|
+
failures.result.breakdown!.map(({ stage, reason, events }) => [
|
|
591
|
+
stage,
|
|
592
|
+
reason,
|
|
593
|
+
events,
|
|
594
|
+
]),
|
|
595
|
+
).toEqual([
|
|
596
|
+
["download", "http", 3],
|
|
597
|
+
["check", "http", 1],
|
|
598
|
+
["download", "network", 1],
|
|
599
|
+
["install", "hash_mismatch", 1],
|
|
600
|
+
]);
|
|
601
|
+
expect(failures.tables.toSorted()).toEqual([
|
|
602
|
+
"insights_failures",
|
|
603
|
+
"insights_overview_daily",
|
|
604
|
+
"insights_sketches_daily",
|
|
605
|
+
]);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it("reads a release's failures since its first from the kept rows, and needs a range of at most 30 days for a channel", async () => {
|
|
609
|
+
const { api, read } = await setup();
|
|
610
|
+
const lifetime = await read(() =>
|
|
611
|
+
api.getUpdateFailures({ ...scope, releaseId: "release-b" }),
|
|
612
|
+
);
|
|
613
|
+
expect(lifetime.result).toEqual({
|
|
614
|
+
coverage: { kind: "complete", sinceMs: 0 },
|
|
615
|
+
measuredAtMs: T0 + 30 * DAY,
|
|
616
|
+
failedUpdates: 5,
|
|
617
|
+
failedInstallations: distinct([
|
|
618
|
+
"install-1",
|
|
619
|
+
"install-2",
|
|
620
|
+
"install-3",
|
|
621
|
+
"install-4",
|
|
622
|
+
]),
|
|
623
|
+
downloads: 3,
|
|
624
|
+
patchDownloads: 2,
|
|
625
|
+
patchFallbacks: 1,
|
|
626
|
+
});
|
|
627
|
+
expect(lifetime.tables.toSorted()).toEqual([
|
|
628
|
+
"insights_overview_lifetime",
|
|
629
|
+
"insights_sketches_lifetime",
|
|
630
|
+
]);
|
|
631
|
+
for (const invalid of [
|
|
632
|
+
scope,
|
|
633
|
+
{ ...scope, platform: "web" },
|
|
634
|
+
{ ...scope, releaseId: "release-b", timeRange: { start: 10, end: 10 } },
|
|
635
|
+
// Longer than the console's longest period, 30 days.
|
|
636
|
+
{ ...scope, timeRange: { start: T0, end: T0 + 30 * DAY + 1 } },
|
|
637
|
+
]) {
|
|
638
|
+
await expect(
|
|
639
|
+
api.getUpdateFailures(invalid as never),
|
|
640
|
+
).rejects.toMatchObject({ code: "invalid-query" });
|
|
641
|
+
}
|
|
642
|
+
});
|
|
355
643
|
});
|
|
356
644
|
|
|
357
645
|
/**
|