@atlaskit/editor-plugin-interactivity 0.3.0 → 1.1.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 +16 -0
- package/README.md +34 -0
- package/afm-cc/tsconfig.json +3 -0
- package/afm-products/tsconfig.json +3 -0
- package/dist/cjs/collector/interaction-events.js +0 -7
- package/dist/cjs/collector/interaction-tracker.js +5 -3
- package/dist/cjs/collector/interactivity-collector.js +27 -7
- package/dist/cjs/collector/interactivity-session.js +3 -0
- package/dist/cjs/collector/slow-interaction-list.js +172 -0
- package/dist/es2019/collector/interaction-events.js +0 -7
- package/dist/es2019/collector/interaction-tracker.js +5 -3
- package/dist/es2019/collector/interactivity-collector.js +25 -6
- package/dist/es2019/collector/interactivity-session.js +3 -0
- package/dist/es2019/collector/slow-interaction-list.js +148 -0
- package/dist/esm/collector/interaction-events.js +0 -7
- package/dist/esm/collector/interaction-tracker.js +5 -3
- package/dist/esm/collector/interactivity-collector.js +27 -7
- package/dist/esm/collector/interactivity-session.js +3 -0
- package/dist/esm/collector/slow-interaction-list.js +166 -0
- package/dist/types/analytics/interactivity-snapshot.d.ts +34 -0
- package/dist/types/collector/interaction-events.d.ts +1 -7
- package/dist/types/collector/interaction-tracker.d.ts +9 -4
- package/dist/types/collector/interactivity-session.d.ts +2 -0
- package/dist/types/collector/slow-interaction-list.d.ts +38 -0
- package/docs/0-intro.tsx +2 -1
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-interactivity
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`ccd1b52723728`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ccd1b52723728) -
|
|
8
|
+
Report the slowest interactions of the session in the `editor interactivity` event — their group,
|
|
9
|
+
event type, latency, timing phases and a sanitised target — so a regression the histograms show
|
|
10
|
+
can be traced to what was interacted with. Behind the platform_editor_editor_interactivity_slowest
|
|
11
|
+
experiment.
|
|
12
|
+
|
|
13
|
+
## 1.0.0
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
|
|
3
19
|
## 0.3.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -31,6 +31,40 @@ interactions were slow, so this plugin keeps bucketed counts instead.
|
|
|
31
31
|
bucket means zero. The boundaries are versioned by `schema`.
|
|
32
32
|
- Snapshots are session-to-date, so a cohort query takes the highest `seq` per
|
|
33
33
|
`interactivitySessionId` and then sums bucket counts.
|
|
34
|
+
- `slowest` — up to five of the slowest interactions of the session, slowest first. See below.
|
|
35
|
+
|
|
36
|
+
## The slowest interactions
|
|
37
|
+
|
|
38
|
+
The histograms count how many interactions were slow. They cannot say what was interacted with, so
|
|
39
|
+
a regression they show cannot be diagnosed from telemetry alone. Each record carries the group and
|
|
40
|
+
the event type, the latency, the phases it divides into — input delay, processing, presentation
|
|
41
|
+
delay — and a name for the target.
|
|
42
|
+
|
|
43
|
+
Only interactions above 200 ms, the Google INP "good" threshold, are candidates, and `slowest` is
|
|
44
|
+
left out of the event when none crossed it, which is the common case. That is also what keeps the
|
|
45
|
+
cost down: a target is named only for the few interactions slow enough to be recorded, never for
|
|
46
|
+
every one the browser reports.
|
|
47
|
+
|
|
48
|
+
Records are kept per interaction, because an interaction's latency grows as later entries arrive:
|
|
49
|
+
one that is already recorded is replaced by its slower self instead of taking a second place. Its
|
|
50
|
+
attribution comes from the entry that measured it at its slowest, read as that entry arrives —
|
|
51
|
+
`entry.target` is `null` once the element has left the document, so there is no reading it later.
|
|
52
|
+
|
|
53
|
+
The target is a short path of tag names plus the first allow-listed attribute above it, such as
|
|
54
|
+
`div[data-vc="editor"] > p > span`. Nothing in it is document content: a tag name comes from the
|
|
55
|
+
schema, and the only attributes read are ones we put there ourselves. Ids, roles, class names, text
|
|
56
|
+
and accessibility labels are all left out, and the path is bounded in both depth and length.
|
|
57
|
+
|
|
58
|
+
An interaction that is not the editor's is recorded as `group: 'outsideEditor'`, so the records also
|
|
59
|
+
answer whether the slow interactions of a session were the editor's at all. That group is not the
|
|
60
|
+
`page` histogram: `page` counts the editor's interactions as well, `outsideEditor` counts only the
|
|
61
|
+
ones that are not.
|
|
62
|
+
|
|
63
|
+
Collecting them is its own experiment, `platform_editor_editor_interactivity_slowest`, read where a
|
|
64
|
+
session decides whether to keep them rather than where the plugin is added to a preset: the plugin
|
|
65
|
+
only exists when the experiment reporting the histograms is on, so the exposure lands on the
|
|
66
|
+
sessions that already send the event, and the cost of the records stays separable from the cost of
|
|
67
|
+
the event carrying them. Nothing is collected, named or sent when it is off.
|
|
34
68
|
|
|
35
69
|
## The editor groups
|
|
36
70
|
|
package/afm-cc/tsconfig.json
CHANGED
|
@@ -5,13 +5,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.INTERACTION_EVENT_TYPES = void 0;
|
|
7
7
|
exports.interactionEventKind = interactionEventKind;
|
|
8
|
-
/**
|
|
9
|
-
* The editor group an interaction belongs to, which is also the field the event reports it in.
|
|
10
|
-
*
|
|
11
|
-
* `editorOther` is the remainder — an interaction the browser counts that is neither typing nor
|
|
12
|
-
* pointing — so the three groups together cover whatever the browser calls an interaction.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
8
|
/**
|
|
16
9
|
* The events an interaction is made of, and the group they belong to. Both sides of the collector
|
|
17
10
|
* read this, so counting and grouping cannot disagree.
|
|
@@ -13,9 +13,9 @@ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol
|
|
|
13
13
|
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; } }
|
|
14
14
|
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; }
|
|
15
15
|
/**
|
|
16
|
-
* The Event Timing fields this package reads
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* The Event Timing fields this package reads, declared optional so that a `PerformanceEntry` from
|
|
17
|
+
* `getEntries()` is assignable without a cast — `interactionId` is missing from the DOM typings'
|
|
18
|
+
* `PerformanceEventTiming` altogether, and the rest are only on it.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -122,6 +122,7 @@ var InteractionTracker = exports.InteractionTracker = /*#__PURE__*/function () {
|
|
|
122
122
|
this.prune(this.interactions);
|
|
123
123
|
return {
|
|
124
124
|
type: 'new',
|
|
125
|
+
interactionId: interactionId,
|
|
125
126
|
latencyMs: entry.duration,
|
|
126
127
|
group: group
|
|
127
128
|
};
|
|
@@ -133,6 +134,7 @@ var InteractionTracker = exports.InteractionTracker = /*#__PURE__*/function () {
|
|
|
133
134
|
tracked.latencyMs = entry.duration;
|
|
134
135
|
return {
|
|
135
136
|
type: 'remeasured',
|
|
137
|
+
interactionId: interactionId,
|
|
136
138
|
fromMs: fromMs,
|
|
137
139
|
toMs: entry.duration,
|
|
138
140
|
group: tracked.group
|
|
@@ -15,6 +15,8 @@ var _interactivitySession = require("./interactivity-session");
|
|
|
15
15
|
var _bucketBoundaries = require("./bucket-boundaries");
|
|
16
16
|
var _lifecycleObserver = require("./lifecycle-observer");
|
|
17
17
|
var _snapshotScheduler = require("./snapshot-scheduler");
|
|
18
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
19
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
18
20
|
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; } } }; }
|
|
19
21
|
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; } }
|
|
20
22
|
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; }
|
|
@@ -259,27 +261,41 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
259
261
|
value: function recordEntries(entries) {
|
|
260
262
|
var _this$session = this.session,
|
|
261
263
|
tracker = _this$session.tracker,
|
|
262
|
-
page = _this$session.page
|
|
264
|
+
page = _this$session.page,
|
|
265
|
+
slowest = _this$session.slowest;
|
|
263
266
|
var _iterator = _createForOfIteratorHelper(entries),
|
|
264
267
|
_step;
|
|
265
268
|
try {
|
|
266
269
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
270
|
+
var _update$group;
|
|
267
271
|
var entry = _step.value;
|
|
268
272
|
var update = tracker.merge(entry);
|
|
269
273
|
if (!update) {
|
|
270
274
|
continue;
|
|
271
275
|
}
|
|
276
|
+
var latencyMs = void 0;
|
|
272
277
|
if (update.type === 'new') {
|
|
273
|
-
|
|
278
|
+
latencyMs = update.latencyMs;
|
|
279
|
+
page.add(latencyMs);
|
|
274
280
|
if (update.group) {
|
|
275
|
-
this.session[update.group].add(
|
|
281
|
+
this.session[update.group].add(latencyMs);
|
|
276
282
|
}
|
|
277
283
|
} else {
|
|
278
|
-
|
|
284
|
+
latencyMs = update.toMs;
|
|
285
|
+
page.remeasure(update.fromMs, latencyMs);
|
|
279
286
|
if (update.group) {
|
|
280
|
-
this.session[update.group].remeasure(update.fromMs,
|
|
287
|
+
this.session[update.group].remeasure(update.fromMs, latencyMs);
|
|
281
288
|
}
|
|
282
289
|
}
|
|
290
|
+
|
|
291
|
+
// An interaction the editor never reported an event for is not the editor's as far as we
|
|
292
|
+
// know.
|
|
293
|
+
slowest === null || slowest === void 0 || slowest.track({
|
|
294
|
+
entry: entry,
|
|
295
|
+
interactionId: update.interactionId,
|
|
296
|
+
latencyMs: latencyMs,
|
|
297
|
+
group: (_update$group = update.group) !== null && _update$group !== void 0 ? _update$group : 'outsideEditor'
|
|
298
|
+
});
|
|
283
299
|
this.session.revision += 1;
|
|
284
300
|
}
|
|
285
301
|
} catch (err) {
|
|
@@ -291,6 +307,7 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
291
307
|
}, {
|
|
292
308
|
key: "takeSnapshot",
|
|
293
309
|
value: function takeSnapshot(reason) {
|
|
310
|
+
var _session$slowest;
|
|
294
311
|
if (this.stopped) {
|
|
295
312
|
return;
|
|
296
313
|
}
|
|
@@ -319,7 +336,8 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
319
336
|
var now = performance.now();
|
|
320
337
|
var hiddenMs = session.hiddenMs + (session.hiddenSince === undefined ? 0 : now - session.hiddenSince);
|
|
321
338
|
var pageTotalCount = _interactionObserver.InteractionObserver.readPageInteractionCount() - session.interactionCountAtStart;
|
|
322
|
-
|
|
339
|
+
var slowest = (_session$slowest = session.slowest) === null || _session$slowest === void 0 ? void 0 : _session$slowest.snapshot();
|
|
340
|
+
this.emit(_objectSpread({
|
|
323
341
|
schema: _bucketBoundaries.SCHEMA_VERSION,
|
|
324
342
|
interactivitySessionId: session.id,
|
|
325
343
|
objectId: session.objectId,
|
|
@@ -335,7 +353,9 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
335
353
|
editorTyping: session.editorTyping.snapshot(),
|
|
336
354
|
editorPointer: session.editorPointer.snapshot(),
|
|
337
355
|
editorOther: session.editorOther.snapshot()
|
|
338
|
-
}
|
|
356
|
+
}, slowest && {
|
|
357
|
+
slowest: slowest
|
|
358
|
+
}));
|
|
339
359
|
}
|
|
340
360
|
}]);
|
|
341
361
|
}();
|
|
@@ -8,9 +8,11 @@ exports.InteractivitySession = void 0;
|
|
|
8
8
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
9
9
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
10
10
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
|
+
var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
|
|
11
12
|
var _interactionObserver = require("./interaction-observer");
|
|
12
13
|
var _interactionTracker = require("./interaction-tracker");
|
|
13
14
|
var _interactionGroup = require("./interaction-group");
|
|
15
|
+
var _slowInteractionList = require("./slow-interaction-list");
|
|
14
16
|
function createSessionId() {
|
|
15
17
|
if (typeof crypto.randomUUID === 'function') {
|
|
16
18
|
return crypto.randomUUID();
|
|
@@ -37,6 +39,7 @@ var InteractivitySession = exports.InteractivitySession = /*#__PURE__*/(0, _crea
|
|
|
37
39
|
(0, _defineProperty2.default)(this, "editorTyping", new _interactionGroup.InteractionGroup());
|
|
38
40
|
(0, _defineProperty2.default)(this, "editorPointer", new _interactionGroup.InteractionGroup());
|
|
39
41
|
(0, _defineProperty2.default)(this, "editorOther", new _interactionGroup.InteractionGroup());
|
|
42
|
+
(0, _defineProperty2.default)(this, "slowest", (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_editor_interactivity_slowest') ? new _slowInteractionList.SlowInteractionList() : undefined);
|
|
40
43
|
/** Increments per snapshot; a query takes the highest one per session. */
|
|
41
44
|
(0, _defineProperty2.default)(this, "seq", 0);
|
|
42
45
|
(0, _defineProperty2.default)(this, "hiddenMs", 0);
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.SlowInteractionList = void 0;
|
|
8
|
+
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
|
|
9
|
+
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
|
|
10
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
|
+
/** Fixed by the schema, so the event stays a bounded size. */
|
|
12
|
+
var MAX_RECORDS = 5;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The latency an interaction has to beat to be recorded. 200 ms is the Google INP "good" threshold,
|
|
16
|
+
* so anything below it is an interaction the user was not waiting for.
|
|
17
|
+
*/
|
|
18
|
+
var MIN_LATENCY_MS = 200;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The attributes a target may be named by, all of them ours: `data-vc` for visual completion, the
|
|
22
|
+
* test ids for tests. Ids, roles, class names, text and accessibility labels are left out because
|
|
23
|
+
* they can carry what the user wrote.
|
|
24
|
+
*/
|
|
25
|
+
var ALLOWED_TARGET_ATTRIBUTES = ['data-vc', 'data-testid', 'data-test-id'];
|
|
26
|
+
var MAX_TARGET_ELEMENTS = 4;
|
|
27
|
+
var MAX_ATTRIBUTE_VALUE_LENGTH = 32;
|
|
28
|
+
var MAX_TARGET_LENGTH = 120;
|
|
29
|
+
|
|
30
|
+
/** The id identifies an interaction across the entries measuring it. It is not reported. */
|
|
31
|
+
|
|
32
|
+
/** An interaction as the collector's two observers saw it. */
|
|
33
|
+
/**
|
|
34
|
+
* The slowest interactions of one session, which is what the event's `slowest` records are.
|
|
35
|
+
*
|
|
36
|
+
* A record is built from the entry that measured the interaction, as that entry arrives:
|
|
37
|
+
* `entry.target` is `null` once the element has left the document.
|
|
38
|
+
*/
|
|
39
|
+
var SlowInteractionList = exports.SlowInteractionList = /*#__PURE__*/function () {
|
|
40
|
+
function SlowInteractionList() {
|
|
41
|
+
(0, _classCallCheck2.default)(this, SlowInteractionList);
|
|
42
|
+
/** Slowest first. */
|
|
43
|
+
(0, _defineProperty2.default)(this, "records", []);
|
|
44
|
+
}
|
|
45
|
+
return (0, _createClass2.default)(SlowInteractionList, [{
|
|
46
|
+
key: "track",
|
|
47
|
+
value: function track(interaction) {
|
|
48
|
+
var interactionId = interaction.interactionId,
|
|
49
|
+
latencyMs = interaction.latencyMs;
|
|
50
|
+
var previousIndex = this.records.findIndex(function (existing) {
|
|
51
|
+
return existing.interactionId === interactionId;
|
|
52
|
+
});
|
|
53
|
+
var toBeatMs = MIN_LATENCY_MS;
|
|
54
|
+
if (previousIndex !== -1) {
|
|
55
|
+
// An interaction's latency grows as later entries arrive, and it replaces itself rather
|
|
56
|
+
// than taking a second place.
|
|
57
|
+
toBeatMs = this.records[previousIndex].durationMs;
|
|
58
|
+
} else if (this.records.length === MAX_RECORDS) {
|
|
59
|
+
toBeatMs = this.records[MAX_RECORDS - 1].durationMs;
|
|
60
|
+
}
|
|
61
|
+
if (latencyMs <= toBeatMs) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Read only once the interaction has earned a place: naming its target walks the DOM, and
|
|
66
|
+
// this runs while the page is already slow.
|
|
67
|
+
var record = this.toRecord(interaction);
|
|
68
|
+
if (previousIndex === -1) {
|
|
69
|
+
this.records.push(record);
|
|
70
|
+
} else {
|
|
71
|
+
this.records[previousIndex] = record;
|
|
72
|
+
}
|
|
73
|
+
this.records.sort(function (a, b) {
|
|
74
|
+
return b.durationMs - a.durationMs;
|
|
75
|
+
});
|
|
76
|
+
this.records.splice(MAX_RECORDS);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @returns the records, slowest first, or nothing when no interaction was slow enough. */
|
|
80
|
+
}, {
|
|
81
|
+
key: "snapshot",
|
|
82
|
+
value: function snapshot() {
|
|
83
|
+
if (this.records.length === 0) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return this.records.map(function (record) {
|
|
87
|
+
return {
|
|
88
|
+
group: record.group,
|
|
89
|
+
name: record.name,
|
|
90
|
+
durationMs: record.durationMs,
|
|
91
|
+
inputDelayMs: record.inputDelayMs,
|
|
92
|
+
processingMs: record.processingMs,
|
|
93
|
+
presentationDelayMs: record.presentationDelayMs,
|
|
94
|
+
target: record.target
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The phases are reported together or not at all: a browser that reports one reports all three,
|
|
101
|
+
* and zeroes standing in for values we never had would read as an interaction that spent no time
|
|
102
|
+
* anywhere. Each is clamped, because they come from timestamps the browser coarsens
|
|
103
|
+
* independently, so a phase can come out just below zero.
|
|
104
|
+
*/
|
|
105
|
+
}, {
|
|
106
|
+
key: "toRecord",
|
|
107
|
+
value: function toRecord(_ref) {
|
|
108
|
+
var entry = _ref.entry,
|
|
109
|
+
group = _ref.group,
|
|
110
|
+
interactionId = _ref.interactionId,
|
|
111
|
+
latencyMs = _ref.latencyMs;
|
|
112
|
+
var processingStart = entry.processingStart,
|
|
113
|
+
processingEnd = entry.processingEnd,
|
|
114
|
+
startTime = entry.startTime;
|
|
115
|
+
var measured = typeof processingStart === 'number' && typeof processingEnd === 'number';
|
|
116
|
+
var phaseMs = function phaseMs(durationMs) {
|
|
117
|
+
return Math.max(0, Math.round(durationMs));
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
interactionId: interactionId,
|
|
121
|
+
group: group,
|
|
122
|
+
name: entry.name,
|
|
123
|
+
durationMs: Math.round(latencyMs),
|
|
124
|
+
inputDelayMs: measured ? phaseMs(processingStart - startTime) : undefined,
|
|
125
|
+
processingMs: measured ? phaseMs(processingEnd - processingStart) : undefined,
|
|
126
|
+
presentationDelayMs: measured ? phaseMs(startTime + latencyMs - processingEnd) : undefined,
|
|
127
|
+
target: this.describeTarget(entry.target)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Names the element an interaction happened on — `div[data-vc="x"] > p > span`, outermost first.
|
|
133
|
+
* The path climbs until an element carries an allow-listed attribute, because that is what says
|
|
134
|
+
* which part of the page this was.
|
|
135
|
+
*/
|
|
136
|
+
}, {
|
|
137
|
+
key: "describeTarget",
|
|
138
|
+
value: function describeTarget(node) {
|
|
139
|
+
var _node$parentElement;
|
|
140
|
+
// An event's target can be a text node, and the element around it is the answer for it.
|
|
141
|
+
var element = node instanceof Element ? node : (_node$parentElement = node === null || node === void 0 ? void 0 : node.parentElement) !== null && _node$parentElement !== void 0 ? _node$parentElement : null;
|
|
142
|
+
var path = [];
|
|
143
|
+
for (var climbed = 0; element && climbed < MAX_TARGET_ELEMENTS; climbed += 1) {
|
|
144
|
+
var attribute = this.identifyingAttribute(element);
|
|
145
|
+
path.unshift("".concat(element.localName).concat(attribute !== null && attribute !== void 0 ? attribute : ''));
|
|
146
|
+
if (attribute) {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
element = element.parentElement;
|
|
150
|
+
}
|
|
151
|
+
if (path.length === 0) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
return path.join(' > ').slice(0, MAX_TARGET_LENGTH);
|
|
155
|
+
}
|
|
156
|
+
}, {
|
|
157
|
+
key: "identifyingAttribute",
|
|
158
|
+
value: function identifyingAttribute(element) {
|
|
159
|
+
for (var _i = 0, _ALLOWED_TARGET_ATTRI = ALLOWED_TARGET_ATTRIBUTES; _i < _ALLOWED_TARGET_ATTRI.length; _i++) {
|
|
160
|
+
var attribute = _ALLOWED_TARGET_ATTRI[_i];
|
|
161
|
+
var value = element.getAttribute(attribute);
|
|
162
|
+
if (value) {
|
|
163
|
+
// Encoded and cut: a value we did not write cannot bring quotes or a paragraph of
|
|
164
|
+
// text into the event.
|
|
165
|
+
var safeValue = encodeURIComponent(value).slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
|
|
166
|
+
return "[".concat(attribute, "=\"").concat(safeValue, "\"]");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}]);
|
|
172
|
+
}();
|
|
@@ -1,10 +1,3 @@
|
|
|
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
|
-
|
|
8
1
|
/**
|
|
9
2
|
* The events an interaction is made of, and the group they belong to. Both sides of the collector
|
|
10
3
|
* read this, so counting and grouping cannot disagree.
|
|
@@ -2,9 +2,9 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
|
2
2
|
import { interactionEventKind } from './interaction-events';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The Event Timing fields this package reads
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The Event Timing fields this package reads, declared optional so that a `PerformanceEntry` from
|
|
6
|
+
* `getEntries()` is assignable without a cast — `interactionId` is missing from the DOM typings'
|
|
7
|
+
* `PerformanceEventTiming` altogether, and the rest are only on it.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -107,6 +107,7 @@ export class InteractionTracker {
|
|
|
107
107
|
this.prune(this.interactions);
|
|
108
108
|
return {
|
|
109
109
|
type: 'new',
|
|
110
|
+
interactionId,
|
|
110
111
|
latencyMs: entry.duration,
|
|
111
112
|
group
|
|
112
113
|
};
|
|
@@ -118,6 +119,7 @@ export class InteractionTracker {
|
|
|
118
119
|
tracked.latencyMs = entry.duration;
|
|
119
120
|
return {
|
|
120
121
|
type: 'remeasured',
|
|
122
|
+
interactionId,
|
|
121
123
|
fromMs,
|
|
122
124
|
toMs: entry.duration,
|
|
123
125
|
group: tracked.group
|
|
@@ -203,28 +203,43 @@ export class InteractivityCollector {
|
|
|
203
203
|
recordEntries(entries) {
|
|
204
204
|
const {
|
|
205
205
|
tracker,
|
|
206
|
-
page
|
|
206
|
+
page,
|
|
207
|
+
slowest
|
|
207
208
|
} = this.session;
|
|
208
209
|
for (const entry of entries) {
|
|
210
|
+
var _update$group;
|
|
209
211
|
const update = tracker.merge(entry);
|
|
210
212
|
if (!update) {
|
|
211
213
|
continue;
|
|
212
214
|
}
|
|
215
|
+
let latencyMs;
|
|
213
216
|
if (update.type === 'new') {
|
|
214
|
-
|
|
217
|
+
latencyMs = update.latencyMs;
|
|
218
|
+
page.add(latencyMs);
|
|
215
219
|
if (update.group) {
|
|
216
|
-
this.session[update.group].add(
|
|
220
|
+
this.session[update.group].add(latencyMs);
|
|
217
221
|
}
|
|
218
222
|
} else {
|
|
219
|
-
|
|
223
|
+
latencyMs = update.toMs;
|
|
224
|
+
page.remeasure(update.fromMs, latencyMs);
|
|
220
225
|
if (update.group) {
|
|
221
|
-
this.session[update.group].remeasure(update.fromMs,
|
|
226
|
+
this.session[update.group].remeasure(update.fromMs, latencyMs);
|
|
222
227
|
}
|
|
223
228
|
}
|
|
229
|
+
|
|
230
|
+
// An interaction the editor never reported an event for is not the editor's as far as we
|
|
231
|
+
// know.
|
|
232
|
+
slowest === null || slowest === void 0 ? void 0 : slowest.track({
|
|
233
|
+
entry,
|
|
234
|
+
interactionId: update.interactionId,
|
|
235
|
+
latencyMs,
|
|
236
|
+
group: (_update$group = update.group) !== null && _update$group !== void 0 ? _update$group : 'outsideEditor'
|
|
237
|
+
});
|
|
224
238
|
this.session.revision += 1;
|
|
225
239
|
}
|
|
226
240
|
}
|
|
227
241
|
takeSnapshot(reason) {
|
|
242
|
+
var _session$slowest;
|
|
228
243
|
if (this.stopped) {
|
|
229
244
|
return;
|
|
230
245
|
}
|
|
@@ -253,6 +268,7 @@ export class InteractivityCollector {
|
|
|
253
268
|
const now = performance.now();
|
|
254
269
|
const hiddenMs = session.hiddenMs + (session.hiddenSince === undefined ? 0 : now - session.hiddenSince);
|
|
255
270
|
const pageTotalCount = InteractionObserver.readPageInteractionCount() - session.interactionCountAtStart;
|
|
271
|
+
const slowest = (_session$slowest = session.slowest) === null || _session$slowest === void 0 ? void 0 : _session$slowest.snapshot();
|
|
256
272
|
this.emit({
|
|
257
273
|
schema: SCHEMA_VERSION,
|
|
258
274
|
interactivitySessionId: session.id,
|
|
@@ -268,7 +284,10 @@ export class InteractivityCollector {
|
|
|
268
284
|
page: session.page.snapshot(pageTotalCount),
|
|
269
285
|
editorTyping: session.editorTyping.snapshot(),
|
|
270
286
|
editorPointer: session.editorPointer.snapshot(),
|
|
271
|
-
editorOther: session.editorOther.snapshot()
|
|
287
|
+
editorOther: session.editorOther.snapshot(),
|
|
288
|
+
...(slowest && {
|
|
289
|
+
slowest
|
|
290
|
+
})
|
|
272
291
|
});
|
|
273
292
|
}
|
|
274
293
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
2
3
|
import { InteractionObserver } from './interaction-observer';
|
|
3
4
|
import { InteractionTracker } from './interaction-tracker';
|
|
4
5
|
import { InteractionGroup } from './interaction-group';
|
|
6
|
+
import { SlowInteractionList } from './slow-interaction-list';
|
|
5
7
|
function createSessionId() {
|
|
6
8
|
if (typeof crypto.randomUUID === 'function') {
|
|
7
9
|
return crypto.randomUUID();
|
|
@@ -26,6 +28,7 @@ export class InteractivitySession {
|
|
|
26
28
|
_defineProperty(this, "editorTyping", new InteractionGroup());
|
|
27
29
|
_defineProperty(this, "editorPointer", new InteractionGroup());
|
|
28
30
|
_defineProperty(this, "editorOther", new InteractionGroup());
|
|
31
|
+
_defineProperty(this, "slowest", isExperimentEnabled('platform_editor_editor_interactivity_slowest') ? new SlowInteractionList() : undefined);
|
|
29
32
|
/** Increments per snapshot; a query takes the highest one per session. */
|
|
30
33
|
_defineProperty(this, "seq", 0);
|
|
31
34
|
_defineProperty(this, "hiddenMs", 0);
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
/** Fixed by the schema, so the event stays a bounded size. */
|
|
3
|
+
const MAX_RECORDS = 5;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The latency an interaction has to beat to be recorded. 200 ms is the Google INP "good" threshold,
|
|
7
|
+
* so anything below it is an interaction the user was not waiting for.
|
|
8
|
+
*/
|
|
9
|
+
const MIN_LATENCY_MS = 200;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The attributes a target may be named by, all of them ours: `data-vc` for visual completion, the
|
|
13
|
+
* test ids for tests. Ids, roles, class names, text and accessibility labels are left out because
|
|
14
|
+
* they can carry what the user wrote.
|
|
15
|
+
*/
|
|
16
|
+
const ALLOWED_TARGET_ATTRIBUTES = ['data-vc', 'data-testid', 'data-test-id'];
|
|
17
|
+
const MAX_TARGET_ELEMENTS = 4;
|
|
18
|
+
const MAX_ATTRIBUTE_VALUE_LENGTH = 32;
|
|
19
|
+
const MAX_TARGET_LENGTH = 120;
|
|
20
|
+
|
|
21
|
+
/** The id identifies an interaction across the entries measuring it. It is not reported. */
|
|
22
|
+
|
|
23
|
+
/** An interaction as the collector's two observers saw it. */
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The slowest interactions of one session, which is what the event's `slowest` records are.
|
|
27
|
+
*
|
|
28
|
+
* A record is built from the entry that measured the interaction, as that entry arrives:
|
|
29
|
+
* `entry.target` is `null` once the element has left the document.
|
|
30
|
+
*/
|
|
31
|
+
export class SlowInteractionList {
|
|
32
|
+
constructor() {
|
|
33
|
+
/** Slowest first. */
|
|
34
|
+
_defineProperty(this, "records", []);
|
|
35
|
+
}
|
|
36
|
+
track(interaction) {
|
|
37
|
+
const {
|
|
38
|
+
interactionId,
|
|
39
|
+
latencyMs
|
|
40
|
+
} = interaction;
|
|
41
|
+
const previousIndex = this.records.findIndex(existing => existing.interactionId === interactionId);
|
|
42
|
+
let toBeatMs = MIN_LATENCY_MS;
|
|
43
|
+
if (previousIndex !== -1) {
|
|
44
|
+
// An interaction's latency grows as later entries arrive, and it replaces itself rather
|
|
45
|
+
// than taking a second place.
|
|
46
|
+
toBeatMs = this.records[previousIndex].durationMs;
|
|
47
|
+
} else if (this.records.length === MAX_RECORDS) {
|
|
48
|
+
toBeatMs = this.records[MAX_RECORDS - 1].durationMs;
|
|
49
|
+
}
|
|
50
|
+
if (latencyMs <= toBeatMs) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Read only once the interaction has earned a place: naming its target walks the DOM, and
|
|
55
|
+
// this runs while the page is already slow.
|
|
56
|
+
const record = this.toRecord(interaction);
|
|
57
|
+
if (previousIndex === -1) {
|
|
58
|
+
this.records.push(record);
|
|
59
|
+
} else {
|
|
60
|
+
this.records[previousIndex] = record;
|
|
61
|
+
}
|
|
62
|
+
this.records.sort((a, b) => b.durationMs - a.durationMs);
|
|
63
|
+
this.records.splice(MAX_RECORDS);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** @returns the records, slowest first, or nothing when no interaction was slow enough. */
|
|
67
|
+
snapshot() {
|
|
68
|
+
if (this.records.length === 0) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
return this.records.map(record => ({
|
|
72
|
+
group: record.group,
|
|
73
|
+
name: record.name,
|
|
74
|
+
durationMs: record.durationMs,
|
|
75
|
+
inputDelayMs: record.inputDelayMs,
|
|
76
|
+
processingMs: record.processingMs,
|
|
77
|
+
presentationDelayMs: record.presentationDelayMs,
|
|
78
|
+
target: record.target
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The phases are reported together or not at all: a browser that reports one reports all three,
|
|
84
|
+
* and zeroes standing in for values we never had would read as an interaction that spent no time
|
|
85
|
+
* anywhere. Each is clamped, because they come from timestamps the browser coarsens
|
|
86
|
+
* independently, so a phase can come out just below zero.
|
|
87
|
+
*/
|
|
88
|
+
toRecord({
|
|
89
|
+
entry,
|
|
90
|
+
group,
|
|
91
|
+
interactionId,
|
|
92
|
+
latencyMs
|
|
93
|
+
}) {
|
|
94
|
+
const {
|
|
95
|
+
processingStart,
|
|
96
|
+
processingEnd,
|
|
97
|
+
startTime
|
|
98
|
+
} = entry;
|
|
99
|
+
const measured = typeof processingStart === 'number' && typeof processingEnd === 'number';
|
|
100
|
+
const phaseMs = durationMs => Math.max(0, Math.round(durationMs));
|
|
101
|
+
return {
|
|
102
|
+
interactionId,
|
|
103
|
+
group,
|
|
104
|
+
name: entry.name,
|
|
105
|
+
durationMs: Math.round(latencyMs),
|
|
106
|
+
inputDelayMs: measured ? phaseMs(processingStart - startTime) : undefined,
|
|
107
|
+
processingMs: measured ? phaseMs(processingEnd - processingStart) : undefined,
|
|
108
|
+
presentationDelayMs: measured ? phaseMs(startTime + latencyMs - processingEnd) : undefined,
|
|
109
|
+
target: this.describeTarget(entry.target)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Names the element an interaction happened on — `div[data-vc="x"] > p > span`, outermost first.
|
|
115
|
+
* The path climbs until an element carries an allow-listed attribute, because that is what says
|
|
116
|
+
* which part of the page this was.
|
|
117
|
+
*/
|
|
118
|
+
describeTarget(node) {
|
|
119
|
+
var _node$parentElement;
|
|
120
|
+
// An event's target can be a text node, and the element around it is the answer for it.
|
|
121
|
+
let element = node instanceof Element ? node : (_node$parentElement = node === null || node === void 0 ? void 0 : node.parentElement) !== null && _node$parentElement !== void 0 ? _node$parentElement : null;
|
|
122
|
+
const path = [];
|
|
123
|
+
for (let climbed = 0; element && climbed < MAX_TARGET_ELEMENTS; climbed += 1) {
|
|
124
|
+
const attribute = this.identifyingAttribute(element);
|
|
125
|
+
path.unshift(`${element.localName}${attribute !== null && attribute !== void 0 ? attribute : ''}`);
|
|
126
|
+
if (attribute) {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
element = element.parentElement;
|
|
130
|
+
}
|
|
131
|
+
if (path.length === 0) {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
return path.join(' > ').slice(0, MAX_TARGET_LENGTH);
|
|
135
|
+
}
|
|
136
|
+
identifyingAttribute(element) {
|
|
137
|
+
for (const attribute of ALLOWED_TARGET_ATTRIBUTES) {
|
|
138
|
+
const value = element.getAttribute(attribute);
|
|
139
|
+
if (value) {
|
|
140
|
+
// Encoded and cut: a value we did not write cannot bring quotes or a paragraph of
|
|
141
|
+
// text into the event.
|
|
142
|
+
const safeValue = encodeURIComponent(value).slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
|
|
143
|
+
return `[${attribute}="${safeValue}"]`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -1,10 +1,3 @@
|
|
|
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
|
-
|
|
8
1
|
/**
|
|
9
2
|
* The events an interaction is made of, and the group they belong to. Both sides of the collector
|
|
10
3
|
* read this, so counting and grouping cannot disagree.
|
|
@@ -7,9 +7,9 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
7
7
|
import { interactionEventKind } from './interaction-events';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* The Event Timing fields this package reads
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The Event Timing fields this package reads, declared optional so that a `PerformanceEntry` from
|
|
11
|
+
* `getEntries()` is assignable without a cast — `interactionId` is missing from the DOM typings'
|
|
12
|
+
* `PerformanceEventTiming` altogether, and the rest are only on it.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -116,6 +116,7 @@ export var InteractionTracker = /*#__PURE__*/function () {
|
|
|
116
116
|
this.prune(this.interactions);
|
|
117
117
|
return {
|
|
118
118
|
type: 'new',
|
|
119
|
+
interactionId: interactionId,
|
|
119
120
|
latencyMs: entry.duration,
|
|
120
121
|
group: group
|
|
121
122
|
};
|
|
@@ -127,6 +128,7 @@ export var InteractionTracker = /*#__PURE__*/function () {
|
|
|
127
128
|
tracked.latencyMs = entry.duration;
|
|
128
129
|
return {
|
|
129
130
|
type: 'remeasured',
|
|
131
|
+
interactionId: interactionId,
|
|
130
132
|
fromMs: fromMs,
|
|
131
133
|
toMs: entry.duration,
|
|
132
134
|
group: tracked.group
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
2
2
|
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
3
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
5
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
4
6
|
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
7
|
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
8
|
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; }
|
|
@@ -252,27 +254,41 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
252
254
|
value: function recordEntries(entries) {
|
|
253
255
|
var _this$session = this.session,
|
|
254
256
|
tracker = _this$session.tracker,
|
|
255
|
-
page = _this$session.page
|
|
257
|
+
page = _this$session.page,
|
|
258
|
+
slowest = _this$session.slowest;
|
|
256
259
|
var _iterator = _createForOfIteratorHelper(entries),
|
|
257
260
|
_step;
|
|
258
261
|
try {
|
|
259
262
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
263
|
+
var _update$group;
|
|
260
264
|
var entry = _step.value;
|
|
261
265
|
var update = tracker.merge(entry);
|
|
262
266
|
if (!update) {
|
|
263
267
|
continue;
|
|
264
268
|
}
|
|
269
|
+
var latencyMs = void 0;
|
|
265
270
|
if (update.type === 'new') {
|
|
266
|
-
|
|
271
|
+
latencyMs = update.latencyMs;
|
|
272
|
+
page.add(latencyMs);
|
|
267
273
|
if (update.group) {
|
|
268
|
-
this.session[update.group].add(
|
|
274
|
+
this.session[update.group].add(latencyMs);
|
|
269
275
|
}
|
|
270
276
|
} else {
|
|
271
|
-
|
|
277
|
+
latencyMs = update.toMs;
|
|
278
|
+
page.remeasure(update.fromMs, latencyMs);
|
|
272
279
|
if (update.group) {
|
|
273
|
-
this.session[update.group].remeasure(update.fromMs,
|
|
280
|
+
this.session[update.group].remeasure(update.fromMs, latencyMs);
|
|
274
281
|
}
|
|
275
282
|
}
|
|
283
|
+
|
|
284
|
+
// An interaction the editor never reported an event for is not the editor's as far as we
|
|
285
|
+
// know.
|
|
286
|
+
slowest === null || slowest === void 0 || slowest.track({
|
|
287
|
+
entry: entry,
|
|
288
|
+
interactionId: update.interactionId,
|
|
289
|
+
latencyMs: latencyMs,
|
|
290
|
+
group: (_update$group = update.group) !== null && _update$group !== void 0 ? _update$group : 'outsideEditor'
|
|
291
|
+
});
|
|
276
292
|
this.session.revision += 1;
|
|
277
293
|
}
|
|
278
294
|
} catch (err) {
|
|
@@ -284,6 +300,7 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
284
300
|
}, {
|
|
285
301
|
key: "takeSnapshot",
|
|
286
302
|
value: function takeSnapshot(reason) {
|
|
303
|
+
var _session$slowest;
|
|
287
304
|
if (this.stopped) {
|
|
288
305
|
return;
|
|
289
306
|
}
|
|
@@ -312,7 +329,8 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
312
329
|
var now = performance.now();
|
|
313
330
|
var hiddenMs = session.hiddenMs + (session.hiddenSince === undefined ? 0 : now - session.hiddenSince);
|
|
314
331
|
var pageTotalCount = InteractionObserver.readPageInteractionCount() - session.interactionCountAtStart;
|
|
315
|
-
|
|
332
|
+
var slowest = (_session$slowest = session.slowest) === null || _session$slowest === void 0 ? void 0 : _session$slowest.snapshot();
|
|
333
|
+
this.emit(_objectSpread({
|
|
316
334
|
schema: SCHEMA_VERSION,
|
|
317
335
|
interactivitySessionId: session.id,
|
|
318
336
|
objectId: session.objectId,
|
|
@@ -328,7 +346,9 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
328
346
|
editorTyping: session.editorTyping.snapshot(),
|
|
329
347
|
editorPointer: session.editorPointer.snapshot(),
|
|
330
348
|
editorOther: session.editorOther.snapshot()
|
|
331
|
-
}
|
|
349
|
+
}, slowest && {
|
|
350
|
+
slowest: slowest
|
|
351
|
+
}));
|
|
332
352
|
}
|
|
333
353
|
}]);
|
|
334
354
|
}();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import _createClass from "@babel/runtime/helpers/createClass";
|
|
2
2
|
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
3
3
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
+
import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
|
|
4
5
|
import { InteractionObserver } from './interaction-observer';
|
|
5
6
|
import { InteractionTracker } from './interaction-tracker';
|
|
6
7
|
import { InteractionGroup } from './interaction-group';
|
|
8
|
+
import { SlowInteractionList } from './slow-interaction-list';
|
|
7
9
|
function createSessionId() {
|
|
8
10
|
if (typeof crypto.randomUUID === 'function') {
|
|
9
11
|
return crypto.randomUUID();
|
|
@@ -30,6 +32,7 @@ export var InteractivitySession = /*#__PURE__*/_createClass(function Interactivi
|
|
|
30
32
|
_defineProperty(this, "editorTyping", new InteractionGroup());
|
|
31
33
|
_defineProperty(this, "editorPointer", new InteractionGroup());
|
|
32
34
|
_defineProperty(this, "editorOther", new InteractionGroup());
|
|
35
|
+
_defineProperty(this, "slowest", isExperimentEnabled('platform_editor_editor_interactivity_slowest') ? new SlowInteractionList() : undefined);
|
|
33
36
|
/** Increments per snapshot; a query takes the highest one per session. */
|
|
34
37
|
_defineProperty(this, "seq", 0);
|
|
35
38
|
_defineProperty(this, "hiddenMs", 0);
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
2
|
+
import _createClass from "@babel/runtime/helpers/createClass";
|
|
3
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
4
|
+
/** Fixed by the schema, so the event stays a bounded size. */
|
|
5
|
+
var MAX_RECORDS = 5;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The latency an interaction has to beat to be recorded. 200 ms is the Google INP "good" threshold,
|
|
9
|
+
* so anything below it is an interaction the user was not waiting for.
|
|
10
|
+
*/
|
|
11
|
+
var MIN_LATENCY_MS = 200;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The attributes a target may be named by, all of them ours: `data-vc` for visual completion, the
|
|
15
|
+
* test ids for tests. Ids, roles, class names, text and accessibility labels are left out because
|
|
16
|
+
* they can carry what the user wrote.
|
|
17
|
+
*/
|
|
18
|
+
var ALLOWED_TARGET_ATTRIBUTES = ['data-vc', 'data-testid', 'data-test-id'];
|
|
19
|
+
var MAX_TARGET_ELEMENTS = 4;
|
|
20
|
+
var MAX_ATTRIBUTE_VALUE_LENGTH = 32;
|
|
21
|
+
var MAX_TARGET_LENGTH = 120;
|
|
22
|
+
|
|
23
|
+
/** The id identifies an interaction across the entries measuring it. It is not reported. */
|
|
24
|
+
|
|
25
|
+
/** An interaction as the collector's two observers saw it. */
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The slowest interactions of one session, which is what the event's `slowest` records are.
|
|
29
|
+
*
|
|
30
|
+
* A record is built from the entry that measured the interaction, as that entry arrives:
|
|
31
|
+
* `entry.target` is `null` once the element has left the document.
|
|
32
|
+
*/
|
|
33
|
+
export var SlowInteractionList = /*#__PURE__*/function () {
|
|
34
|
+
function SlowInteractionList() {
|
|
35
|
+
_classCallCheck(this, SlowInteractionList);
|
|
36
|
+
/** Slowest first. */
|
|
37
|
+
_defineProperty(this, "records", []);
|
|
38
|
+
}
|
|
39
|
+
return _createClass(SlowInteractionList, [{
|
|
40
|
+
key: "track",
|
|
41
|
+
value: function track(interaction) {
|
|
42
|
+
var interactionId = interaction.interactionId,
|
|
43
|
+
latencyMs = interaction.latencyMs;
|
|
44
|
+
var previousIndex = this.records.findIndex(function (existing) {
|
|
45
|
+
return existing.interactionId === interactionId;
|
|
46
|
+
});
|
|
47
|
+
var toBeatMs = MIN_LATENCY_MS;
|
|
48
|
+
if (previousIndex !== -1) {
|
|
49
|
+
// An interaction's latency grows as later entries arrive, and it replaces itself rather
|
|
50
|
+
// than taking a second place.
|
|
51
|
+
toBeatMs = this.records[previousIndex].durationMs;
|
|
52
|
+
} else if (this.records.length === MAX_RECORDS) {
|
|
53
|
+
toBeatMs = this.records[MAX_RECORDS - 1].durationMs;
|
|
54
|
+
}
|
|
55
|
+
if (latencyMs <= toBeatMs) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Read only once the interaction has earned a place: naming its target walks the DOM, and
|
|
60
|
+
// this runs while the page is already slow.
|
|
61
|
+
var record = this.toRecord(interaction);
|
|
62
|
+
if (previousIndex === -1) {
|
|
63
|
+
this.records.push(record);
|
|
64
|
+
} else {
|
|
65
|
+
this.records[previousIndex] = record;
|
|
66
|
+
}
|
|
67
|
+
this.records.sort(function (a, b) {
|
|
68
|
+
return b.durationMs - a.durationMs;
|
|
69
|
+
});
|
|
70
|
+
this.records.splice(MAX_RECORDS);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** @returns the records, slowest first, or nothing when no interaction was slow enough. */
|
|
74
|
+
}, {
|
|
75
|
+
key: "snapshot",
|
|
76
|
+
value: function snapshot() {
|
|
77
|
+
if (this.records.length === 0) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
return this.records.map(function (record) {
|
|
81
|
+
return {
|
|
82
|
+
group: record.group,
|
|
83
|
+
name: record.name,
|
|
84
|
+
durationMs: record.durationMs,
|
|
85
|
+
inputDelayMs: record.inputDelayMs,
|
|
86
|
+
processingMs: record.processingMs,
|
|
87
|
+
presentationDelayMs: record.presentationDelayMs,
|
|
88
|
+
target: record.target
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The phases are reported together or not at all: a browser that reports one reports all three,
|
|
95
|
+
* and zeroes standing in for values we never had would read as an interaction that spent no time
|
|
96
|
+
* anywhere. Each is clamped, because they come from timestamps the browser coarsens
|
|
97
|
+
* independently, so a phase can come out just below zero.
|
|
98
|
+
*/
|
|
99
|
+
}, {
|
|
100
|
+
key: "toRecord",
|
|
101
|
+
value: function toRecord(_ref) {
|
|
102
|
+
var entry = _ref.entry,
|
|
103
|
+
group = _ref.group,
|
|
104
|
+
interactionId = _ref.interactionId,
|
|
105
|
+
latencyMs = _ref.latencyMs;
|
|
106
|
+
var processingStart = entry.processingStart,
|
|
107
|
+
processingEnd = entry.processingEnd,
|
|
108
|
+
startTime = entry.startTime;
|
|
109
|
+
var measured = typeof processingStart === 'number' && typeof processingEnd === 'number';
|
|
110
|
+
var phaseMs = function phaseMs(durationMs) {
|
|
111
|
+
return Math.max(0, Math.round(durationMs));
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
interactionId: interactionId,
|
|
115
|
+
group: group,
|
|
116
|
+
name: entry.name,
|
|
117
|
+
durationMs: Math.round(latencyMs),
|
|
118
|
+
inputDelayMs: measured ? phaseMs(processingStart - startTime) : undefined,
|
|
119
|
+
processingMs: measured ? phaseMs(processingEnd - processingStart) : undefined,
|
|
120
|
+
presentationDelayMs: measured ? phaseMs(startTime + latencyMs - processingEnd) : undefined,
|
|
121
|
+
target: this.describeTarget(entry.target)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Names the element an interaction happened on — `div[data-vc="x"] > p > span`, outermost first.
|
|
127
|
+
* The path climbs until an element carries an allow-listed attribute, because that is what says
|
|
128
|
+
* which part of the page this was.
|
|
129
|
+
*/
|
|
130
|
+
}, {
|
|
131
|
+
key: "describeTarget",
|
|
132
|
+
value: function describeTarget(node) {
|
|
133
|
+
var _node$parentElement;
|
|
134
|
+
// An event's target can be a text node, and the element around it is the answer for it.
|
|
135
|
+
var element = node instanceof Element ? node : (_node$parentElement = node === null || node === void 0 ? void 0 : node.parentElement) !== null && _node$parentElement !== void 0 ? _node$parentElement : null;
|
|
136
|
+
var path = [];
|
|
137
|
+
for (var climbed = 0; element && climbed < MAX_TARGET_ELEMENTS; climbed += 1) {
|
|
138
|
+
var attribute = this.identifyingAttribute(element);
|
|
139
|
+
path.unshift("".concat(element.localName).concat(attribute !== null && attribute !== void 0 ? attribute : ''));
|
|
140
|
+
if (attribute) {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
element = element.parentElement;
|
|
144
|
+
}
|
|
145
|
+
if (path.length === 0) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
return path.join(' > ').slice(0, MAX_TARGET_LENGTH);
|
|
149
|
+
}
|
|
150
|
+
}, {
|
|
151
|
+
key: "identifyingAttribute",
|
|
152
|
+
value: function identifyingAttribute(element) {
|
|
153
|
+
for (var _i = 0, _ALLOWED_TARGET_ATTRI = ALLOWED_TARGET_ATTRIBUTES; _i < _ALLOWED_TARGET_ATTRI.length; _i++) {
|
|
154
|
+
var attribute = _ALLOWED_TARGET_ATTRI[_i];
|
|
155
|
+
var value = element.getAttribute(attribute);
|
|
156
|
+
if (value) {
|
|
157
|
+
// Encoded and cut: a value we did not write cannot bring quotes or a paragraph of
|
|
158
|
+
// text into the event.
|
|
159
|
+
var safeValue = encodeURIComponent(value).slice(0, MAX_ATTRIBUTE_VALUE_LENGTH);
|
|
160
|
+
return "[".concat(attribute, "=\"").concat(safeValue, "\"]");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
}]);
|
|
166
|
+
}();
|
|
@@ -20,6 +20,38 @@ export type SnapshotReason = 'timer' | 'hidden' | 'pagehide' | 'unmount' | 'navi
|
|
|
20
20
|
* editable editor, `reading` is a live page being viewed with the editor still mounted.
|
|
21
21
|
*/
|
|
22
22
|
export type SessionMode = 'editing' | 'reading';
|
|
23
|
+
/**
|
|
24
|
+
* The editor groups, which are also the fields their histograms are reported in.
|
|
25
|
+
*
|
|
26
|
+
* `editorOther` is the remainder — an interaction the browser counts that is neither typing nor
|
|
27
|
+
* pointing — so the three together cover whatever the browser calls an interaction.
|
|
28
|
+
*/
|
|
29
|
+
export type EditorInteractionGroupName = 'editorOther' | 'editorPointer' | 'editorTyping';
|
|
30
|
+
/**
|
|
31
|
+
* The group a record is attributed to. Unlike the `page` histogram, which counts the editor's
|
|
32
|
+
* interactions as well, `outsideEditor` is only the interactions that are not the editor's.
|
|
33
|
+
*/
|
|
34
|
+
export type SlowInteractionGroup = EditorInteractionGroupName | 'outsideEditor';
|
|
35
|
+
/**
|
|
36
|
+
* One of the slowest interactions of the session: which target was slow, and where the time went,
|
|
37
|
+
* neither of which the histograms can answer.
|
|
38
|
+
*
|
|
39
|
+
* The three phases divide `durationMs` and add up to it within rounding.
|
|
40
|
+
*/
|
|
41
|
+
export type SlowInteraction = {
|
|
42
|
+
durationMs: number;
|
|
43
|
+
group: SlowInteractionGroup;
|
|
44
|
+
/** Time between the event arriving and its handlers starting to run. */
|
|
45
|
+
inputDelayMs?: number;
|
|
46
|
+
/** Type of the event the latency was measured on, which for a pointer press is usually `click`. */
|
|
47
|
+
name: string;
|
|
48
|
+
/** Time between the handlers finishing and the next frame being presented. */
|
|
49
|
+
presentationDelayMs?: number;
|
|
50
|
+
/** Time spent running the event's handlers. */
|
|
51
|
+
processingMs?: number;
|
|
52
|
+
/** A short DOM path. Absent once the element has been removed from the document. */
|
|
53
|
+
target?: string;
|
|
54
|
+
};
|
|
23
55
|
/**
|
|
24
56
|
* Session-to-date latency distribution for one group of interactions.
|
|
25
57
|
*
|
|
@@ -63,4 +95,6 @@ export type InteractivitySnapshot = {
|
|
|
63
95
|
seq: number;
|
|
64
96
|
/** Fixed for the whole session: a mode change closes it and opens the next. */
|
|
65
97
|
sessionMode?: SessionMode;
|
|
98
|
+
/** Slowest first. Absent when nothing was slow enough to record. */
|
|
99
|
+
slowest?: SlowInteraction[];
|
|
66
100
|
};
|
|
@@ -1,10 +1,4 @@
|
|
|
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';
|
|
1
|
+
import type { EditorInteractionGroupName } from '../analytics/interactivity-snapshot';
|
|
8
2
|
export type InteractionEventKind = {
|
|
9
3
|
/**
|
|
10
4
|
* Whether an interaction is counted when this event arrives. One event of each group has it,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import type { EditorInteractionGroupName } from '
|
|
1
|
+
import type { EditorInteractionGroupName } from '../analytics/interactivity-snapshot';
|
|
2
2
|
/**
|
|
3
|
-
* The Event Timing fields this package reads
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* The Event Timing fields this package reads, declared optional so that a `PerformanceEntry` from
|
|
4
|
+
* `getEntries()` is assignable without a cast — `interactionId` is missing from the DOM typings'
|
|
5
|
+
* `PerformanceEventTiming` altogether, and the rest are only on it.
|
|
6
6
|
*/
|
|
7
7
|
export type InteractionEntry = PerformanceEntry & {
|
|
8
8
|
interactionId?: number;
|
|
9
|
+
processingEnd?: number;
|
|
10
|
+
processingStart?: number;
|
|
11
|
+
target?: Node | null;
|
|
9
12
|
};
|
|
10
13
|
/**
|
|
11
14
|
* What an entry did to the interaction it belongs to: either it is the first entry of a new
|
|
@@ -14,11 +17,13 @@ export type InteractionEntry = PerformanceEntry & {
|
|
|
14
17
|
*/
|
|
15
18
|
export type InteractionUpdate = {
|
|
16
19
|
group: EditorInteractionGroupName | undefined;
|
|
20
|
+
interactionId: number;
|
|
17
21
|
latencyMs: number;
|
|
18
22
|
type: 'new';
|
|
19
23
|
} | {
|
|
20
24
|
fromMs: number;
|
|
21
25
|
group: EditorInteractionGroupName | undefined;
|
|
26
|
+
interactionId: number;
|
|
22
27
|
toMs: number;
|
|
23
28
|
type: 'remeasured';
|
|
24
29
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SessionMode } from '../analytics/interactivity-snapshot';
|
|
2
2
|
import { InteractionTracker } from './interaction-tracker';
|
|
3
3
|
import { InteractionGroup } from './interaction-group';
|
|
4
|
+
import { SlowInteractionList } from './slow-interaction-list';
|
|
4
5
|
export type InteractivitySessionStart = {
|
|
5
6
|
hidden: boolean;
|
|
6
7
|
mode: SessionMode | undefined;
|
|
@@ -23,6 +24,7 @@ export declare class InteractivitySession {
|
|
|
23
24
|
readonly editorTyping: InteractionGroup;
|
|
24
25
|
readonly editorPointer: InteractionGroup;
|
|
25
26
|
readonly editorOther: InteractionGroup;
|
|
27
|
+
readonly slowest: SlowInteractionList | undefined;
|
|
26
28
|
/** Increments per snapshot; a query takes the highest one per session. */
|
|
27
29
|
seq: number;
|
|
28
30
|
/** Both fixed for the session: a change to either closes it and opens the next. */
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SlowInteraction, SlowInteractionGroup } from '../analytics/interactivity-snapshot';
|
|
2
|
+
import type { InteractionEntry } from './interaction-tracker';
|
|
3
|
+
/** An interaction as the collector's two observers saw it. */
|
|
4
|
+
type ObservedInteraction = {
|
|
5
|
+
entry: InteractionEntry;
|
|
6
|
+
group: SlowInteractionGroup;
|
|
7
|
+
interactionId: number;
|
|
8
|
+
/** The duration of the entry measuring the interaction at its slowest. */
|
|
9
|
+
latencyMs: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The slowest interactions of one session, which is what the event's `slowest` records are.
|
|
13
|
+
*
|
|
14
|
+
* A record is built from the entry that measured the interaction, as that entry arrives:
|
|
15
|
+
* `entry.target` is `null` once the element has left the document.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SlowInteractionList {
|
|
18
|
+
/** Slowest first. */
|
|
19
|
+
private readonly records;
|
|
20
|
+
track(interaction: ObservedInteraction): void;
|
|
21
|
+
/** @returns the records, slowest first, or nothing when no interaction was slow enough. */
|
|
22
|
+
snapshot(): SlowInteraction[] | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* The phases are reported together or not at all: a browser that reports one reports all three,
|
|
25
|
+
* and zeroes standing in for values we never had would read as an interaction that spent no time
|
|
26
|
+
* anywhere. Each is clamped, because they come from timestamps the browser coarsens
|
|
27
|
+
* independently, so a phase can come out just below zero.
|
|
28
|
+
*/
|
|
29
|
+
private toRecord;
|
|
30
|
+
/**
|
|
31
|
+
* Names the element an interaction happened on — `div[data-vc="x"] > p > span`, outermost first.
|
|
32
|
+
* The path climbs until an element carries an allow-listed attribute, because that is what says
|
|
33
|
+
* which part of the page this was.
|
|
34
|
+
*/
|
|
35
|
+
private describeTarget;
|
|
36
|
+
private identifyingAttribute;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
package/docs/0-intro.tsx
CHANGED
|
@@ -25,7 +25,8 @@ ${createEditorUseOnlyNotice('Editor Plugin Interactivity', [
|
|
|
25
25
|
|
|
26
26
|
It reports the \`editor interactivity\` operational event: session-to-date interaction latency
|
|
27
27
|
histograms for full page editor sessions — for the page as a whole, and for the editor's typing,
|
|
28
|
-
pointer and other interactions —
|
|
28
|
+
pointer and other interactions — along with the slowest interactions of the session, per
|
|
29
|
+
[RFC 095](https://hello.atlassian.net/wiki/spaces/EDITOR/pages/7527607488/Editor+RFC+095+Confluence+editor+responsiveness+bucketed+INP+telemetry).
|
|
29
30
|
See the package README for the event shape, the snapshot cadence and what ends a session.
|
|
30
31
|
|
|
31
32
|
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": "
|
|
3
|
+
"version": "1.1.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,15 @@
|
|
|
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
|
+
"@atlaskit/platform-feature-experiments": "^0.3.0",
|
|
26
27
|
"@babel/runtime": "^7.0.0",
|
|
27
28
|
"bind-event-listener": "^3.0.0"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
|
-
"@atlaskit/editor-common": "^
|
|
31
|
+
"@atlaskit/editor-common": "^120.1.0",
|
|
31
32
|
"react": "^18.2.0 || ^19.2.0"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|