@atlaskit/editor-plugin-interactivity 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +31 -0
- package/dist/cjs/collector/editor-event-observer.js +63 -0
- package/dist/cjs/collector/interaction-events.js +62 -0
- package/dist/cjs/collector/interaction-group.js +20 -2
- package/dist/cjs/collector/interaction-tracker.js +62 -21
- package/dist/cjs/collector/interactivity-collector.js +54 -2
- package/dist/cjs/collector/interactivity-session.js +3 -0
- package/dist/cjs/interactivityPlugin.js +16 -4
- package/dist/es2019/collector/editor-event-observer.js +46 -0
- package/dist/es2019/collector/interaction-events.js +55 -0
- package/dist/es2019/collector/interaction-group.js +17 -2
- package/dist/es2019/collector/interaction-tracker.js +60 -20
- package/dist/es2019/collector/interactivity-collector.js +48 -2
- package/dist/es2019/collector/interactivity-session.js +3 -0
- package/dist/es2019/interactivityPlugin.js +17 -5
- package/dist/esm/collector/editor-event-observer.js +57 -0
- package/dist/esm/collector/interaction-events.js +55 -0
- package/dist/esm/collector/interaction-group.js +20 -2
- package/dist/esm/collector/interaction-tracker.js +63 -21
- package/dist/esm/collector/interactivity-collector.js +54 -2
- package/dist/esm/collector/interactivity-session.js +3 -0
- package/dist/esm/interactivityPlugin.js +17 -5
- package/dist/types/analytics/interactivity-snapshot.d.ts +5 -0
- package/dist/types/collector/editor-event-observer.d.ts +19 -0
- package/dist/types/collector/interaction-events.d.ts +19 -0
- package/dist/types/collector/interaction-group.d.ts +14 -2
- package/dist/types/collector/interaction-tracker.d.ts +12 -3
- package/dist/types/collector/interactivity-collector.d.ts +15 -0
- package/dist/types/collector/interactivity-session.d.ts +3 -0
- package/docs/0-intro.tsx +2 -1
- package/package.json +5 -5
|
@@ -4,6 +4,8 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
4
4
|
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
5
5
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
6
6
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
7
|
+
import { interactionEventKind } from './interaction-events';
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* The Event Timing fields this package reads. `interactionId` is missing from the DOM
|
|
9
11
|
* typings' `PerformanceEventTiming`, so it is declared here as optional, which also makes
|
|
@@ -13,12 +15,13 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
13
15
|
/**
|
|
14
16
|
* What an entry did to the interaction it belongs to: either it is the first entry of a new
|
|
15
17
|
* interaction, or it measured an interaction that was already counted as slower than it was
|
|
16
|
-
* known to be.
|
|
18
|
+
* known to be. Both carry the editor group of the interaction, if it is one of the editor's.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
|
-
* How many interactions are remembered, so their growth can still be applied
|
|
21
|
-
* interaction arrive within the interaction itself, so
|
|
22
|
+
* How many interactions are remembered, so their growth can still be applied, and how many of
|
|
23
|
+
* the editor's events. Entries of one interaction arrive within the interaction itself, so
|
|
24
|
+
* anything older than the last few hundred is not needed.
|
|
22
25
|
*/
|
|
23
26
|
var MAX_TRACKED = 256;
|
|
24
27
|
/**
|
|
@@ -28,7 +31,17 @@ var MAX_TRACKED = 256;
|
|
|
28
31
|
var PRUNE_BATCH = 64;
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
|
-
*
|
|
34
|
+
* Identifies the event an entry measured: an entry's `startTime` is that event's timestamp and its
|
|
35
|
+
* `name` is its type. The type is in the key because browsers coarsen the timestamp, so two events
|
|
36
|
+
* of one task can share it — and events of one type are always of one group, so a collision cannot
|
|
37
|
+
* move an interaction into another group.
|
|
38
|
+
*/
|
|
39
|
+
function eventKey(type, timeStamp) {
|
|
40
|
+
return "".concat(type, "|").concat(timeStamp);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Makes interactions out of what the two observers report, for one session.
|
|
32
45
|
*
|
|
33
46
|
* Entries sharing a non-zero `interactionId` are one interaction whose latency is the
|
|
34
47
|
* maximum `duration` among them. Entries arrive incrementally, so an interaction's latency
|
|
@@ -38,6 +51,9 @@ var PRUNE_BATCH = 64;
|
|
|
38
51
|
* A non-zero `interactionId` is the browser's own definition of an interaction, which is
|
|
39
52
|
* also what INP filters on: it is assigned to the pointer and keyboard events that make one
|
|
40
53
|
* up, and never to scrolling or pointer movement.
|
|
54
|
+
*
|
|
55
|
+
* The editor's events answer what an entry cannot: which interactions were with the editor, and
|
|
56
|
+
* how many there were, including the ones below the Event Timing reporting threshold.
|
|
41
57
|
*/
|
|
42
58
|
export var InteractionTracker = /*#__PURE__*/function () {
|
|
43
59
|
/**
|
|
@@ -50,7 +66,8 @@ export var InteractionTracker = /*#__PURE__*/function () {
|
|
|
50
66
|
function InteractionTracker() {
|
|
51
67
|
var startsAfterInteractionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
|
52
68
|
_classCallCheck(this, InteractionTracker);
|
|
53
|
-
_defineProperty(this, "
|
|
69
|
+
_defineProperty(this, "interactions", new Map());
|
|
70
|
+
_defineProperty(this, "groupByEvent", new Map());
|
|
54
71
|
_defineProperty(this, "highestInteractionId", 0);
|
|
55
72
|
this.startsAfterInteractionId = startsAfterInteractionId;
|
|
56
73
|
}
|
|
@@ -87,41 +104,66 @@ export var InteractionTracker = /*#__PURE__*/function () {
|
|
|
87
104
|
if (!Number.isFinite(entry.duration) || entry.duration < 0) {
|
|
88
105
|
return undefined;
|
|
89
106
|
}
|
|
90
|
-
var
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
var tracked = this.interactions.get(interactionId);
|
|
108
|
+
if (tracked === undefined) {
|
|
109
|
+
// Taken once: an entry that only makes the interaction slower has to move its count
|
|
110
|
+
// within the group it was counted in, not into another one.
|
|
111
|
+
var group = this.groupByEvent.get(eventKey(entry.name, entry.startTime));
|
|
112
|
+
this.interactions.set(interactionId, {
|
|
113
|
+
latencyMs: entry.duration,
|
|
114
|
+
group: group
|
|
115
|
+
});
|
|
116
|
+
this.prune(this.interactions);
|
|
94
117
|
return {
|
|
95
118
|
type: 'new',
|
|
96
|
-
latencyMs: entry.duration
|
|
119
|
+
latencyMs: entry.duration,
|
|
120
|
+
group: group
|
|
97
121
|
};
|
|
98
122
|
}
|
|
99
|
-
if (entry.duration <=
|
|
123
|
+
if (entry.duration <= tracked.latencyMs) {
|
|
100
124
|
return undefined;
|
|
101
125
|
}
|
|
102
|
-
|
|
126
|
+
var fromMs = tracked.latencyMs;
|
|
127
|
+
tracked.latencyMs = entry.duration;
|
|
103
128
|
return {
|
|
104
129
|
type: 'remeasured',
|
|
105
|
-
fromMs:
|
|
106
|
-
toMs: entry.duration
|
|
130
|
+
fromMs: fromMs,
|
|
131
|
+
toMs: entry.duration,
|
|
132
|
+
group: tracked.group
|
|
107
133
|
};
|
|
108
134
|
}
|
|
135
|
+
|
|
136
|
+
/** @returns the group of an interaction to count, when this is the event its group counts on. */
|
|
137
|
+
}, {
|
|
138
|
+
key: "recordEditorEvent",
|
|
139
|
+
value: function recordEditorEvent(event) {
|
|
140
|
+
var kind = interactionEventKind(event.type);
|
|
141
|
+
if (!kind) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Every event of the interaction, because any of them can be the one Event Timing reports
|
|
146
|
+
// as the slowest: for a pointer press that is usually the click.
|
|
147
|
+
this.groupByEvent.set(eventKey(event.type, event.timeStamp), kind.group);
|
|
148
|
+
this.prune(this.groupByEvent);
|
|
149
|
+
return kind.counts ? kind.group : undefined;
|
|
150
|
+
}
|
|
109
151
|
}, {
|
|
110
152
|
key: "prune",
|
|
111
|
-
value: function prune() {
|
|
112
|
-
if (
|
|
153
|
+
value: function prune(entries) {
|
|
154
|
+
if (entries.size <= MAX_TRACKED) {
|
|
113
155
|
return;
|
|
114
156
|
}
|
|
115
157
|
|
|
116
|
-
// `Map` preserves insertion order
|
|
117
|
-
//
|
|
158
|
+
// `Map` preserves insertion order, so the entries inserted first are the least likely to
|
|
159
|
+
// see another entry or another event.
|
|
118
160
|
var remaining = PRUNE_BATCH;
|
|
119
|
-
var _iterator = _createForOfIteratorHelper(
|
|
161
|
+
var _iterator = _createForOfIteratorHelper(entries.keys()),
|
|
120
162
|
_step;
|
|
121
163
|
try {
|
|
122
164
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
123
|
-
var
|
|
124
|
-
|
|
165
|
+
var key = _step.value;
|
|
166
|
+
entries.delete(key);
|
|
125
167
|
remaining -= 1;
|
|
126
168
|
if (remaining === 0) {
|
|
127
169
|
return;
|
|
@@ -5,6 +5,7 @@ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol
|
|
|
5
5
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
6
6
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
7
7
|
import { getDocument } from '@atlaskit/browser-apis';
|
|
8
|
+
import { EditorEventObserver } from './editor-event-observer';
|
|
8
9
|
import { InteractionObserver } from './interaction-observer';
|
|
9
10
|
import { InteractivitySession } from './interactivity-session';
|
|
10
11
|
import { SCHEMA_VERSION } from './bucket-boundaries';
|
|
@@ -13,6 +14,9 @@ import { SnapshotScheduler } from './snapshot-scheduler';
|
|
|
13
14
|
/**
|
|
14
15
|
* Collects interaction latencies for one editor mount and emits session-to-date snapshots.
|
|
15
16
|
*
|
|
17
|
+
* Every interaction is counted in `page`, and the ones with the editor again in one of the editor
|
|
18
|
+
* groups, which is what tells a slow editor apart from a slow page around it.
|
|
19
|
+
*
|
|
16
20
|
* `start` and `stop` bound the collecting, which happens once. Within it there can be several
|
|
17
21
|
* sessions, because a session covers one document in one mode: a Confluence live page
|
|
18
22
|
* navigates and switches between reading and editing without ever remounting the editor, and
|
|
@@ -32,6 +36,9 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
32
36
|
this.interactionObserver = new InteractionObserver(function (entries) {
|
|
33
37
|
return _this.recordEntries(entries);
|
|
34
38
|
});
|
|
39
|
+
this.editorEvents = new EditorEventObserver(function (event) {
|
|
40
|
+
return _this.recordEditorEvent(event);
|
|
41
|
+
});
|
|
35
42
|
this.snapshotScheduler = new SnapshotScheduler(function () {
|
|
36
43
|
return _this.onTimer();
|
|
37
44
|
});
|
|
@@ -76,6 +83,7 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
76
83
|
// this object was constructed.
|
|
77
84
|
this.session = this.createSession(0);
|
|
78
85
|
this.interactionObserver.start();
|
|
86
|
+
this.editorEvents.observe(this.editorRoot);
|
|
79
87
|
this.snapshotScheduler.start();
|
|
80
88
|
this.lifecycleObserver.start();
|
|
81
89
|
this.started = true;
|
|
@@ -92,10 +100,24 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
92
100
|
this.takeSnapshot('unmount');
|
|
93
101
|
this.stopped = true;
|
|
94
102
|
this.interactionObserver.stop();
|
|
103
|
+
this.editorEvents.stop();
|
|
95
104
|
this.snapshotScheduler.stop();
|
|
96
105
|
this.lifecycleObserver.stop();
|
|
97
106
|
}
|
|
98
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The element the editor renders itself into is what makes an interaction one of the editor's.
|
|
110
|
+
* The session is not tied to it: interactions from before it arrives are counted in `page`.
|
|
111
|
+
*/
|
|
112
|
+
}, {
|
|
113
|
+
key: "setEditorRoot",
|
|
114
|
+
value: function setEditorRoot(root) {
|
|
115
|
+
this.editorRoot = root !== null && root !== void 0 ? root : undefined;
|
|
116
|
+
if (this.started && !this.stopped) {
|
|
117
|
+
this.editorEvents.observe(this.editorRoot);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
99
121
|
/**
|
|
100
122
|
* Rotates the session when the editor is pointed at different content.
|
|
101
123
|
*
|
|
@@ -205,6 +227,26 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
205
227
|
value: function onPageShow() {
|
|
206
228
|
this.session.lifecycleSnapshotEmitted = false;
|
|
207
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Counts an interaction with the editor, including the ones Event Timing never reports because
|
|
233
|
+
* they were faster than its threshold — which is why a count alone moves the session on.
|
|
234
|
+
*/
|
|
235
|
+
}, {
|
|
236
|
+
key: "recordEditorEvent",
|
|
237
|
+
value: function recordEditorEvent(event) {
|
|
238
|
+
if (this.stopped) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
var group = this.session.tracker.recordEditorEvent(event);
|
|
242
|
+
if (!group) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// The session names its editor groups after the fields they are reported in.
|
|
247
|
+
this.session[group].countTotal();
|
|
248
|
+
this.session.revision += 1;
|
|
249
|
+
}
|
|
208
250
|
}, {
|
|
209
251
|
key: "recordEntries",
|
|
210
252
|
value: function recordEntries(entries) {
|
|
@@ -222,8 +264,14 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
222
264
|
}
|
|
223
265
|
if (update.type === 'new') {
|
|
224
266
|
page.add(update.latencyMs);
|
|
267
|
+
if (update.group) {
|
|
268
|
+
this.session[update.group].add(update.latencyMs);
|
|
269
|
+
}
|
|
225
270
|
} else {
|
|
226
271
|
page.remeasure(update.fromMs, update.toMs);
|
|
272
|
+
if (update.group) {
|
|
273
|
+
this.session[update.group].remeasure(update.fromMs, update.toMs);
|
|
274
|
+
}
|
|
227
275
|
}
|
|
228
276
|
this.session.revision += 1;
|
|
229
277
|
}
|
|
@@ -263,7 +311,7 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
263
311
|
session.emittedRevision = session.revision;
|
|
264
312
|
var now = performance.now();
|
|
265
313
|
var hiddenMs = session.hiddenMs + (session.hiddenSince === undefined ? 0 : now - session.hiddenSince);
|
|
266
|
-
var
|
|
314
|
+
var pageTotalCount = InteractionObserver.readPageInteractionCount() - session.interactionCountAtStart;
|
|
267
315
|
this.emit({
|
|
268
316
|
schema: SCHEMA_VERSION,
|
|
269
317
|
interactivitySessionId: session.id,
|
|
@@ -275,7 +323,11 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
275
323
|
hiddenMs: Math.round(hiddenMs),
|
|
276
324
|
nodeSize: this.getNodeSize(),
|
|
277
325
|
editorDomSize: this.getEditorDomSize(),
|
|
278
|
-
page
|
|
326
|
+
// Only `page` is told its total; each editor group has counted its own.
|
|
327
|
+
page: session.page.snapshot(pageTotalCount),
|
|
328
|
+
editorTyping: session.editorTyping.snapshot(),
|
|
329
|
+
editorPointer: session.editorPointer.snapshot(),
|
|
330
|
+
editorOther: session.editorOther.snapshot()
|
|
279
331
|
});
|
|
280
332
|
}
|
|
281
333
|
}]);
|
|
@@ -27,6 +27,9 @@ export var InteractivitySession = /*#__PURE__*/_createClass(function Interactivi
|
|
|
27
27
|
/** Page interaction count when the session opened, subtracted to get its own total. */
|
|
28
28
|
_defineProperty(this, "interactionCountAtStart", InteractionObserver.readPageInteractionCount());
|
|
29
29
|
_defineProperty(this, "page", new InteractionGroup());
|
|
30
|
+
_defineProperty(this, "editorTyping", new InteractionGroup());
|
|
31
|
+
_defineProperty(this, "editorPointer", new InteractionGroup());
|
|
32
|
+
_defineProperty(this, "editorOther", new InteractionGroup());
|
|
30
33
|
/** Increments per snapshot; a query takes the highest one per session. */
|
|
31
34
|
_defineProperty(this, "seq", 0);
|
|
32
35
|
_defineProperty(this, "hiddenMs", 0);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { logException } from '@atlaskit/editor-common/monitoring';
|
|
3
3
|
import { fireInteractivityEvent } from './analytics/fire-interactivity-event';
|
|
4
4
|
import { InteractivityCollector } from './collector/interactivity-collector';
|
|
@@ -16,11 +16,10 @@ export var interactivityPlugin = function interactivityPlugin(_ref) {
|
|
|
16
16
|
return {
|
|
17
17
|
name: 'interactivity',
|
|
18
18
|
usePluginHook: function usePluginHook(_ref2) {
|
|
19
|
-
var editorView = _ref2.editorView
|
|
19
|
+
var editorView = _ref2.editorView,
|
|
20
|
+
wrapperElement = _ref2.wrapperElement;
|
|
21
|
+
var collectorRef = useRef(undefined);
|
|
20
22
|
useEffect(function () {
|
|
21
|
-
// Nothing measured here is worth an editor. This effect runs inside the plugin slot's
|
|
22
|
-
// error boundary, which also renders every plugin's content components, so a throw
|
|
23
|
-
// from instrumentation would take that UI down with it.
|
|
24
23
|
try {
|
|
25
24
|
var _api$contextIdentifie2, _api$editorViewMode2;
|
|
26
25
|
var collector = new InteractivityCollector({
|
|
@@ -51,6 +50,9 @@ export var interactivityPlugin = function interactivityPlugin(_ref) {
|
|
|
51
50
|
return editorView.isDestroyed ? undefined : editorView.dom.getElementsByTagName('*').length;
|
|
52
51
|
}
|
|
53
52
|
});
|
|
53
|
+
|
|
54
|
+
// Effects run in declaration order, so the one below always finds it.
|
|
55
|
+
collectorRef.current = collector;
|
|
54
56
|
if (!collector.start()) {
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
@@ -82,6 +84,16 @@ export var interactivityPlugin = function interactivityPlugin(_ref) {
|
|
|
82
84
|
// live registry, and restarting the session on a reconfigure would split one
|
|
83
85
|
// editor session in two.
|
|
84
86
|
}, [editorView]);
|
|
87
|
+
useEffect(function () {
|
|
88
|
+
try {
|
|
89
|
+
var _collectorRef$current;
|
|
90
|
+
(_collectorRef$current = collectorRef.current) === null || _collectorRef$current === void 0 || _collectorRef$current.setEditorRoot(wrapperElement);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
void logException(error, {
|
|
93
|
+
location: 'editor-plugin-interactivity/observe'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}, [wrapperElement]);
|
|
85
97
|
}
|
|
86
98
|
};
|
|
87
99
|
};
|
|
@@ -48,10 +48,15 @@ export type InteractionGroupSnapshot = {
|
|
|
48
48
|
export type InteractivitySnapshot = {
|
|
49
49
|
activeMs: number;
|
|
50
50
|
editorDomSize?: number;
|
|
51
|
+
/** Interactions inside the editor that are neither typing nor pointing. */
|
|
52
|
+
editorOther: InteractionGroupSnapshot;
|
|
53
|
+
editorPointer: InteractionGroupSnapshot;
|
|
54
|
+
editorTyping: InteractionGroupSnapshot;
|
|
51
55
|
hiddenMs: number;
|
|
52
56
|
interactivitySessionId: string;
|
|
53
57
|
nodeSize?: number;
|
|
54
58
|
objectId?: string;
|
|
59
|
+
/** Every interaction on the page, the editor groups included. */
|
|
55
60
|
page: InteractionGroupSnapshot;
|
|
56
61
|
reason: SnapshotReason;
|
|
57
62
|
schema: number;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports the events the interactions with the editor are made of, as the browser dispatched
|
|
3
|
+
* them. What they mean is the tracker's business.
|
|
4
|
+
*
|
|
5
|
+
* Event Timing cannot answer which interactions were with the editor, because it observes the
|
|
6
|
+
* whole document, nor how many there were, because it reports none below 16 ms.
|
|
7
|
+
*/
|
|
8
|
+
export declare class EditorEventObserver {
|
|
9
|
+
private readonly onEvent;
|
|
10
|
+
private root;
|
|
11
|
+
private unbind;
|
|
12
|
+
constructor(onEvent: (event: Event) => void);
|
|
13
|
+
/**
|
|
14
|
+
* Called as the element the editor renders itself into changes: the editor only knows it after
|
|
15
|
+
* its first render, and can replace it.
|
|
16
|
+
*/
|
|
17
|
+
observe(root: Element | null | undefined): void;
|
|
18
|
+
stop(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor group an interaction belongs to, which is also the field the event reports it in.
|
|
3
|
+
*
|
|
4
|
+
* `editorOther` is the remainder — an interaction the browser counts that is neither typing nor
|
|
5
|
+
* pointing — so the three groups together cover whatever the browser calls an interaction.
|
|
6
|
+
*/
|
|
7
|
+
export type EditorInteractionGroupName = 'editorOther' | 'editorPointer' | 'editorTyping';
|
|
8
|
+
export type InteractionEventKind = {
|
|
9
|
+
/**
|
|
10
|
+
* Whether an interaction is counted when this event arrives. One event of each group has it,
|
|
11
|
+
* because an interaction is several events — `keydown`, what the key produced, `keyup`, or
|
|
12
|
+
* `pointerdown`, `pointerup`, `click` — and counting each would count it three times.
|
|
13
|
+
*/
|
|
14
|
+
counts?: boolean;
|
|
15
|
+
group: EditorInteractionGroupName;
|
|
16
|
+
};
|
|
17
|
+
/** Derived, so the types listened for cannot drift from the table. */
|
|
18
|
+
export declare const INTERACTION_EVENT_TYPES: string[];
|
|
19
|
+
export declare function interactionEventKind(type: string): InteractionEventKind | undefined;
|
|
@@ -7,16 +7,28 @@ import type { InteractionGroupSnapshot } from '../analytics/interactivity-snapsh
|
|
|
7
7
|
* whatever its latency was, and everything the event carries — the reported buckets, the
|
|
8
8
|
* count, the sum, the maximum and the percentiles — is derived from that map when a snapshot
|
|
9
9
|
* is taken. Nothing is computed while interactions arrive.
|
|
10
|
+
*
|
|
11
|
+
* The two counters count different populations: `add` takes the interactions Event Timing
|
|
12
|
+
* measured, `countTotal` takes all of them, including the ones below the 16 ms reporting threshold
|
|
13
|
+
* it never delivers. So `totalCount >= observedCount`, and the difference is how many were too
|
|
14
|
+
* fast to be measured.
|
|
10
15
|
*/
|
|
11
16
|
export declare class InteractionGroup {
|
|
12
17
|
private countByLatency;
|
|
18
|
+
private totalCount;
|
|
13
19
|
add(latencyMs: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* Counts an interaction towards the group's total, measured or not. `page` has no use for it:
|
|
22
|
+
* `performance.interactionCount` counts the page's interactions.
|
|
23
|
+
*/
|
|
24
|
+
countTotal(): void;
|
|
14
25
|
remeasure(previousLatencyMs: number, latencyMs: number): void;
|
|
15
26
|
/**
|
|
16
27
|
* @param totalCount every interaction of the group, including those below the Event Timing
|
|
17
|
-
* reporting threshold, which
|
|
28
|
+
* reporting threshold. Defaults to what `countTotal` was told, which is where an editor
|
|
29
|
+
* group's total comes from; `page` passes `performance.interactionCount` instead.
|
|
18
30
|
*/
|
|
19
|
-
snapshot(totalCount
|
|
31
|
+
snapshot(totalCount?: number): InteractionGroupSnapshot;
|
|
20
32
|
private observedCount;
|
|
21
33
|
/** Rounding lives here so that every count goes through the same step, in or out. */
|
|
22
34
|
private roundLatencyUp;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EditorInteractionGroupName } from './interaction-events';
|
|
1
2
|
/**
|
|
2
3
|
* The Event Timing fields this package reads. `interactionId` is missing from the DOM
|
|
3
4
|
* typings' `PerformanceEventTiming`, so it is declared here as optional, which also makes
|
|
@@ -9,18 +10,20 @@ export type InteractionEntry = PerformanceEntry & {
|
|
|
9
10
|
/**
|
|
10
11
|
* What an entry did to the interaction it belongs to: either it is the first entry of a new
|
|
11
12
|
* interaction, or it measured an interaction that was already counted as slower than it was
|
|
12
|
-
* known to be.
|
|
13
|
+
* known to be. Both carry the editor group of the interaction, if it is one of the editor's.
|
|
13
14
|
*/
|
|
14
15
|
export type InteractionUpdate = {
|
|
16
|
+
group: EditorInteractionGroupName | undefined;
|
|
15
17
|
latencyMs: number;
|
|
16
18
|
type: 'new';
|
|
17
19
|
} | {
|
|
18
20
|
fromMs: number;
|
|
21
|
+
group: EditorInteractionGroupName | undefined;
|
|
19
22
|
toMs: number;
|
|
20
23
|
type: 'remeasured';
|
|
21
24
|
};
|
|
22
25
|
/**
|
|
23
|
-
*
|
|
26
|
+
* Makes interactions out of what the two observers report, for one session.
|
|
24
27
|
*
|
|
25
28
|
* Entries sharing a non-zero `interactionId` are one interaction whose latency is the
|
|
26
29
|
* maximum `duration` among them. Entries arrive incrementally, so an interaction's latency
|
|
@@ -30,10 +33,14 @@ export type InteractionUpdate = {
|
|
|
30
33
|
* A non-zero `interactionId` is the browser's own definition of an interaction, which is
|
|
31
34
|
* also what INP filters on: it is assigned to the pointer and keyboard events that make one
|
|
32
35
|
* up, and never to scrolling or pointer movement.
|
|
36
|
+
*
|
|
37
|
+
* The editor's events answer what an entry cannot: which interactions were with the editor, and
|
|
38
|
+
* how many there were, including the ones below the Event Timing reporting threshold.
|
|
33
39
|
*/
|
|
34
40
|
export declare class InteractionTracker {
|
|
35
|
-
private latencies;
|
|
36
41
|
private readonly startsAfterInteractionId;
|
|
42
|
+
private readonly interactions;
|
|
43
|
+
private readonly groupByEvent;
|
|
37
44
|
private highestInteractionId;
|
|
38
45
|
/**
|
|
39
46
|
* @param startsAfterInteractionId interactions up to and including this one belong to the
|
|
@@ -52,5 +59,7 @@ export declare class InteractionTracker {
|
|
|
52
59
|
* part of an interaction, belongs to a previous tracker, or does not change one.
|
|
53
60
|
*/
|
|
54
61
|
merge(entry: InteractionEntry): InteractionUpdate | undefined;
|
|
62
|
+
/** @returns the group of an interaction to count, when this is the event its group counts on. */
|
|
63
|
+
recordEditorEvent(event: Event): EditorInteractionGroupName | undefined;
|
|
55
64
|
private prune;
|
|
56
65
|
}
|
|
@@ -11,6 +11,9 @@ export type InteractivityCollectorOptions = {
|
|
|
11
11
|
/**
|
|
12
12
|
* Collects interaction latencies for one editor mount and emits session-to-date snapshots.
|
|
13
13
|
*
|
|
14
|
+
* Every interaction is counted in `page`, and the ones with the editor again in one of the editor
|
|
15
|
+
* groups, which is what tells a slow editor apart from a slow page around it.
|
|
16
|
+
*
|
|
14
17
|
* `start` and `stop` bound the collecting, which happens once. Within it there can be several
|
|
15
18
|
* sessions, because a session covers one document in one mode: a Confluence live page
|
|
16
19
|
* navigates and switches between reading and editing without ever remounting the editor, and
|
|
@@ -23,9 +26,11 @@ export declare class InteractivityCollector {
|
|
|
23
26
|
private readonly getObjectId;
|
|
24
27
|
private readonly getSessionMode;
|
|
25
28
|
private readonly interactionObserver;
|
|
29
|
+
private readonly editorEvents;
|
|
26
30
|
private readonly snapshotScheduler;
|
|
27
31
|
private readonly lifecycleObserver;
|
|
28
32
|
private session;
|
|
33
|
+
private editorRoot;
|
|
29
34
|
private started;
|
|
30
35
|
private stopped;
|
|
31
36
|
constructor(options: InteractivityCollectorOptions);
|
|
@@ -38,6 +43,11 @@ export declare class InteractivityCollector {
|
|
|
38
43
|
start(): boolean;
|
|
39
44
|
/** Reports the session as ended by the editor unmounting, and stops collecting. */
|
|
40
45
|
stop(): void;
|
|
46
|
+
/**
|
|
47
|
+
* The element the editor renders itself into is what makes an interaction one of the editor's.
|
|
48
|
+
* The session is not tied to it: interactions from before it arrives are counted in `page`.
|
|
49
|
+
*/
|
|
50
|
+
setEditorRoot(root: Element | null | undefined): void;
|
|
41
51
|
/**
|
|
42
52
|
* Rotates the session when the editor is pointed at different content.
|
|
43
53
|
*
|
|
@@ -70,6 +80,11 @@ export declare class InteractivityCollector {
|
|
|
70
80
|
* one is due.
|
|
71
81
|
*/
|
|
72
82
|
private onPageShow;
|
|
83
|
+
/**
|
|
84
|
+
* Counts an interaction with the editor, including the ones Event Timing never reports because
|
|
85
|
+
* they were faster than its threshold — which is why a count alone moves the session on.
|
|
86
|
+
*/
|
|
87
|
+
private recordEditorEvent;
|
|
73
88
|
private recordEntries;
|
|
74
89
|
private takeSnapshot;
|
|
75
90
|
}
|
|
@@ -20,6 +20,9 @@ export declare class InteractivitySession {
|
|
|
20
20
|
readonly interactionCountAtStart: number;
|
|
21
21
|
readonly tracker: InteractionTracker;
|
|
22
22
|
readonly page: InteractionGroup;
|
|
23
|
+
readonly editorTyping: InteractionGroup;
|
|
24
|
+
readonly editorPointer: InteractionGroup;
|
|
25
|
+
readonly editorOther: InteractionGroup;
|
|
23
26
|
/** Increments per snapshot; a query takes the highest one per session. */
|
|
24
27
|
seq: number;
|
|
25
28
|
/** Both fixed for the session: a change to either closes it and opens the next. */
|
package/docs/0-intro.tsx
CHANGED
|
@@ -24,7 +24,8 @@ ${createEditorUseOnlyNotice('Editor Plugin Interactivity', [
|
|
|
24
24
|
This package includes the interactivity plugin used by \`@atlaskit/editor-core\`.
|
|
25
25
|
|
|
26
26
|
It reports the \`editor interactivity\` operational event: session-to-date interaction latency
|
|
27
|
-
histograms for full page editor sessions,
|
|
27
|
+
histograms for full page editor sessions — for the page as a whole, and for the editor's typing,
|
|
28
|
+
pointer and other interactions — per [RFC 095](https://hello.atlassian.net/wiki/spaces/EDITOR/pages/7527607488/Editor+RFC+095+Confluence+editor+responsiveness+bucketed+INP+telemetry).
|
|
28
29
|
See the package README for the event shape, the snapshot cadence and what ends a session.
|
|
29
30
|
|
|
30
31
|
The plugin has no configuration, state, actions or commands — adding it to a preset is what turns
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-interactivity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Interactivity plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"sideEffects": false,
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@atlaskit/browser-apis": "^1.2.0",
|
|
23
|
-
"@atlaskit/editor-plugin-analytics": "^
|
|
24
|
-
"@atlaskit/editor-plugin-context-identifier": "^
|
|
25
|
-
"@atlaskit/editor-plugin-editor-viewmode": "^
|
|
23
|
+
"@atlaskit/editor-plugin-analytics": "^16.0.0",
|
|
24
|
+
"@atlaskit/editor-plugin-context-identifier": "^16.0.0",
|
|
25
|
+
"@atlaskit/editor-plugin-editor-viewmode": "^18.0.0",
|
|
26
26
|
"@babel/runtime": "^7.0.0",
|
|
27
27
|
"bind-event-listener": "^3.0.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@atlaskit/editor-common": "^
|
|
30
|
+
"@atlaskit/editor-common": "^120.0.0",
|
|
31
31
|
"react": "^18.2.0 || ^19.2.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|