UNPKG

@atlaskit/editor-plugin-collab-edit

Version:

Collab Edit plugin for @atlaskit/editor-core

144 lines (135 loc) 8.28 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.monitorOrganic = exports.getScheduler = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _steps = require("@atlaskit/adf-schema/steps"); var _trackLastOrganicChange = require("./track-last-organic-change"); var _trackSteps = require("./track-steps"); var _excluded = ["steps"]; 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; } 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; } 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; } // This is essentially a queue of cached events. When the background task runs to send these items then this queue is flushed. var organicReportingCache = []; // Every ten seconds we will try to process the step data. var LOW_PRIORITY_DELAY = 10000; // See https://developer.mozilla.org/en-US/docs/Web/API/Scheduler/ var getScheduler = exports.getScheduler = function getScheduler(obj) { if (!obj) { return null; } if ('scheduler' in obj) { return obj.scheduler; } return null; }; /** * Processes the steps metadata from the cache and calls the callback function with the processed data. * * @param {OrganicCacheType} cache - A cache containing steps metadata. * @param {(data: OrganicMetadataAnalytics[]) => void} onDataProcessed - Callback function to be called with the processed data. */ var task = function task(cache, onDataProcessed) { var data = []; var _iterator = _createForOfIteratorHelper(cache), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var item = _step.value; var steps = item.steps, rest = (0, _objectWithoutProperties2.default)(item, _excluded); // We'll use the same grouping and sanitize logic from the track-steps util var stepTypesAmount = (0, _trackSteps.groupSteps)(steps.map(_trackSteps.sanitizeStep)); data.push(_objectSpread(_objectSpread({}, rest), {}, { stepTypesAmount: stepTypesAmount })); } // clear the cache. } catch (err) { _iterator.e(err); } finally { _iterator.f(); } cache.length = 0; if (data.length > 0) { onDataProcessed(data); } }; /** * Tracks the steps sent by the client by storing them in a cache and scheduling a task to process them. Once the steps are processed, the onDataProcessed callabck will be called. * * This is a non-critical code. If the browser doesn't support the Scheduler API https://developer.mozilla.org/en-US/docs/Web/API/Scheduler/ * * @param {TrackProps} props - The properties required for tracking steps. * @param {ExtractInjectionAPI<CollabEditPlugin> | undefined} props.api - The API for the CollabEdit plugin. * @param {EditorState} props.newEditorState - The new editor state. * @param {Readonly<Transaction[]>} props.transactions - The transactions that contain the steps. * @param {(data: OrganicMetadataAnalytics[]) => void} props.onDataProcessed - Callback function to be called with the processed data. */ var monitorOrganic = exports.monitorOrganic = function monitorOrganic(_ref) { var newEditorState = _ref.newEditorState, oldEditorState = _ref.oldEditorState, transactions = _ref.transactions, onDataProcessed = _ref.onDataProcessed; // We can exclude analytic steps since they should never trigger an organic change. var newSteps = transactions.flatMap(function (t) { return t.steps; }).filter(function (step) { return !(step instanceof _steps.AnalyticsStep); }); var scheduler = getScheduler(window); if (!newSteps.length || !scheduler) { return; } // We know that an organic change during startup will trigger a draft sync which will; // fire editor edited event -> confluence/next/packages/editor-features/src/hooks/useDraftSync/useDraftSync.tsx // and call triggerUpdate() notifying the BE that user edited the page -> confluence/next/packages/editor-features/src/hooks/useDraftSync/useEditorDraftSyncAction.tsx // This can cause a problem with statsig metrics if organic changes are being incorrectly reported, ie an automated change // occurs which contributes the user towards editing a page when in fact they didn't edit the page. // var oldPluginState = _trackLastOrganicChange.trackLastOrganicChangePluginKey.getState(oldEditorState); var newPluginState = _trackLastOrganicChange.trackLastOrganicChangePluginKey.getState(newEditorState); if (newSteps.length && (oldPluginState === null || oldPluginState === void 0 ? void 0 : oldPluginState.lastLocalOrganicBodyChangeAt) !== (newPluginState === null || newPluginState === void 0 ? void 0 : newPluginState.lastLocalOrganicBodyChangeAt)) { var now = Date.now(); var isFirstChange = !(oldPluginState !== null && oldPluginState !== void 0 && oldPluginState.lastLocalOrganicBodyChangeAt); // Check if we should compact with the previous entry (within 2 seconds) this means with a possible 10sec delay // due to the postTask we could have a potential 5 grouped organic changes listed. var shouldCompact = organicReportingCache.length > 0 && now - organicReportingCache[organicReportingCache.length - 1].startedAt < 2000; if (shouldCompact) { // Compact with previous entry // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var prev = organicReportingCache.pop(); // We know it exists due to shouldCompact check // tr.docChanged organicReportingCache.push({ transactions: prev.transactions + 1, startedAt: prev.startedAt, endedAt: now, isFirstChange: prev.isFirstChange || isFirstChange, steps: prev.steps.concat(newSteps) }); } else { // Add new entry organicReportingCache.push({ transactions: 1, startedAt: now, endedAt: now, isFirstChange: isFirstChange, steps: newSteps }); } if (organicReportingCache.length === 1) { scheduler.postTask(function () { task(organicReportingCache, onDataProcessed); }, { priority: 'background', delay: LOW_PRIORITY_DELAY }); } } };