UNPKG

@atlaskit/editor-plugin-interactivity

Version:

Interactivity plugin for @atlaskit/editor-core

212 lines (203 loc) 9.6 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.InteractionGroup = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _bucketBoundaries = require("./bucket-boundaries"); 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; } } }; } 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; } } 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; } /** * Latencies are counted per 8 ms, the resolution Event Timing reports durations at. Every * latency is rounded up to this step on the way in, which caps the number of distinct values * a group can hold whatever the latency was derived from. */ var RESOLUTION_MS = 8; /** Which percentiles are reported, as quantiles. */ var REPORTED_QUANTILES = [0.9, 0.98]; /** * The latencies of one set of interactions — every interaction on the page, or only the ones * inside the editor — reported as one object in the event. * * The whole state is a count per distinct latency, so an interaction only costs a counter * whatever its latency was, and everything the event carries — the reported buckets, the * count, the sum, the maximum and the percentiles — is derived from that map when a snapshot * is taken. Nothing is computed while interactions arrive. * * The two counters count different populations: `trackInteractionUpdate` takes the interactions * Event Timing measured, `countTotal` takes all of them, including the ones below the 16 ms * reporting threshold it never delivers. So `totalCount >= observedCount`, and the difference is how * many were too fast to be measured. */ var InteractionGroup = exports.InteractionGroup = /*#__PURE__*/function () { function InteractionGroup() { (0, _classCallCheck2.default)(this, InteractionGroup); (0, _defineProperty2.default)(this, "countByLatency", new Map()); (0, _defineProperty2.default)(this, "totalCount", 0); } return (0, _createClass2.default)(InteractionGroup, [{ key: "trackInteractionUpdate", value: /** * Takes in what the tracker now says about an interaction: a new one is counted, and one measured * again moves the count it already has. * * @returns whether the group changed. */ function trackInteractionUpdate(update) { if (update.type === 'new') { this.add(update.latencyMs); return true; } return this.remeasure(update.previousLatencyMs, update.latencyMs); } }, { key: "add", value: function add(latencyMs) { this.increment(latencyMs); } /** * Counts an interaction towards the group's total, measured or not. `page` has no use for it: * `performance.interactionCount` counts the page's interactions. */ }, { key: "countTotal", value: function countTotal() { this.totalCount += 1; } /** * @returns whether the count moved, which is `false` when both latencies fall in the step the * interaction is already counted in — including when the interaction was measured no slower at * all and only its boundaries moved. */ }, { key: "remeasure", value: function remeasure(previousLatencyMs, latencyMs) { if (this.roundLatencyUp(previousLatencyMs) === this.roundLatencyUp(latencyMs)) { return false; } // Moved rather than counted again: the count belongs to the same interaction. this.decrement(previousLatencyMs); this.increment(latencyMs); return true; } /** * @param totalCount every interaction of the group, including those below the Event Timing * reporting threshold. Defaults to what `countTotal` was told, which is where an editor * group's total comes from; `page` passes `performance.interactionCount` instead. */ }, { key: "snapshot", value: function snapshot() { var _latencies; var totalCount = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.totalCount; // Ascending, so the reported buckets come out in order and the last latency is the // maximum. Sorted once for everything below. var latencies = Array.from(this.countByLatency.keys()).sort(function (a, b) { return a - b; }); var observedCount = this.observedCount(); var percentileRanks = REPORTED_QUANTILES.map(function (quantile) { return { key: String(Math.round(quantile * 100)), rank: Math.max(1, Math.ceil(quantile * observedCount)) }; }); var buckets = {}; var percentilesMs = {}; var sumMs = 0; var counted = 0; var _iterator = _createForOfIteratorHelper(latencies), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var _this$countByLatency$, _buckets$bucket; var latencyMs = _step.value; var count = (_this$countByLatency$ = this.countByLatency.get(latencyMs)) !== null && _this$countByLatency$ !== void 0 ? _this$countByLatency$ : 0; sumMs += latencyMs * count; var bucket = String((0, _bucketBoundaries.bucketKeyForMs)(latencyMs)); buckets[bucket] = ((_buckets$bucket = buckets[bucket]) !== null && _buckets$bucket !== void 0 ? _buckets$bucket : 0) + count; // A percentile is the latency the group's interactions reach counting up from the // fastest, so it is answered as soon as this many of them have been passed. counted += count; var _iterator2 = _createForOfIteratorHelper(percentileRanks), _step2; try { for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { var _step2$value = _step2.value, key = _step2$value.key, rank = _step2$value.rank; if (percentilesMs[key] === undefined && counted >= rank) { percentilesMs[key] = latencyMs; } } } catch (err) { _iterator2.e(err); } finally { _iterator2.f(); } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return { // `performance.interactionCount` can lag the entries the observer has delivered. totalCount: Math.max(totalCount, observedCount), observedCount: observedCount, sumMs: sumMs, maxMs: (_latencies = latencies[latencies.length - 1]) !== null && _latencies !== void 0 ? _latencies : 0, buckets: buckets, percentilesMs: percentilesMs }; } }, { key: "observedCount", value: function observedCount() { var total = 0; var _iterator3 = _createForOfIteratorHelper(this.countByLatency.values()), _step3; try { for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) { var count = _step3.value; total += count; } } catch (err) { _iterator3.e(err); } finally { _iterator3.f(); } return total; } /** Rounding lives here so that every count goes through the same step, in or out. */ }, { key: "roundLatencyUp", value: function roundLatencyUp(latencyMs) { return Math.ceil(latencyMs / RESOLUTION_MS) * RESOLUTION_MS; } }, { key: "increment", value: function increment(latencyMs) { var _this$countByLatency$2; var step = this.roundLatencyUp(latencyMs); this.countByLatency.set(step, ((_this$countByLatency$2 = this.countByLatency.get(step)) !== null && _this$countByLatency$2 !== void 0 ? _this$countByLatency$2 : 0) + 1); } }, { key: "decrement", value: function decrement(latencyMs) { var _this$countByLatency$3; var step = this.roundLatencyUp(latencyMs); var next = ((_this$countByLatency$3 = this.countByLatency.get(step)) !== null && _this$countByLatency$3 !== void 0 ? _this$countByLatency$3 : 0) - 1; if (next > 0) { this.countByLatency.set(step, next); } else { this.countByLatency.delete(step); } } }]); }();