@atlaskit/editor-plugin-interactivity 1.2.1 → 2.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 +22 -0
- package/README.md +8 -6
- package/dist/cjs/collector/interaction-group.js +68 -24
- package/dist/cjs/collector/interactivity-collector.js +5 -3
- package/dist/cjs/collector/interactivity-session.js +1 -0
- package/dist/es2019/collector/interaction-group.js +41 -5
- package/dist/es2019/collector/interactivity-collector.js +5 -3
- package/dist/es2019/collector/interactivity-session.js +1 -0
- package/dist/esm/collector/interaction-group.js +69 -25
- package/dist/esm/collector/interactivity-collector.js +5 -3
- package/dist/esm/collector/interactivity-session.js +1 -0
- package/dist/types/analytics/interactivity-snapshot.d.ts +6 -7
- package/dist/types/collector/interactivity-collector.d.ts +2 -2
- package/dist/types/collector/interactivity-session.d.ts +1 -0
- package/docs/0-intro.tsx +3 -2
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-interactivity
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
|
|
9
|
+
## 1.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`49ec73019afa7`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/49ec73019afa7) -
|
|
14
|
+
Add an `editor` group to the `editor interactivity` event: the three editor groups as one, in the
|
|
15
|
+
same shape, so the session p98 of the editor as a whole can be read from `editor.percentilesMs`.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [`58e0ed4f4c98f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/58e0ed4f4c98f) -
|
|
20
|
+
Rank `percentilesMs` in the `editor interactivity` event over `totalCount` rather than
|
|
21
|
+
`observedCount`, so the interactions below the Event Timing reporting threshold count towards the
|
|
22
|
+
percentile as they do for INP. A percentile that falls among them is reported as the
|
|
23
|
+
threshold, 16.
|
|
24
|
+
|
|
3
25
|
## 1.2.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -10,22 +10,24 @@ be able to use this component but will not be able to submit issues.
|
|
|
10
10
|
The Interactivity plugin reports the `editor interactivity` operational event: session-to-date
|
|
11
11
|
interaction latency distributions for full page editor sessions, per
|
|
12
12
|
[RFC 095](https://hello.atlassian.net/wiki/spaces/EDITOR/pages/7527607488/Editor+RFC+095+Confluence+editor+responsiveness+bucketed+INP+telemetry).
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
Signals that report one value per session cannot answer how many interactions were slow, so this
|
|
14
|
+
plugin keeps bucketed counts instead.
|
|
15
15
|
|
|
16
16
|
## What it reports
|
|
17
17
|
|
|
18
18
|
- `page` — every interaction on the page, as `totalCount`, `observedCount`, `sumMs`, `maxMs`,
|
|
19
19
|
`buckets` and `percentilesMs`. `totalCount` comes from `performance.interactionCount` and includes
|
|
20
20
|
interactions below the 16 ms Event Timing reporting threshold, so `totalCount - observedCount` is
|
|
21
|
-
the sub-threshold count.
|
|
21
|
+
the sub-threshold count. `percentilesMs` ranks over `totalCount`, as INP does, so a percentile that
|
|
22
|
+
falls among the sub-threshold interactions reads as 16.
|
|
22
23
|
- `editorTyping`, `editorPointer` and `editorOther` — the interactions with the editor, in the same
|
|
23
24
|
shape as `page`. Event Timing observes the whole document, so `page` alone cannot say whether a
|
|
24
25
|
regression is in the editor or elsewhere on the page; these can. Every interaction with the editor
|
|
25
26
|
is in exactly one of them, and in `page` as well.
|
|
26
|
-
- `
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
- `editor` — the three editor groups as one, in the same shape: the per-session p98 of the editor as
|
|
28
|
+
a whole, which theirs cannot give.
|
|
29
|
+
- `percentilesMs` — percentiles of the same interactions, keyed by percentile and exact to the 8 ms
|
|
30
|
+
Event Timing reports durations at; a per-session metric reads these rather than `buckets`.
|
|
29
31
|
- Bucket keys are the upper boundary of the bucket in milliseconds and count only interactions above
|
|
30
32
|
the previous boundary. Buckets are not cumulative and empty buckets are omitted, so a missing
|
|
31
33
|
bucket means zero. The boundaries are versioned by `schema`.
|
|
@@ -102,28 +102,73 @@ var InteractionGroup = exports.InteractionGroup = /*#__PURE__*/function () {
|
|
|
102
102
|
value: function snapshot() {
|
|
103
103
|
var _latencies;
|
|
104
104
|
var totalCount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.totalCount;
|
|
105
|
+
var observedCount = this.observedCount();
|
|
106
|
+
// `performance.interactionCount` can lag the entries the observer has delivered.
|
|
107
|
+
var reportedTotalCount = Math.max(totalCount, observedCount);
|
|
108
|
+
if (reportedTotalCount === 0) {
|
|
109
|
+
return {
|
|
110
|
+
totalCount: 0,
|
|
111
|
+
observedCount: 0,
|
|
112
|
+
sumMs: 0,
|
|
113
|
+
maxMs: 0,
|
|
114
|
+
buckets: {},
|
|
115
|
+
percentilesMs: {}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
105
119
|
// Ascending, so the reported buckets come out in order and the last latency is the
|
|
106
120
|
// maximum. Sorted once for everything below.
|
|
107
121
|
var latencies = Array.from(this.countByLatency.keys()).sort(function (a, b) {
|
|
108
122
|
return a - b;
|
|
109
123
|
});
|
|
110
|
-
|
|
124
|
+
|
|
125
|
+
// A percentile is the interaction at position `ceil(quantile * total)` of all interactions
|
|
126
|
+
// sorted by latency. Event Timing never reports the ones under 16 ms, but they are the fastest,
|
|
127
|
+
// so they fill the front of the line, and the rank is the position among the measured ones:
|
|
128
|
+
//
|
|
129
|
+
// 250 interactions, 150 of them unmeasured
|
|
130
|
+
//
|
|
131
|
+
// position 1 ........... 150 | 151 ........... 245 .... 250
|
|
132
|
+
// latency unmeasured, < 16 ms | 16 ms ............. measured
|
|
133
|
+
// ^ p98
|
|
134
|
+
//
|
|
135
|
+
// p98 is position ceil(0.98 * 250) = 245 of all, rank 245 - 150 = 95 among the measured.
|
|
136
|
+
var unmeasuredCount = reportedTotalCount - observedCount;
|
|
111
137
|
var percentileRanks = REPORTED_QUANTILES.map(function (quantile) {
|
|
112
138
|
return {
|
|
113
139
|
key: String(Math.round(quantile * 100)),
|
|
114
|
-
rank: Math.
|
|
140
|
+
rank: Math.ceil(quantile * reportedTotalCount) - unmeasuredCount
|
|
115
141
|
};
|
|
116
142
|
});
|
|
117
143
|
var buckets = {};
|
|
118
144
|
var percentilesMs = {};
|
|
119
145
|
var sumMs = 0;
|
|
120
146
|
var counted = 0;
|
|
121
|
-
|
|
147
|
+
|
|
148
|
+
// A rank of 0 or below is a latency Event Timing never reports, so the percentile gets the
|
|
149
|
+
// lowest one it does.
|
|
150
|
+
var _iterator = _createForOfIteratorHelper(percentileRanks),
|
|
122
151
|
_step;
|
|
123
152
|
try {
|
|
124
153
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
154
|
+
var _step$value = _step.value,
|
|
155
|
+
key = _step$value.key,
|
|
156
|
+
rank = _step$value.rank;
|
|
157
|
+
if (rank <= 0) {
|
|
158
|
+
percentilesMs[key] = _bucketBoundaries.REPORTING_THRESHOLD_MS;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
_iterator.e(err);
|
|
163
|
+
} finally {
|
|
164
|
+
_iterator.f();
|
|
165
|
+
}
|
|
166
|
+
var _iterator2 = _createForOfIteratorHelper(latencies),
|
|
167
|
+
_step2;
|
|
168
|
+
try {
|
|
169
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
125
170
|
var _this$countByLatency$, _buckets$bucket;
|
|
126
|
-
var latencyMs =
|
|
171
|
+
var latencyMs = _step2.value;
|
|
127
172
|
var count = (_this$countByLatency$ = this.countByLatency.get(latencyMs)) !== null && _this$countByLatency$ !== void 0 ? _this$countByLatency$ : 0;
|
|
128
173
|
sumMs += latencyMs * count;
|
|
129
174
|
var bucket = String((0, _bucketBoundaries.bucketKeyForMs)(latencyMs));
|
|
@@ -132,31 +177,30 @@ var InteractionGroup = exports.InteractionGroup = /*#__PURE__*/function () {
|
|
|
132
177
|
// A percentile is the latency the group's interactions reach counting up from the
|
|
133
178
|
// fastest, so it is answered as soon as this many of them have been passed.
|
|
134
179
|
counted += count;
|
|
135
|
-
var
|
|
136
|
-
|
|
180
|
+
var _iterator3 = _createForOfIteratorHelper(percentileRanks),
|
|
181
|
+
_step3;
|
|
137
182
|
try {
|
|
138
|
-
for (
|
|
139
|
-
var
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (percentilesMs[
|
|
143
|
-
percentilesMs[
|
|
183
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
184
|
+
var _step3$value = _step3.value,
|
|
185
|
+
_key = _step3$value.key,
|
|
186
|
+
_rank = _step3$value.rank;
|
|
187
|
+
if (percentilesMs[_key] === undefined && counted >= _rank) {
|
|
188
|
+
percentilesMs[_key] = latencyMs;
|
|
144
189
|
}
|
|
145
190
|
}
|
|
146
191
|
} catch (err) {
|
|
147
|
-
|
|
192
|
+
_iterator3.e(err);
|
|
148
193
|
} finally {
|
|
149
|
-
|
|
194
|
+
_iterator3.f();
|
|
150
195
|
}
|
|
151
196
|
}
|
|
152
197
|
} catch (err) {
|
|
153
|
-
|
|
198
|
+
_iterator2.e(err);
|
|
154
199
|
} finally {
|
|
155
|
-
|
|
200
|
+
_iterator2.f();
|
|
156
201
|
}
|
|
157
202
|
return {
|
|
158
|
-
|
|
159
|
-
totalCount: Math.max(totalCount, observedCount),
|
|
203
|
+
totalCount: reportedTotalCount,
|
|
160
204
|
observedCount: observedCount,
|
|
161
205
|
sumMs: sumMs,
|
|
162
206
|
maxMs: (_latencies = latencies[latencies.length - 1]) !== null && _latencies !== void 0 ? _latencies : 0,
|
|
@@ -168,17 +212,17 @@ var InteractionGroup = exports.InteractionGroup = /*#__PURE__*/function () {
|
|
|
168
212
|
key: "observedCount",
|
|
169
213
|
value: function observedCount() {
|
|
170
214
|
var total = 0;
|
|
171
|
-
var
|
|
172
|
-
|
|
215
|
+
var _iterator4 = _createForOfIteratorHelper(this.countByLatency.values()),
|
|
216
|
+
_step4;
|
|
173
217
|
try {
|
|
174
|
-
for (
|
|
175
|
-
var count =
|
|
218
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
219
|
+
var count = _step4.value;
|
|
176
220
|
total += count;
|
|
177
221
|
}
|
|
178
222
|
} catch (err) {
|
|
179
|
-
|
|
223
|
+
_iterator4.e(err);
|
|
180
224
|
} finally {
|
|
181
|
-
|
|
225
|
+
_iterator4.f();
|
|
182
226
|
}
|
|
183
227
|
return total;
|
|
184
228
|
}
|
|
@@ -25,8 +25,8 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
25
25
|
/**
|
|
26
26
|
* Collects interaction latencies for one editor mount and emits session-to-date snapshots.
|
|
27
27
|
*
|
|
28
|
-
* Every interaction is counted in `page`, and the ones with the editor again in
|
|
29
|
-
* groups, which is what tells a slow editor apart from a slow page around it.
|
|
28
|
+
* Every interaction is counted in `page`, and the ones with the editor again in `editor` and in one
|
|
29
|
+
* of the editor groups, which is what tells a slow editor apart from a slow page around it.
|
|
30
30
|
*
|
|
31
31
|
* `start` and `stop` bound the collecting, which happens once. Within it there can be several
|
|
32
32
|
* sessions, because a session covers one document in one mode: a Confluence live page
|
|
@@ -265,7 +265,7 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
265
265
|
if (!group) {
|
|
266
266
|
return;
|
|
267
267
|
}
|
|
268
|
-
|
|
268
|
+
this.session.editor.countTotal();
|
|
269
269
|
// The session names its editor groups after the fields they are reported in.
|
|
270
270
|
this.session[group].countTotal();
|
|
271
271
|
this.session.revision += 1;
|
|
@@ -299,6 +299,7 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
299
299
|
var update = _step2.value;
|
|
300
300
|
var groupsChanged = this.session.page.trackInteractionUpdate(update);
|
|
301
301
|
if (update.group) {
|
|
302
|
+
groupsChanged = this.session.editor.trackInteractionUpdate(update) || groupsChanged;
|
|
302
303
|
groupsChanged = this.session[update.group].trackInteractionUpdate(update) || groupsChanged;
|
|
303
304
|
}
|
|
304
305
|
var slowestChanged = (_this$session$slowest2 = this.session.slowest) === null || _this$session$slowest2 === void 0 ? void 0 : _this$session$slowest2.trackInteractionUpdate(entry, update);
|
|
@@ -365,6 +366,7 @@ var InteractivityCollector = exports.InteractivityCollector = /*#__PURE__*/funct
|
|
|
365
366
|
editorDomSize: this.getEditorDomSize(),
|
|
366
367
|
// Only `page` is told its total; each editor group has counted its own.
|
|
367
368
|
page: session.page.snapshot(pageTotalCount),
|
|
369
|
+
editor: session.editor.snapshot(),
|
|
368
370
|
editorTyping: session.editorTyping.snapshot(),
|
|
369
371
|
editorPointer: session.editorPointer.snapshot(),
|
|
370
372
|
editorOther: session.editorOther.snapshot()
|
|
@@ -36,6 +36,7 @@ var InteractivitySession = exports.InteractivitySession = /*#__PURE__*/(0, _crea
|
|
|
36
36
|
/** Page interaction count when the session opened, subtracted to get its own total. */
|
|
37
37
|
(0, _defineProperty2.default)(this, "interactionCountAtStart", _interactionObserver.InteractionObserver.readPageInteractionCount());
|
|
38
38
|
(0, _defineProperty2.default)(this, "page", new _interactionGroup.InteractionGroup());
|
|
39
|
+
(0, _defineProperty2.default)(this, "editor", new _interactionGroup.InteractionGroup());
|
|
39
40
|
(0, _defineProperty2.default)(this, "editorTyping", new _interactionGroup.InteractionGroup());
|
|
40
41
|
(0, _defineProperty2.default)(this, "editorPointer", new _interactionGroup.InteractionGroup());
|
|
41
42
|
(0, _defineProperty2.default)(this, "editorOther", new _interactionGroup.InteractionGroup());
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
-
import { bucketKeyForMs } from './bucket-boundaries';
|
|
2
|
+
import { bucketKeyForMs, REPORTING_THRESHOLD_MS } from './bucket-boundaries';
|
|
3
3
|
/**
|
|
4
4
|
* Latencies are counted per 8 ms, the resolution Event Timing reports durations at. Every
|
|
5
5
|
* latency is rounded up to this step on the way in, which caps the number of distinct values
|
|
@@ -77,18 +77,55 @@ export class InteractionGroup {
|
|
|
77
77
|
*/
|
|
78
78
|
snapshot(totalCount = this.totalCount) {
|
|
79
79
|
var _latencies;
|
|
80
|
+
const observedCount = this.observedCount();
|
|
81
|
+
// `performance.interactionCount` can lag the entries the observer has delivered.
|
|
82
|
+
const reportedTotalCount = Math.max(totalCount, observedCount);
|
|
83
|
+
if (reportedTotalCount === 0) {
|
|
84
|
+
return {
|
|
85
|
+
totalCount: 0,
|
|
86
|
+
observedCount: 0,
|
|
87
|
+
sumMs: 0,
|
|
88
|
+
maxMs: 0,
|
|
89
|
+
buckets: {},
|
|
90
|
+
percentilesMs: {}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
80
94
|
// Ascending, so the reported buckets come out in order and the last latency is the
|
|
81
95
|
// maximum. Sorted once for everything below.
|
|
82
96
|
const latencies = Array.from(this.countByLatency.keys()).sort((a, b) => a - b);
|
|
83
|
-
|
|
97
|
+
|
|
98
|
+
// A percentile is the interaction at position `ceil(quantile * total)` of all interactions
|
|
99
|
+
// sorted by latency. Event Timing never reports the ones under 16 ms, but they are the fastest,
|
|
100
|
+
// so they fill the front of the line, and the rank is the position among the measured ones:
|
|
101
|
+
//
|
|
102
|
+
// 250 interactions, 150 of them unmeasured
|
|
103
|
+
//
|
|
104
|
+
// position 1 ........... 150 | 151 ........... 245 .... 250
|
|
105
|
+
// latency unmeasured, < 16 ms | 16 ms ............. measured
|
|
106
|
+
// ^ p98
|
|
107
|
+
//
|
|
108
|
+
// p98 is position ceil(0.98 * 250) = 245 of all, rank 245 - 150 = 95 among the measured.
|
|
109
|
+
const unmeasuredCount = reportedTotalCount - observedCount;
|
|
84
110
|
const percentileRanks = REPORTED_QUANTILES.map(quantile => ({
|
|
85
111
|
key: String(Math.round(quantile * 100)),
|
|
86
|
-
rank: Math.
|
|
112
|
+
rank: Math.ceil(quantile * reportedTotalCount) - unmeasuredCount
|
|
87
113
|
}));
|
|
88
114
|
const buckets = {};
|
|
89
115
|
const percentilesMs = {};
|
|
90
116
|
let sumMs = 0;
|
|
91
117
|
let counted = 0;
|
|
118
|
+
|
|
119
|
+
// A rank of 0 or below is a latency Event Timing never reports, so the percentile gets the
|
|
120
|
+
// lowest one it does.
|
|
121
|
+
for (const {
|
|
122
|
+
key,
|
|
123
|
+
rank
|
|
124
|
+
} of percentileRanks) {
|
|
125
|
+
if (rank <= 0) {
|
|
126
|
+
percentilesMs[key] = REPORTING_THRESHOLD_MS;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
92
129
|
for (const latencyMs of latencies) {
|
|
93
130
|
var _this$countByLatency$, _buckets$bucket;
|
|
94
131
|
const count = (_this$countByLatency$ = this.countByLatency.get(latencyMs)) !== null && _this$countByLatency$ !== void 0 ? _this$countByLatency$ : 0;
|
|
@@ -109,8 +146,7 @@ export class InteractionGroup {
|
|
|
109
146
|
}
|
|
110
147
|
}
|
|
111
148
|
return {
|
|
112
|
-
|
|
113
|
-
totalCount: Math.max(totalCount, observedCount),
|
|
149
|
+
totalCount: reportedTotalCount,
|
|
114
150
|
observedCount,
|
|
115
151
|
sumMs,
|
|
116
152
|
maxMs: (_latencies = latencies[latencies.length - 1]) !== null && _latencies !== void 0 ? _latencies : 0,
|
|
@@ -11,8 +11,8 @@ import { SnapshotScheduler } from './snapshot-scheduler';
|
|
|
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
|
|
15
|
-
* groups, which is what tells a slow editor apart from a slow page around it.
|
|
14
|
+
* Every interaction is counted in `page`, and the ones with the editor again in `editor` and in one
|
|
15
|
+
* of the editor groups, which is what tells a slow editor apart from a slow page around it.
|
|
16
16
|
*
|
|
17
17
|
* `start` and `stop` bound the collecting, which happens once. Within it there can be several
|
|
18
18
|
* sessions, because a session covers one document in one mode: a Confluence live page
|
|
@@ -207,7 +207,7 @@ export class InteractivityCollector {
|
|
|
207
207
|
if (!group) {
|
|
208
208
|
return;
|
|
209
209
|
}
|
|
210
|
-
|
|
210
|
+
this.session.editor.countTotal();
|
|
211
211
|
// The session names its editor groups after the fields they are reported in.
|
|
212
212
|
this.session[group].countTotal();
|
|
213
213
|
this.session.revision += 1;
|
|
@@ -229,6 +229,7 @@ export class InteractivityCollector {
|
|
|
229
229
|
var _this$session$slowest2;
|
|
230
230
|
let groupsChanged = this.session.page.trackInteractionUpdate(update);
|
|
231
231
|
if (update.group) {
|
|
232
|
+
groupsChanged = this.session.editor.trackInteractionUpdate(update) || groupsChanged;
|
|
232
233
|
groupsChanged = this.session[update.group].trackInteractionUpdate(update) || groupsChanged;
|
|
233
234
|
}
|
|
234
235
|
const slowestChanged = (_this$session$slowest2 = this.session.slowest) === null || _this$session$slowest2 === void 0 ? void 0 : _this$session$slowest2.trackInteractionUpdate(entry, update);
|
|
@@ -283,6 +284,7 @@ export class InteractivityCollector {
|
|
|
283
284
|
editorDomSize: this.getEditorDomSize(),
|
|
284
285
|
// Only `page` is told its total; each editor group has counted its own.
|
|
285
286
|
page: session.page.snapshot(pageTotalCount),
|
|
287
|
+
editor: session.editor.snapshot(),
|
|
286
288
|
editorTyping: session.editorTyping.snapshot(),
|
|
287
289
|
editorPointer: session.editorPointer.snapshot(),
|
|
288
290
|
editorOther: session.editorOther.snapshot(),
|
|
@@ -25,6 +25,7 @@ export class InteractivitySession {
|
|
|
25
25
|
/** Page interaction count when the session opened, subtracted to get its own total. */
|
|
26
26
|
_defineProperty(this, "interactionCountAtStart", InteractionObserver.readPageInteractionCount());
|
|
27
27
|
_defineProperty(this, "page", new InteractionGroup());
|
|
28
|
+
_defineProperty(this, "editor", new InteractionGroup());
|
|
28
29
|
_defineProperty(this, "editorTyping", new InteractionGroup());
|
|
29
30
|
_defineProperty(this, "editorPointer", new InteractionGroup());
|
|
30
31
|
_defineProperty(this, "editorOther", new InteractionGroup());
|
|
@@ -4,7 +4,7 @@ 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 { bucketKeyForMs } from './bucket-boundaries';
|
|
7
|
+
import { bucketKeyForMs, REPORTING_THRESHOLD_MS } from './bucket-boundaries';
|
|
8
8
|
/**
|
|
9
9
|
* Latencies are counted per 8 ms, the resolution Event Timing reports durations at. Every
|
|
10
10
|
* latency is rounded up to this step on the way in, which caps the number of distinct values
|
|
@@ -95,28 +95,73 @@ export var InteractionGroup = /*#__PURE__*/function () {
|
|
|
95
95
|
value: function snapshot() {
|
|
96
96
|
var _latencies;
|
|
97
97
|
var totalCount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.totalCount;
|
|
98
|
+
var observedCount = this.observedCount();
|
|
99
|
+
// `performance.interactionCount` can lag the entries the observer has delivered.
|
|
100
|
+
var reportedTotalCount = Math.max(totalCount, observedCount);
|
|
101
|
+
if (reportedTotalCount === 0) {
|
|
102
|
+
return {
|
|
103
|
+
totalCount: 0,
|
|
104
|
+
observedCount: 0,
|
|
105
|
+
sumMs: 0,
|
|
106
|
+
maxMs: 0,
|
|
107
|
+
buckets: {},
|
|
108
|
+
percentilesMs: {}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
98
112
|
// Ascending, so the reported buckets come out in order and the last latency is the
|
|
99
113
|
// maximum. Sorted once for everything below.
|
|
100
114
|
var latencies = Array.from(this.countByLatency.keys()).sort(function (a, b) {
|
|
101
115
|
return a - b;
|
|
102
116
|
});
|
|
103
|
-
|
|
117
|
+
|
|
118
|
+
// A percentile is the interaction at position `ceil(quantile * total)` of all interactions
|
|
119
|
+
// sorted by latency. Event Timing never reports the ones under 16 ms, but they are the fastest,
|
|
120
|
+
// so they fill the front of the line, and the rank is the position among the measured ones:
|
|
121
|
+
//
|
|
122
|
+
// 250 interactions, 150 of them unmeasured
|
|
123
|
+
//
|
|
124
|
+
// position 1 ........... 150 | 151 ........... 245 .... 250
|
|
125
|
+
// latency unmeasured, < 16 ms | 16 ms ............. measured
|
|
126
|
+
// ^ p98
|
|
127
|
+
//
|
|
128
|
+
// p98 is position ceil(0.98 * 250) = 245 of all, rank 245 - 150 = 95 among the measured.
|
|
129
|
+
var unmeasuredCount = reportedTotalCount - observedCount;
|
|
104
130
|
var percentileRanks = REPORTED_QUANTILES.map(function (quantile) {
|
|
105
131
|
return {
|
|
106
132
|
key: String(Math.round(quantile * 100)),
|
|
107
|
-
rank: Math.
|
|
133
|
+
rank: Math.ceil(quantile * reportedTotalCount) - unmeasuredCount
|
|
108
134
|
};
|
|
109
135
|
});
|
|
110
136
|
var buckets = {};
|
|
111
137
|
var percentilesMs = {};
|
|
112
138
|
var sumMs = 0;
|
|
113
139
|
var counted = 0;
|
|
114
|
-
|
|
140
|
+
|
|
141
|
+
// A rank of 0 or below is a latency Event Timing never reports, so the percentile gets the
|
|
142
|
+
// lowest one it does.
|
|
143
|
+
var _iterator = _createForOfIteratorHelper(percentileRanks),
|
|
115
144
|
_step;
|
|
116
145
|
try {
|
|
117
146
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
147
|
+
var _step$value = _step.value,
|
|
148
|
+
key = _step$value.key,
|
|
149
|
+
rank = _step$value.rank;
|
|
150
|
+
if (rank <= 0) {
|
|
151
|
+
percentilesMs[key] = REPORTING_THRESHOLD_MS;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
_iterator.e(err);
|
|
156
|
+
} finally {
|
|
157
|
+
_iterator.f();
|
|
158
|
+
}
|
|
159
|
+
var _iterator2 = _createForOfIteratorHelper(latencies),
|
|
160
|
+
_step2;
|
|
161
|
+
try {
|
|
162
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
118
163
|
var _this$countByLatency$, _buckets$bucket;
|
|
119
|
-
var latencyMs =
|
|
164
|
+
var latencyMs = _step2.value;
|
|
120
165
|
var count = (_this$countByLatency$ = this.countByLatency.get(latencyMs)) !== null && _this$countByLatency$ !== void 0 ? _this$countByLatency$ : 0;
|
|
121
166
|
sumMs += latencyMs * count;
|
|
122
167
|
var bucket = String(bucketKeyForMs(latencyMs));
|
|
@@ -125,31 +170,30 @@ export var InteractionGroup = /*#__PURE__*/function () {
|
|
|
125
170
|
// A percentile is the latency the group's interactions reach counting up from the
|
|
126
171
|
// fastest, so it is answered as soon as this many of them have been passed.
|
|
127
172
|
counted += count;
|
|
128
|
-
var
|
|
129
|
-
|
|
173
|
+
var _iterator3 = _createForOfIteratorHelper(percentileRanks),
|
|
174
|
+
_step3;
|
|
130
175
|
try {
|
|
131
|
-
for (
|
|
132
|
-
var
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (percentilesMs[
|
|
136
|
-
percentilesMs[
|
|
176
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
177
|
+
var _step3$value = _step3.value,
|
|
178
|
+
_key = _step3$value.key,
|
|
179
|
+
_rank = _step3$value.rank;
|
|
180
|
+
if (percentilesMs[_key] === undefined && counted >= _rank) {
|
|
181
|
+
percentilesMs[_key] = latencyMs;
|
|
137
182
|
}
|
|
138
183
|
}
|
|
139
184
|
} catch (err) {
|
|
140
|
-
|
|
185
|
+
_iterator3.e(err);
|
|
141
186
|
} finally {
|
|
142
|
-
|
|
187
|
+
_iterator3.f();
|
|
143
188
|
}
|
|
144
189
|
}
|
|
145
190
|
} catch (err) {
|
|
146
|
-
|
|
191
|
+
_iterator2.e(err);
|
|
147
192
|
} finally {
|
|
148
|
-
|
|
193
|
+
_iterator2.f();
|
|
149
194
|
}
|
|
150
195
|
return {
|
|
151
|
-
|
|
152
|
-
totalCount: Math.max(totalCount, observedCount),
|
|
196
|
+
totalCount: reportedTotalCount,
|
|
153
197
|
observedCount: observedCount,
|
|
154
198
|
sumMs: sumMs,
|
|
155
199
|
maxMs: (_latencies = latencies[latencies.length - 1]) !== null && _latencies !== void 0 ? _latencies : 0,
|
|
@@ -161,17 +205,17 @@ export var InteractionGroup = /*#__PURE__*/function () {
|
|
|
161
205
|
key: "observedCount",
|
|
162
206
|
value: function observedCount() {
|
|
163
207
|
var total = 0;
|
|
164
|
-
var
|
|
165
|
-
|
|
208
|
+
var _iterator4 = _createForOfIteratorHelper(this.countByLatency.values()),
|
|
209
|
+
_step4;
|
|
166
210
|
try {
|
|
167
|
-
for (
|
|
168
|
-
var count =
|
|
211
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
212
|
+
var count = _step4.value;
|
|
169
213
|
total += count;
|
|
170
214
|
}
|
|
171
215
|
} catch (err) {
|
|
172
|
-
|
|
216
|
+
_iterator4.e(err);
|
|
173
217
|
} finally {
|
|
174
|
-
|
|
218
|
+
_iterator4.f();
|
|
175
219
|
}
|
|
176
220
|
return total;
|
|
177
221
|
}
|
|
@@ -18,8 +18,8 @@ import { SnapshotScheduler } from './snapshot-scheduler';
|
|
|
18
18
|
/**
|
|
19
19
|
* Collects interaction latencies for one editor mount and emits session-to-date snapshots.
|
|
20
20
|
*
|
|
21
|
-
* Every interaction is counted in `page`, and the ones with the editor again in
|
|
22
|
-
* groups, which is what tells a slow editor apart from a slow page around it.
|
|
21
|
+
* Every interaction is counted in `page`, and the ones with the editor again in `editor` and in one
|
|
22
|
+
* of the editor groups, which is what tells a slow editor apart from a slow page around it.
|
|
23
23
|
*
|
|
24
24
|
* `start` and `stop` bound the collecting, which happens once. Within it there can be several
|
|
25
25
|
* sessions, because a session covers one document in one mode: a Confluence live page
|
|
@@ -258,7 +258,7 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
258
258
|
if (!group) {
|
|
259
259
|
return;
|
|
260
260
|
}
|
|
261
|
-
|
|
261
|
+
this.session.editor.countTotal();
|
|
262
262
|
// The session names its editor groups after the fields they are reported in.
|
|
263
263
|
this.session[group].countTotal();
|
|
264
264
|
this.session.revision += 1;
|
|
@@ -292,6 +292,7 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
292
292
|
var update = _step2.value;
|
|
293
293
|
var groupsChanged = this.session.page.trackInteractionUpdate(update);
|
|
294
294
|
if (update.group) {
|
|
295
|
+
groupsChanged = this.session.editor.trackInteractionUpdate(update) || groupsChanged;
|
|
295
296
|
groupsChanged = this.session[update.group].trackInteractionUpdate(update) || groupsChanged;
|
|
296
297
|
}
|
|
297
298
|
var slowestChanged = (_this$session$slowest2 = this.session.slowest) === null || _this$session$slowest2 === void 0 ? void 0 : _this$session$slowest2.trackInteractionUpdate(entry, update);
|
|
@@ -358,6 +359,7 @@ export var InteractivityCollector = /*#__PURE__*/function () {
|
|
|
358
359
|
editorDomSize: this.getEditorDomSize(),
|
|
359
360
|
// Only `page` is told its total; each editor group has counted its own.
|
|
360
361
|
page: session.page.snapshot(pageTotalCount),
|
|
362
|
+
editor: session.editor.snapshot(),
|
|
361
363
|
editorTyping: session.editorTyping.snapshot(),
|
|
362
364
|
editorPointer: session.editorPointer.snapshot(),
|
|
363
365
|
editorOther: session.editorOther.snapshot()
|
|
@@ -29,6 +29,7 @@ export var InteractivitySession = /*#__PURE__*/_createClass(function Interactivi
|
|
|
29
29
|
/** Page interaction count when the session opened, subtracted to get its own total. */
|
|
30
30
|
_defineProperty(this, "interactionCountAtStart", InteractionObserver.readPageInteractionCount());
|
|
31
31
|
_defineProperty(this, "page", new InteractionGroup());
|
|
32
|
+
_defineProperty(this, "editor", new InteractionGroup());
|
|
32
33
|
_defineProperty(this, "editorTyping", new InteractionGroup());
|
|
33
34
|
_defineProperty(this, "editorPointer", new InteractionGroup());
|
|
34
35
|
_defineProperty(this, "editorOther", new InteractionGroup());
|
|
@@ -87,19 +87,16 @@ export type SlowInteraction = {
|
|
|
87
87
|
* Session-to-date latency distribution for one group of interactions.
|
|
88
88
|
*
|
|
89
89
|
* `totalCount` counts every interaction, including those below the Event Timing reporting
|
|
90
|
-
* threshold, so `totalCount - observedCount` is the sub-threshold count. `
|
|
91
|
-
*
|
|
90
|
+
* threshold, so `totalCount - observedCount` is the sub-threshold count. `percentilesMs` ranks over
|
|
91
|
+
* `totalCount`, so a percentile among the sub-threshold interactions reads as the threshold. `buckets`
|
|
92
|
+
* is keyed by each bucket's upper boundary in milliseconds and is not cumulative; empty buckets are
|
|
92
93
|
* omitted, so a missing bucket means zero.
|
|
93
94
|
*/
|
|
94
95
|
export type InteractionGroupSnapshot = {
|
|
95
96
|
buckets: Record<string, number>;
|
|
96
97
|
maxMs: number;
|
|
97
98
|
observedCount: number;
|
|
98
|
-
/**
|
|
99
|
-
* Temporary. Percentiles of the same interactions, keyed by percentile and exact to the 8 ms
|
|
100
|
-
* Event Timing reports durations at, to confirm that a percentile read off `buckets` lands
|
|
101
|
-
* where the latencies actually are. Goes once that is established.
|
|
102
|
-
*/
|
|
99
|
+
/** Percentiles of the same interactions, keyed by percentile and exact to the 8 ms of Event Timing. */
|
|
103
100
|
percentilesMs: Record<string, number>;
|
|
104
101
|
sumMs: number;
|
|
105
102
|
totalCount: number;
|
|
@@ -110,6 +107,8 @@ export type InteractionGroupSnapshot = {
|
|
|
110
107
|
*/
|
|
111
108
|
export type InteractivitySnapshot = {
|
|
112
109
|
activeMs: number;
|
|
110
|
+
/** The three editor groups as one; a percentile of the union cannot be derived from theirs. */
|
|
111
|
+
editor: InteractionGroupSnapshot;
|
|
113
112
|
editorDomSize?: number;
|
|
114
113
|
/** Interactions inside the editor that are neither typing nor pointing. */
|
|
115
114
|
editorOther: InteractionGroupSnapshot;
|
|
@@ -11,8 +11,8 @@ 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
|
|
15
|
-
* groups, which is what tells a slow editor apart from a slow page around it.
|
|
14
|
+
* Every interaction is counted in `page`, and the ones with the editor again in `editor` and in one
|
|
15
|
+
* of the editor groups, which is what tells a slow editor apart from a slow page around it.
|
|
16
16
|
*
|
|
17
17
|
* `start` and `stop` bound the collecting, which happens once. Within it there can be several
|
|
18
18
|
* sessions, because a session covers one document in one mode: a Confluence live page
|
|
@@ -21,6 +21,7 @@ export declare class InteractivitySession {
|
|
|
21
21
|
readonly interactionCountAtStart: number;
|
|
22
22
|
readonly tracker: InteractionTracker;
|
|
23
23
|
readonly page: InteractionGroup;
|
|
24
|
+
readonly editor: InteractionGroup;
|
|
24
25
|
readonly editorTyping: InteractionGroup;
|
|
25
26
|
readonly editorPointer: InteractionGroup;
|
|
26
27
|
readonly editorOther: InteractionGroup;
|
package/docs/0-intro.tsx
CHANGED
|
@@ -24,8 +24,9 @@ ${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 — for the page as a whole,
|
|
28
|
-
pointer and other interactions — along with the slowest interactions of
|
|
27
|
+
histograms for full page editor sessions — for the page as a whole, for the editor as a whole, and
|
|
28
|
+
for the editor's typing, pointer and other interactions — along with the slowest interactions of
|
|
29
|
+
the session, per
|
|
29
30
|
[RFC 095](https://hello.atlassian.net/wiki/spaces/EDITOR/pages/7527607488/Editor+RFC+095+Confluence+editor+responsiveness+bucketed+INP+telemetry).
|
|
30
31
|
See the package README for the event shape, the snapshot cadence and what ends a session.
|
|
31
32
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-interactivity",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Interactivity plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -20,15 +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": "^17.0.0",
|
|
24
|
+
"@atlaskit/editor-plugin-context-identifier": "^17.0.0",
|
|
25
|
+
"@atlaskit/editor-plugin-editor-viewmode": "^19.0.0",
|
|
26
26
|
"@atlaskit/platform-feature-experiments": "^0.3.0",
|
|
27
27
|
"@babel/runtime": "^7.0.0",
|
|
28
28
|
"bind-event-listener": "^3.0.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@atlaskit/editor-common": "^
|
|
31
|
+
"@atlaskit/editor-common": "^121.0.0",
|
|
32
32
|
"react": "^18.2.0 || ^19.2.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|