@player-ui/metrics-plugin 0.8.0--canary.307.9645 → 0.8.0-next.1
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/MetricsPlugin.native.js +8078 -0
- package/dist/MetricsPlugin.native.js.map +1 -0
- package/dist/{index.cjs.js → cjs/index.cjs} +137 -124
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/{index.esm.js → index.legacy-esm.js} +96 -106
- package/dist/index.mjs +330 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +23 -60
- package/src/index.test.ts +271 -0
- package/src/index.ts +2 -2
- package/src/metrics.ts +35 -35
- package/src/symbols.ts +2 -2
- package/types/index.d.ts +3 -0
- package/{dist/index.d.ts → types/metrics.d.ts} +16 -19
- package/types/symbols.d.ts +3 -0
- package/dist/metrics-plugin.dev.js +0 -11110
- package/dist/metrics-plugin.prod.js +0 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/metrics/core/src/metrics.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/metrics/core/src/symbols.ts"],"sourcesContent":["import type { Player, PlayerPlugin } from \"@player-ui/player\";\nimport { SyncHook, SyncBailHook } from \"tapable-ts\";\nimport type { BeaconPluginPlugin, BeaconArgs } from \"@player-ui/beacon-plugin\";\nimport { BeaconPlugin } from \"@player-ui/beacon-plugin\";\nimport {\n MetricsCorePluginSymbol,\n MetricsViewBeaconPluginContextSymbol,\n} from \"./symbols\";\n\n// Try to use performance.now() but fall back to Date.now() if you can't\nexport const defaultGetTime =\n typeof performance === \"undefined\"\n ? () => Date.now()\n : () => performance.now();\n\nexport type Timing = {\n /** Time this duration started (ms) */\n startTime: number;\n} & (\n | {\n /** Flag set if this is currently in progress */\n completed: false;\n }\n | {\n /** The stopwatch has stopped */\n completed: true;\n\n /** The time in (ms) that the process ended */\n endTime: number;\n\n /** The elapsed time of this event (ms) */\n duration: number;\n }\n);\n\nexport type NodeMetrics = Timing & {\n /** The type of the flow-state */\n stateType: string;\n\n /** The name of the flow-state */\n stateName: string;\n};\n\nexport type NodeRenderMetrics = NodeMetrics & {\n /** Timing representing the initial render */\n render: Timing;\n\n /** An array of timings representing updates to the view */\n updates: Array<Timing>;\n};\n\nexport interface PlayerFlowMetrics {\n /** All metrics about a running flow */\n flow?: {\n /** The id of the flow these metrics are for */\n id: string;\n\n /** request time */\n requestTime?: number;\n\n /** A timeline of events for each node-state */\n timeline: Array<NodeMetrics | NodeRenderMetrics>;\n\n /** A timing measuring until the first interactive render */\n interactive: Timing;\n } & Timing;\n}\n\nconst callbacks = [\n \"onFlowBegin\",\n \"onFlowEnd\",\n \"onInteractive\",\n \"onNodeStart\",\n \"onNodeEnd\",\n \"onRenderStart\",\n \"onRenderEnd\",\n \"onUpdateStart\",\n \"onUpdateEnd\",\n \"onUpdate\",\n] as const;\n\n/** Context structure for 'viewed' beacons rendering metrics */\nexport interface MetricsViewBeaconPluginContext {\n /** Represents the time taken before the view is first rendered */\n renderTime?: number;\n /** request time */\n requestTime?: number;\n}\n\n/** Simple [BeaconPluginPlugin] that adds renderTime to 'viewed' beacons data */\nexport class MetricsViewBeaconPlugin implements BeaconPluginPlugin {\n static Symbol = MetricsViewBeaconPluginContextSymbol;\n public readonly symbol = MetricsViewBeaconPlugin.Symbol;\n\n private metricsPlugin: MetricsCorePlugin;\n\n private resolvePendingRenderTime: ((renderTime: number) => void) | undefined;\n\n constructor(metricsPlugin: MetricsCorePlugin) {\n this.metricsPlugin = metricsPlugin;\n this.metricsPlugin.hooks.onRenderEnd.tap(\n \"MetricsViewBeaconPlugin\",\n (timing) => {\n if (timing.completed && this.resolvePendingRenderTime) {\n this.resolvePendingRenderTime(timing.duration);\n this.resolvePendingRenderTime = undefined;\n }\n },\n );\n }\n\n apply(beaconPlugin: BeaconPlugin) {\n beaconPlugin.hooks.buildBeacon.intercept({\n context: true,\n call: (context: any, beacon) => {\n if (context && (beacon as BeaconArgs).action === \"viewed\") {\n context[this.symbol] = this.buildContext();\n }\n },\n });\n }\n\n private async buildContext(): Promise<MetricsViewBeaconPluginContext> {\n return {\n renderTime: await this.getRenderTime(),\n requestTime: this.getRequestTime(),\n };\n }\n\n private async getRenderTime(): Promise<number> {\n const { flow } = this.metricsPlugin.getMetrics();\n\n if (flow) {\n const lastItem = flow.timeline[flow.timeline.length - 1];\n\n if (\"render\" in lastItem && lastItem.render.completed) {\n return lastItem.render.duration;\n }\n }\n\n return new Promise((resolve) => {\n this.resolvePendingRenderTime = resolve;\n });\n }\n\n private getRequestTime(): number | undefined {\n const { flow } = this.metricsPlugin.getMetrics();\n\n return flow?.requestTime;\n }\n}\n\nexport interface MetricsWebPluginOptions {\n /** Called when a flow starts */\n onFlowBegin?: (update: PlayerFlowMetrics) => void;\n\n /** Called when a flow ends */\n onFlowEnd?: (update: PlayerFlowMetrics) => void;\n\n /** Called when a flow becomes interactive for the first time */\n onInteractive?: (timing: Timing, update: PlayerFlowMetrics) => void;\n\n /** Called when a new node is started */\n onNodeStart?: (\n nodeMetrics: NodeMetrics | NodeRenderMetrics,\n update: PlayerFlowMetrics,\n ) => void;\n\n /** Called when a node is ended */\n onNodeEnd?: (\n nodeMetrics: NodeMetrics | NodeRenderMetrics,\n update: PlayerFlowMetrics,\n ) => void;\n\n /** Called when rendering for a node begins */\n onRenderStart?: (\n timing: Timing,\n nodeMetrics: NodeRenderMetrics,\n update: PlayerFlowMetrics,\n ) => void;\n\n /** Called when rendering for a node ends */\n onRenderEnd?: (\n timing: Timing,\n nodeMetrics: NodeRenderMetrics,\n update: PlayerFlowMetrics,\n ) => void;\n\n /** Called when an update for a node begins */\n onUpdateStart?: (\n timing: Timing,\n nodeMetrics: NodeRenderMetrics,\n update: PlayerFlowMetrics,\n ) => void;\n\n /** Called when an update for a node ends */\n onUpdateEnd?: (\n timing: Timing,\n nodeMetrics: NodeRenderMetrics,\n update: PlayerFlowMetrics,\n ) => void;\n\n /** Callback to subscribe to updates for any metric */\n onUpdate?: (metrics: PlayerFlowMetrics) => void;\n\n /**\n * A flag to set if you want to track render times for nodes\n * This requires that the UI calls `renderEnd()` when the view is painted.\n */\n trackRenderTime?: boolean;\n\n /**\n * A flag to set if you want to track update times for nodes\n * This requires that the UI calls `renderEnd()` when the view is painted.\n */\n trackUpdateTime?: boolean;\n\n /** A function to get the current time (in ms) */\n getTime?: () => number;\n}\n\n/**\n * A plugin that enables request time metrics\n */\nexport class RequestTimeWebPlugin {\n getRequestTime: () => number | undefined;\n name = \"RequestTimeWebPlugin\";\n\n constructor(getRequestTime: () => number | undefined) {\n this.getRequestTime = getRequestTime;\n }\n\n apply(metricsCorePlugin: MetricsCorePlugin) {\n metricsCorePlugin.hooks.resolveRequestTime.tap(this.name, () => {\n return this.getRequestTime();\n });\n }\n}\n\n/**\n * A plugin that enables gathering of render metrics\n */\nexport class MetricsCorePlugin implements PlayerPlugin {\n name = \"metrics\";\n\n static Symbol = MetricsCorePluginSymbol;\n public readonly symbol = MetricsCorePluginSymbol;\n\n protected trackRender: boolean;\n protected trackUpdate: boolean;\n protected getTime: () => number;\n\n public readonly hooks = {\n resolveRequestTime: new SyncBailHook<[], number>(),\n\n onFlowBegin: new SyncHook<[PlayerFlowMetrics]>(),\n onFlowEnd: new SyncHook<[PlayerFlowMetrics]>(),\n\n onInteractive: new SyncHook<[Timing, PlayerFlowMetrics]>(),\n\n onNodeStart: new SyncHook<[NodeMetrics | NodeRenderMetrics]>(),\n onNodeEnd: new SyncHook<[NodeMetrics | NodeRenderMetrics]>(),\n\n onRenderStart: new SyncHook<\n [Timing, NodeRenderMetrics, PlayerFlowMetrics]\n >(),\n onRenderEnd: new SyncHook<[Timing, NodeRenderMetrics, PlayerFlowMetrics]>(),\n\n onUpdateStart: new SyncHook<\n [Timing, NodeRenderMetrics, PlayerFlowMetrics]\n >(),\n onUpdateEnd: new SyncHook<[Timing, NodeRenderMetrics, PlayerFlowMetrics]>(),\n\n onUpdate: new SyncHook<[PlayerFlowMetrics]>(),\n };\n\n private metrics: PlayerFlowMetrics = {};\n\n constructor(options?: MetricsWebPluginOptions) {\n this.trackRender = options?.trackRenderTime ?? false;\n this.trackUpdate = options?.trackUpdateTime ?? false;\n this.getTime = options?.getTime ?? defaultGetTime;\n\n /** fn to call the update hook */\n const callOnUpdate = () => {\n this.hooks.onUpdate.call(this.metrics);\n };\n\n this.hooks.onFlowBegin.tap(this.name, callOnUpdate);\n this.hooks.onFlowEnd.tap(this.name, callOnUpdate);\n this.hooks.onInteractive.tap(this.name, callOnUpdate);\n this.hooks.onNodeStart.tap(this.name, callOnUpdate);\n this.hooks.onNodeEnd.tap(this.name, callOnUpdate);\n\n this.hooks.onRenderStart.tap(this.name, callOnUpdate);\n this.hooks.onRenderEnd.tap(this.name, callOnUpdate);\n\n this.hooks.onUpdateStart.tap(this.name, callOnUpdate);\n this.hooks.onUpdateEnd.tap(this.name, callOnUpdate);\n\n callbacks.forEach((hookName) => {\n if (options?.[hookName] !== undefined) {\n (this.hooks[hookName] as any).tap(\"options\", options?.[hookName]);\n }\n });\n }\n\n /**\n * Fetch the metrics of the current flow\n */\n public getMetrics(): PlayerFlowMetrics {\n return this.metrics;\n }\n\n /** Called when the UI layer wishes to start a timer for rendering */\n private renderStart(): void {\n // Grab the last update\n const timeline = this.metrics.flow?.timeline;\n\n if (!timeline || timeline.length === 0) {\n return;\n }\n\n const lastItem = timeline[timeline.length - 1];\n\n if (\"updates\" in lastItem) {\n // Get the last update, make sure it's completed\n if (lastItem.updates.length > 0) {\n const lastUpdate = lastItem.updates[lastItem.updates.length - 1];\n\n if (lastUpdate.completed === false) {\n // Starting a new render before the last one was finished.\n // Just ignore it and include as part of 1 render time\n return;\n }\n }\n\n if (!lastItem.render.completed) {\n // Starting a new render before the last one was finished.\n // Just ignore it and include as part of 1 render time\n return;\n }\n\n const update: Timing = {\n completed: false,\n startTime: defaultGetTime(),\n };\n\n lastItem.updates.push(update);\n\n this.hooks.onUpdateStart.call(update, lastItem, this.metrics);\n } else {\n const renderInfo = {\n ...lastItem,\n render: {\n completed: false,\n startTime: defaultGetTime(),\n },\n updates: [],\n } as NodeRenderMetrics;\n\n timeline[timeline.length - 1] = renderInfo;\n\n this.hooks.onRenderStart.call(\n renderInfo.render,\n renderInfo,\n this.metrics,\n );\n }\n }\n\n /** Called when the UI layer wants to end the rendering timer */\n public renderEnd(): void {\n if (!this.trackRender) {\n throw new Error(\n \"Must start the metrics-plugin with render tracking enabled\",\n );\n }\n\n const { flow } = this.metrics;\n\n if (!flow) {\n return;\n }\n\n const { timeline, interactive } = flow;\n\n if (!timeline || !interactive || timeline.length === 0) {\n return;\n }\n\n const lastItem = timeline[timeline.length - 1];\n\n if (!(\"render\" in lastItem)) {\n return;\n }\n\n // Check if this is an update or render\n const endTime = defaultGetTime();\n\n if (lastItem.render.completed) {\n // This is the end of an existing update\n\n if (lastItem.updates.length === 0) {\n // throw new Error(\"Trying to end an update that's not in progress\");\n return;\n }\n\n const lastUpdate = lastItem.updates[lastItem.updates.length - 1];\n\n if (lastUpdate.completed === true) {\n // throw new Error(\"Trying to end an update that's not in progress\");\n return;\n }\n\n const update = {\n ...lastUpdate,\n completed: true,\n endTime,\n duration: endTime - lastUpdate.startTime,\n };\n\n lastItem.updates[lastItem.updates.length - 1] = update;\n this.hooks.onUpdateEnd.call(update, lastItem, this.metrics);\n } else {\n lastItem.render = {\n ...lastItem.render,\n completed: true,\n endTime,\n duration: endTime - lastItem.startTime,\n };\n this.hooks.onRenderEnd.call(lastItem.render, lastItem, this.metrics);\n\n if (!interactive.completed) {\n flow.interactive = {\n ...interactive,\n completed: true,\n duration: endTime - interactive.startTime,\n endTime,\n };\n\n this.hooks.onInteractive.call(flow.interactive, this.metrics);\n }\n }\n }\n\n apply(player: Player): void {\n player.hooks.onStart.tap(this.name, (flow) => {\n const requestTime = this.hooks.resolveRequestTime.call();\n const startTime = defaultGetTime();\n this.metrics = {\n flow: {\n id: flow.id,\n requestTime: requestTime ?? undefined,\n timeline: [],\n startTime,\n completed: false,\n interactive: {\n completed: false,\n startTime,\n },\n },\n };\n\n this.hooks.onFlowBegin.call(this.metrics);\n });\n\n player.hooks.state.tap(this.name, (state) => {\n if (state.status === \"completed\" || state.status === \"error\") {\n const endTime = defaultGetTime();\n const { flow } = this.metrics;\n\n if (flow === undefined || flow?.completed === true) {\n return;\n }\n\n this.metrics = {\n flow: {\n ...flow,\n completed: true,\n endTime,\n duration: endTime - flow.startTime,\n },\n };\n\n // get the last update\n\n const lastUpdate = flow.timeline[flow.timeline.length - 1];\n\n if (lastUpdate && !lastUpdate.completed) {\n (this.metrics.flow as any).timeline[flow.timeline.length - 1] = {\n ...lastUpdate,\n completed: true,\n endTime,\n duration: endTime - lastUpdate.startTime,\n };\n }\n\n this.hooks.onFlowEnd.call(this.metrics);\n }\n });\n\n player.hooks.flowController.tap(this.name, (fc) => {\n fc.hooks.flow.tap(this.name, (f) => {\n f.hooks.transition.tap(this.name, (from, to) => {\n const time = defaultGetTime();\n const { flow } = this.metrics;\n\n if (!flow) {\n return;\n }\n\n const { timeline } = flow;\n\n // End the last state, and start the next one\n\n if (timeline.length > 0) {\n const prev = timeline[timeline.length - 1];\n\n if (prev.completed) {\n throw new Error(\"Completing a state that's already done.\");\n }\n\n timeline[timeline.length - 1] = {\n ...prev,\n completed: true,\n endTime: time,\n duration: time - prev.startTime,\n };\n\n this.hooks.onNodeEnd.call(timeline[timeline.length - 1]);\n }\n\n const nodeMetrics = {\n completed: false,\n startTime: time,\n stateName: to.name,\n stateType: to.value.state_type,\n } as const;\n\n timeline.push(nodeMetrics);\n this.hooks.onNodeStart.call(nodeMetrics);\n });\n });\n });\n\n if (this.trackRender) {\n player.hooks.view.tap(this.name, (v) => {\n if (this.trackUpdate) {\n v.hooks.onUpdate.tap(this.name, () => {\n this.renderStart();\n });\n } else {\n this.renderStart();\n }\n });\n\n player.applyTo<BeaconPlugin>(BeaconPlugin.Symbol, (beaconPlugin) =>\n new MetricsViewBeaconPlugin(this).apply(beaconPlugin),\n );\n }\n }\n}\n\nexport default MetricsCorePlugin;\n","export const MetricsCorePluginSymbol = Symbol.for(\"MetricsCorePlugin\");\nexport const MetricsViewBeaconPluginContextSymbol = Symbol.for(\n \"MetricsViewBeaconPluginContext\",\n);\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,68 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@player-ui/metrics-plugin",
|
|
3
|
-
"version": "0.8.0
|
|
4
|
-
"
|
|
5
|
-
"publishConfig": {
|
|
6
|
-
"registry": "https://registry.npmjs.org"
|
|
7
|
-
},
|
|
8
|
-
"peerDependencies": {
|
|
9
|
-
"@player-ui/player": "0.8.0--canary.307.9645"
|
|
10
|
-
},
|
|
3
|
+
"version": "0.8.0-next.1",
|
|
4
|
+
"main": "dist/cjs/index.cjs",
|
|
11
5
|
"dependencies": {
|
|
12
|
-
"@player-ui/beacon-plugin": "0.8.0
|
|
6
|
+
"@player-ui/beacon-plugin": "0.8.0-next.1",
|
|
13
7
|
"tapable-ts": "^0.2.3",
|
|
14
|
-
"
|
|
15
|
-
},
|
|
16
|
-
"main": "dist/index.cjs.js",
|
|
17
|
-
"module": "dist/index.esm.js",
|
|
18
|
-
"typings": "dist/index.d.ts",
|
|
19
|
-
"sideEffects": false,
|
|
20
|
-
"license": "MIT",
|
|
21
|
-
"repository": {
|
|
22
|
-
"type": "git",
|
|
23
|
-
"url": "https://github.com/player-ui/player-ui"
|
|
8
|
+
"tslib": "^2.6.2"
|
|
24
9
|
},
|
|
25
|
-
"
|
|
26
|
-
"
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"@player-ui/player": "0.8.0-next.1"
|
|
27
12
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"name": "Harris Borawski",
|
|
40
|
-
"url": "https://github.com/hborawski"
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"name": "Jeremiah Zucker",
|
|
44
|
-
"url": "https://github.com/sugarmanz"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"name": "Ketan Reddy",
|
|
48
|
-
"url": "https://github.com/KetanReddy"
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"name": "Brocollie08",
|
|
52
|
-
"url": "https://github.com/brocollie08"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"name": "Kelly Harrop",
|
|
56
|
-
"url": "https://github.com/kharrop"
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
"name": "Alejandro Fimbres",
|
|
60
|
-
"url": "https://github.com/lexfm"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"name": "Rafael Campos",
|
|
64
|
-
"url": "https://github.com/rafbcampos"
|
|
13
|
+
"module": "dist/index.legacy-esm.js",
|
|
14
|
+
"types": "types/index.d.ts",
|
|
15
|
+
"bundle": "dist/MetricsPlugin.native.js",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
"./dist/index.css": "./dist/index.css",
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./types/index.d.ts",
|
|
22
|
+
"import": "./dist/index.mjs",
|
|
23
|
+
"default": "./dist/cjs/index.cjs"
|
|
65
24
|
}
|
|
66
|
-
|
|
67
|
-
"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src",
|
|
29
|
+
"types"
|
|
30
|
+
]
|
|
68
31
|
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { vitest, test, expect } from "vitest";
|
|
2
|
+
import type { BeaconPluginPlugin } from "@player-ui/beacon-plugin";
|
|
3
|
+
import { BeaconPlugin } from "@player-ui/beacon-plugin";
|
|
4
|
+
import type { InProgressState, Flow } from "@player-ui/player";
|
|
5
|
+
import { Player } from "@player-ui/player";
|
|
6
|
+
import type { NodeRenderMetrics } from ".";
|
|
7
|
+
import {
|
|
8
|
+
MetricsCorePlugin,
|
|
9
|
+
MetricsViewBeaconPlugin,
|
|
10
|
+
RequestTimeWebPlugin,
|
|
11
|
+
} from ".";
|
|
12
|
+
|
|
13
|
+
const basicContentWithActions: Flow<any> = {
|
|
14
|
+
id: "test-flow",
|
|
15
|
+
views: [
|
|
16
|
+
{
|
|
17
|
+
id: "my-view",
|
|
18
|
+
actions: [
|
|
19
|
+
{
|
|
20
|
+
asset: {
|
|
21
|
+
id: "next-label-action",
|
|
22
|
+
type: "action",
|
|
23
|
+
value: "{{foo.bar}}",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
navigation: {
|
|
30
|
+
BEGIN: "FLOW_1",
|
|
31
|
+
FLOW_1: {
|
|
32
|
+
startState: "VIEW_1",
|
|
33
|
+
VIEW_1: {
|
|
34
|
+
state_type: "VIEW",
|
|
35
|
+
ref: "my-view",
|
|
36
|
+
transitions: {
|
|
37
|
+
"*": "OTHER_2",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
OTHER_2: {
|
|
41
|
+
state_type: "VIEW",
|
|
42
|
+
ref: "my-view",
|
|
43
|
+
transitions: {
|
|
44
|
+
"*": "END_Done",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
END_Done: {
|
|
48
|
+
state_type: "END",
|
|
49
|
+
outcome: "done",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
test("tracks metrics", () => {
|
|
56
|
+
const onUpdate = vitest.fn();
|
|
57
|
+
const metrics = new MetricsCorePlugin({ onUpdate });
|
|
58
|
+
const player = new Player({ plugins: [metrics] });
|
|
59
|
+
player.start(basicContentWithActions);
|
|
60
|
+
|
|
61
|
+
let flowMetrics = metrics.getMetrics();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
const transition = () => {
|
|
67
|
+
(player.getState() as InProgressState).controllers.flow.transition("Next");
|
|
68
|
+
flowMetrics = metrics.getMetrics();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
expect(onUpdate).toBeCalledTimes(2);
|
|
72
|
+
expect(flowMetrics.flow?.completed).toBe(false);
|
|
73
|
+
expect(flowMetrics.flow?.id).toBe("test-flow");
|
|
74
|
+
expect(flowMetrics.flow?.timeline[0]).toMatchObject({
|
|
75
|
+
stateName: "VIEW_1",
|
|
76
|
+
stateType: "VIEW",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
transition();
|
|
80
|
+
expect(onUpdate).toBeCalledTimes(4);
|
|
81
|
+
expect(flowMetrics.flow?.timeline[0].completed).toBe(true);
|
|
82
|
+
expect(flowMetrics.flow?.timeline).toHaveLength(2);
|
|
83
|
+
transition();
|
|
84
|
+
expect(onUpdate).toBeCalledTimes(6);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("tracks metrics w/ render time", () => {
|
|
88
|
+
const onRenderEnd = vitest.fn();
|
|
89
|
+
const metrics = new MetricsCorePlugin({
|
|
90
|
+
trackRenderTime: true,
|
|
91
|
+
trackUpdateTime: true,
|
|
92
|
+
onRenderEnd,
|
|
93
|
+
});
|
|
94
|
+
const player = new Player({ plugins: [metrics] });
|
|
95
|
+
player.start(basicContentWithActions);
|
|
96
|
+
metrics.renderEnd();
|
|
97
|
+
expect(onRenderEnd).toBeCalledTimes(1);
|
|
98
|
+
const flowMetrics = metrics.getMetrics();
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
*
|
|
102
|
+
*/
|
|
103
|
+
const transition = () => {
|
|
104
|
+
(player.getState() as InProgressState).controllers.flow.transition("Next");
|
|
105
|
+
|
|
106
|
+
const state = player.getState();
|
|
107
|
+
|
|
108
|
+
if (state.status === "in-progress") {
|
|
109
|
+
if (
|
|
110
|
+
state.controllers.flow.current?.currentState?.value.state_type ===
|
|
111
|
+
"VIEW"
|
|
112
|
+
) {
|
|
113
|
+
metrics.renderEnd();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
expect(flowMetrics.flow?.completed).toBe(false);
|
|
119
|
+
expect(flowMetrics.flow?.id).toBe("test-flow");
|
|
120
|
+
expect(flowMetrics.flow?.timeline[0]).toMatchObject({
|
|
121
|
+
stateName: "VIEW_1",
|
|
122
|
+
stateType: "VIEW",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
transition();
|
|
126
|
+
expect(flowMetrics.flow?.timeline[0].completed).toBe(true);
|
|
127
|
+
expect(flowMetrics.flow?.timeline).toHaveLength(2);
|
|
128
|
+
transition();
|
|
129
|
+
|
|
130
|
+
expect(
|
|
131
|
+
(flowMetrics.flow?.timeline[0] as NodeRenderMetrics).render.completed,
|
|
132
|
+
).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("handles double updates", async () => {
|
|
136
|
+
const onRenderEnd = vitest.fn();
|
|
137
|
+
const metrics = new MetricsCorePlugin({
|
|
138
|
+
trackRenderTime: true,
|
|
139
|
+
trackUpdateTime: true,
|
|
140
|
+
onRenderEnd,
|
|
141
|
+
});
|
|
142
|
+
const player = new Player({ plugins: [metrics] });
|
|
143
|
+
player.start(basicContentWithActions);
|
|
144
|
+
metrics.renderEnd();
|
|
145
|
+
expect(onRenderEnd).toBeCalledTimes(1);
|
|
146
|
+
/**
|
|
147
|
+
*
|
|
148
|
+
*/
|
|
149
|
+
const getDataController = () =>
|
|
150
|
+
(player.getState() as InProgressState).controllers.data;
|
|
151
|
+
getDataController().set([["foo.bar", "update-1"]]);
|
|
152
|
+
metrics.renderEnd(); // Adds 1 update
|
|
153
|
+
|
|
154
|
+
await vitest.waitFor(() =>
|
|
155
|
+
expect(
|
|
156
|
+
(metrics.getMetrics().flow?.timeline[0] as NodeRenderMetrics).updates,
|
|
157
|
+
).toHaveLength(1),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Don't send a render-end for the second
|
|
161
|
+
getDataController().set([["foo.bar", "update-2"]]);
|
|
162
|
+
getDataController().set([["foo.bar", "update-3"]]);
|
|
163
|
+
metrics.renderEnd(); // Adds another update
|
|
164
|
+
|
|
165
|
+
await vitest.waitFor(() =>
|
|
166
|
+
expect(
|
|
167
|
+
(metrics.getMetrics().flow?.timeline[0] as NodeRenderMetrics).updates,
|
|
168
|
+
).toHaveLength(2),
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
class MyBeaconPluginPlugin implements BeaconPluginPlugin {
|
|
173
|
+
apply(beaconPlugin: BeaconPlugin) {
|
|
174
|
+
beaconPlugin.hooks.buildBeacon.tap(
|
|
175
|
+
{ name: "my-beacon-plugin", context: true },
|
|
176
|
+
async (context, beacon: any) => {
|
|
177
|
+
const { renderTime } =
|
|
178
|
+
(await (context as any)[MetricsViewBeaconPlugin.Symbol]) || {};
|
|
179
|
+
|
|
180
|
+
// move renderTime from data to top-level
|
|
181
|
+
return {
|
|
182
|
+
...beacon,
|
|
183
|
+
...(renderTime && { renderTime }),
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
test("viewed beacon builder can use request time", async () => {
|
|
191
|
+
const getRequestTime = vitest.fn().mockImplementation(() => 123);
|
|
192
|
+
const metricsPlugin = new MetricsCorePlugin({
|
|
193
|
+
trackRenderTime: true,
|
|
194
|
+
trackUpdateTime: true,
|
|
195
|
+
});
|
|
196
|
+
new RequestTimeWebPlugin(getRequestTime).apply(metricsPlugin);
|
|
197
|
+
const beaconPlugin = new BeaconPlugin({
|
|
198
|
+
plugins: [new MyBeaconPluginPlugin()],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const player = new Player({
|
|
202
|
+
plugins: [beaconPlugin, metricsPlugin],
|
|
203
|
+
});
|
|
204
|
+
player.start(basicContentWithActions as any);
|
|
205
|
+
expect(getRequestTime).toBeCalledTimes(1);
|
|
206
|
+
expect(metricsPlugin.getMetrics()?.flow?.requestTime).toBe(123);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("viewed beacon builder can use render time", async () => {
|
|
210
|
+
const handler = vitest.fn();
|
|
211
|
+
|
|
212
|
+
const metricsPlugin = new MetricsCorePlugin({
|
|
213
|
+
trackRenderTime: true,
|
|
214
|
+
trackUpdateTime: true,
|
|
215
|
+
});
|
|
216
|
+
const beaconPlugin = new BeaconPlugin({
|
|
217
|
+
callback: handler,
|
|
218
|
+
plugins: [new MyBeaconPluginPlugin()],
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const player = new Player({
|
|
222
|
+
plugins: [beaconPlugin, metricsPlugin],
|
|
223
|
+
});
|
|
224
|
+
player.start(basicContentWithActions as any);
|
|
225
|
+
|
|
226
|
+
const onRenderEndPromise = new Promise((resolve) => {
|
|
227
|
+
metricsPlugin.hooks.onRenderEnd.tap(
|
|
228
|
+
"on-render-end",
|
|
229
|
+
(timing) => timing.completed && resolve(timing.duration),
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
metricsPlugin.renderEnd();
|
|
233
|
+
const duration = await onRenderEndPromise;
|
|
234
|
+
|
|
235
|
+
await vitest.waitFor(() =>
|
|
236
|
+
expect(handler.mock.calls[0][0].assetId).toBe("my-view"),
|
|
237
|
+
);
|
|
238
|
+
expect(handler.mock.calls[0][0].renderTime).toBe(duration);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("viewed beacon builder can use render time when it resolves", async () => {
|
|
242
|
+
const handler = vitest.fn();
|
|
243
|
+
|
|
244
|
+
const metricsPlugin = new MetricsCorePlugin({
|
|
245
|
+
trackRenderTime: true,
|
|
246
|
+
trackUpdateTime: true,
|
|
247
|
+
});
|
|
248
|
+
const beaconPlugin = new BeaconPlugin({
|
|
249
|
+
callback: handler,
|
|
250
|
+
plugins: [new MyBeaconPluginPlugin()],
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const player = new Player({
|
|
254
|
+
plugins: [beaconPlugin, metricsPlugin],
|
|
255
|
+
});
|
|
256
|
+
player.start(basicContentWithActions as any);
|
|
257
|
+
|
|
258
|
+
const onRenderEndPromise = new Promise((resolve) => {
|
|
259
|
+
metricsPlugin.hooks.onRenderEnd.tap(
|
|
260
|
+
"on-render-end",
|
|
261
|
+
(timing) => timing.completed && resolve(timing.duration),
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
metricsPlugin.renderEnd();
|
|
265
|
+
const duration = await onRenderEndPromise;
|
|
266
|
+
|
|
267
|
+
await vitest.waitFor(() =>
|
|
268
|
+
expect(handler.mock.calls[0][0].assetId).toBe("my-view"),
|
|
269
|
+
);
|
|
270
|
+
expect(handler.mock.calls[0][0].renderTime).toBe(duration);
|
|
271
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./metrics";
|
|
2
|
+
export * from "./symbols";
|
package/src/metrics.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { Player, PlayerPlugin } from
|
|
2
|
-
import { SyncHook, SyncBailHook } from
|
|
3
|
-
import type { BeaconPluginPlugin, BeaconArgs } from
|
|
4
|
-
import { BeaconPlugin } from
|
|
1
|
+
import type { Player, PlayerPlugin } from "@player-ui/player";
|
|
2
|
+
import { SyncHook, SyncBailHook } from "tapable-ts";
|
|
3
|
+
import type { BeaconPluginPlugin, BeaconArgs } from "@player-ui/beacon-plugin";
|
|
4
|
+
import { BeaconPlugin } from "@player-ui/beacon-plugin";
|
|
5
5
|
import {
|
|
6
6
|
MetricsCorePluginSymbol,
|
|
7
7
|
MetricsViewBeaconPluginContextSymbol,
|
|
8
|
-
} from
|
|
8
|
+
} from "./symbols";
|
|
9
9
|
|
|
10
10
|
// Try to use performance.now() but fall back to Date.now() if you can't
|
|
11
11
|
export const defaultGetTime =
|
|
12
|
-
typeof performance ===
|
|
12
|
+
typeof performance === "undefined"
|
|
13
13
|
? () => Date.now()
|
|
14
14
|
: () => performance.now();
|
|
15
15
|
|
|
@@ -67,16 +67,16 @@ export interface PlayerFlowMetrics {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const callbacks = [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
"onFlowBegin",
|
|
71
|
+
"onFlowEnd",
|
|
72
|
+
"onInteractive",
|
|
73
|
+
"onNodeStart",
|
|
74
|
+
"onNodeEnd",
|
|
75
|
+
"onRenderStart",
|
|
76
|
+
"onRenderEnd",
|
|
77
|
+
"onUpdateStart",
|
|
78
|
+
"onUpdateEnd",
|
|
79
|
+
"onUpdate",
|
|
80
80
|
] as const;
|
|
81
81
|
|
|
82
82
|
/** Context structure for 'viewed' beacons rendering metrics */
|
|
@@ -99,13 +99,13 @@ export class MetricsViewBeaconPlugin implements BeaconPluginPlugin {
|
|
|
99
99
|
constructor(metricsPlugin: MetricsCorePlugin) {
|
|
100
100
|
this.metricsPlugin = metricsPlugin;
|
|
101
101
|
this.metricsPlugin.hooks.onRenderEnd.tap(
|
|
102
|
-
|
|
102
|
+
"MetricsViewBeaconPlugin",
|
|
103
103
|
(timing) => {
|
|
104
104
|
if (timing.completed && this.resolvePendingRenderTime) {
|
|
105
105
|
this.resolvePendingRenderTime(timing.duration);
|
|
106
106
|
this.resolvePendingRenderTime = undefined;
|
|
107
107
|
}
|
|
108
|
-
}
|
|
108
|
+
},
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -113,7 +113,7 @@ export class MetricsViewBeaconPlugin implements BeaconPluginPlugin {
|
|
|
113
113
|
beaconPlugin.hooks.buildBeacon.intercept({
|
|
114
114
|
context: true,
|
|
115
115
|
call: (context: any, beacon) => {
|
|
116
|
-
if (context && (beacon as BeaconArgs).action ===
|
|
116
|
+
if (context && (beacon as BeaconArgs).action === "viewed") {
|
|
117
117
|
context[this.symbol] = this.buildContext();
|
|
118
118
|
}
|
|
119
119
|
},
|
|
@@ -133,7 +133,7 @@ export class MetricsViewBeaconPlugin implements BeaconPluginPlugin {
|
|
|
133
133
|
if (flow) {
|
|
134
134
|
const lastItem = flow.timeline[flow.timeline.length - 1];
|
|
135
135
|
|
|
136
|
-
if (
|
|
136
|
+
if ("render" in lastItem && lastItem.render.completed) {
|
|
137
137
|
return lastItem.render.duration;
|
|
138
138
|
}
|
|
139
139
|
}
|
|
@@ -163,41 +163,41 @@ export interface MetricsWebPluginOptions {
|
|
|
163
163
|
/** Called when a new node is started */
|
|
164
164
|
onNodeStart?: (
|
|
165
165
|
nodeMetrics: NodeMetrics | NodeRenderMetrics,
|
|
166
|
-
update: PlayerFlowMetrics
|
|
166
|
+
update: PlayerFlowMetrics,
|
|
167
167
|
) => void;
|
|
168
168
|
|
|
169
169
|
/** Called when a node is ended */
|
|
170
170
|
onNodeEnd?: (
|
|
171
171
|
nodeMetrics: NodeMetrics | NodeRenderMetrics,
|
|
172
|
-
update: PlayerFlowMetrics
|
|
172
|
+
update: PlayerFlowMetrics,
|
|
173
173
|
) => void;
|
|
174
174
|
|
|
175
175
|
/** Called when rendering for a node begins */
|
|
176
176
|
onRenderStart?: (
|
|
177
177
|
timing: Timing,
|
|
178
178
|
nodeMetrics: NodeRenderMetrics,
|
|
179
|
-
update: PlayerFlowMetrics
|
|
179
|
+
update: PlayerFlowMetrics,
|
|
180
180
|
) => void;
|
|
181
181
|
|
|
182
182
|
/** Called when rendering for a node ends */
|
|
183
183
|
onRenderEnd?: (
|
|
184
184
|
timing: Timing,
|
|
185
185
|
nodeMetrics: NodeRenderMetrics,
|
|
186
|
-
update: PlayerFlowMetrics
|
|
186
|
+
update: PlayerFlowMetrics,
|
|
187
187
|
) => void;
|
|
188
188
|
|
|
189
189
|
/** Called when an update for a node begins */
|
|
190
190
|
onUpdateStart?: (
|
|
191
191
|
timing: Timing,
|
|
192
192
|
nodeMetrics: NodeRenderMetrics,
|
|
193
|
-
update: PlayerFlowMetrics
|
|
193
|
+
update: PlayerFlowMetrics,
|
|
194
194
|
) => void;
|
|
195
195
|
|
|
196
196
|
/** Called when an update for a node ends */
|
|
197
197
|
onUpdateEnd?: (
|
|
198
198
|
timing: Timing,
|
|
199
199
|
nodeMetrics: NodeRenderMetrics,
|
|
200
|
-
update: PlayerFlowMetrics
|
|
200
|
+
update: PlayerFlowMetrics,
|
|
201
201
|
) => void;
|
|
202
202
|
|
|
203
203
|
/** Callback to subscribe to updates for any metric */
|
|
@@ -224,7 +224,7 @@ export interface MetricsWebPluginOptions {
|
|
|
224
224
|
*/
|
|
225
225
|
export class RequestTimeWebPlugin {
|
|
226
226
|
getRequestTime: () => number | undefined;
|
|
227
|
-
name =
|
|
227
|
+
name = "RequestTimeWebPlugin";
|
|
228
228
|
|
|
229
229
|
constructor(getRequestTime: () => number | undefined) {
|
|
230
230
|
this.getRequestTime = getRequestTime;
|
|
@@ -241,7 +241,7 @@ export class RequestTimeWebPlugin {
|
|
|
241
241
|
* A plugin that enables gathering of render metrics
|
|
242
242
|
*/
|
|
243
243
|
export class MetricsCorePlugin implements PlayerPlugin {
|
|
244
|
-
name =
|
|
244
|
+
name = "metrics";
|
|
245
245
|
|
|
246
246
|
static Symbol = MetricsCorePluginSymbol;
|
|
247
247
|
public readonly symbol = MetricsCorePluginSymbol;
|
|
@@ -300,7 +300,7 @@ export class MetricsCorePlugin implements PlayerPlugin {
|
|
|
300
300
|
|
|
301
301
|
callbacks.forEach((hookName) => {
|
|
302
302
|
if (options?.[hookName] !== undefined) {
|
|
303
|
-
(this.hooks[hookName] as any).tap(
|
|
303
|
+
(this.hooks[hookName] as any).tap("options", options?.[hookName]);
|
|
304
304
|
}
|
|
305
305
|
});
|
|
306
306
|
}
|
|
@@ -323,7 +323,7 @@ export class MetricsCorePlugin implements PlayerPlugin {
|
|
|
323
323
|
|
|
324
324
|
const lastItem = timeline[timeline.length - 1];
|
|
325
325
|
|
|
326
|
-
if (
|
|
326
|
+
if ("updates" in lastItem) {
|
|
327
327
|
// Get the last update, make sure it's completed
|
|
328
328
|
if (lastItem.updates.length > 0) {
|
|
329
329
|
const lastUpdate = lastItem.updates[lastItem.updates.length - 1];
|
|
@@ -364,7 +364,7 @@ export class MetricsCorePlugin implements PlayerPlugin {
|
|
|
364
364
|
this.hooks.onRenderStart.call(
|
|
365
365
|
renderInfo.render,
|
|
366
366
|
renderInfo,
|
|
367
|
-
this.metrics
|
|
367
|
+
this.metrics,
|
|
368
368
|
);
|
|
369
369
|
}
|
|
370
370
|
}
|
|
@@ -373,7 +373,7 @@ export class MetricsCorePlugin implements PlayerPlugin {
|
|
|
373
373
|
public renderEnd(): void {
|
|
374
374
|
if (!this.trackRender) {
|
|
375
375
|
throw new Error(
|
|
376
|
-
|
|
376
|
+
"Must start the metrics-plugin with render tracking enabled",
|
|
377
377
|
);
|
|
378
378
|
}
|
|
379
379
|
|
|
@@ -391,7 +391,7 @@ export class MetricsCorePlugin implements PlayerPlugin {
|
|
|
391
391
|
|
|
392
392
|
const lastItem = timeline[timeline.length - 1];
|
|
393
393
|
|
|
394
|
-
if (!(
|
|
394
|
+
if (!("render" in lastItem)) {
|
|
395
395
|
return;
|
|
396
396
|
}
|
|
397
397
|
|
|
@@ -466,7 +466,7 @@ export class MetricsCorePlugin implements PlayerPlugin {
|
|
|
466
466
|
});
|
|
467
467
|
|
|
468
468
|
player.hooks.state.tap(this.name, (state) => {
|
|
469
|
-
if (state.status ===
|
|
469
|
+
if (state.status === "completed" || state.status === "error") {
|
|
470
470
|
const endTime = defaultGetTime();
|
|
471
471
|
const { flow } = this.metrics;
|
|
472
472
|
|
|
@@ -556,7 +556,7 @@ export class MetricsCorePlugin implements PlayerPlugin {
|
|
|
556
556
|
});
|
|
557
557
|
|
|
558
558
|
player.applyTo<BeaconPlugin>(BeaconPlugin.Symbol, (beaconPlugin) =>
|
|
559
|
-
new MetricsViewBeaconPlugin(this).apply(beaconPlugin)
|
|
559
|
+
new MetricsViewBeaconPlugin(this).apply(beaconPlugin),
|
|
560
560
|
);
|
|
561
561
|
}
|
|
562
562
|
}
|
package/src/symbols.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const MetricsCorePluginSymbol = Symbol.for(
|
|
1
|
+
export const MetricsCorePluginSymbol = Symbol.for("MetricsCorePlugin");
|
|
2
2
|
export const MetricsViewBeaconPluginContextSymbol = Symbol.for(
|
|
3
|
-
|
|
3
|
+
"MetricsViewBeaconPluginContext",
|
|
4
4
|
);
|
package/types/index.d.ts
ADDED