@atlaskit/collab-provider 10.20.2 → 10.20.3

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # @atlaskit/collab-provider
2
2
 
3
+ ## 10.20.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#173357](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/173357)
8
+ [`f17a667b25b42`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f17a667b25b42) -
9
+ enable merging of prosemirror steps for single player sessions
10
+ - Updated dependencies
11
+
3
12
  ## 10.20.2
4
13
 
5
14
  ### Patch Changes
@@ -16,10 +16,12 @@ var _prosemirrorCollab = require("@atlaskit/prosemirror-collab");
16
16
  var _state = require("@atlaskit/editor-prosemirror/state");
17
17
  var _editorJsonTransformer = require("@atlaskit/editor-json-transformer");
18
18
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
19
+ var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
19
20
  var _const = require("../helpers/const");
20
21
  var _performance = require("../analytics/performance");
21
22
  var _internalErrors = require("../errors/internal-errors");
22
23
  var _utils = require("../helpers/utils");
24
+ var _participantsService = require("../participants/participants-service");
23
25
  var _provider = require("../provider");
24
26
  var _commitStep = require("../provider/commit-step");
25
27
  var _customErrors = require("../errors/custom-errors");
@@ -807,6 +809,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
807
809
  this.getConnected = getConnected;
808
810
  this.stepQueue = new _stepQueueState.StepQueueState();
809
811
  this.onErrorHandled = onErrorHandled;
812
+ this.commitStepService = new _commitStep.CommitStepService(this.broadcast, this.analyticsHelper, this.providerEmitCallback, this.onErrorHandled);
810
813
  }
811
814
  return (0, _createClass2.default)(DocumentService, [{
812
815
  key: "getVersionFromCollabState",
@@ -1000,8 +1003,9 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
1000
1003
  var version = this.getVersionFromCollabState(newState, 'collab-provider: send');
1001
1004
 
1002
1005
  // Don't send any steps before we're ready.
1003
- if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
1004
- if (!unconfirmedStepsData || !this.getConnected()) {
1006
+ if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true) || (0, _expValEquals.expValEquals)('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
1007
+ var enableStepsMergingForSinglePlayer = (0, _expValEquals.expValEquals)('platform_editor_enable_single_player_step_merging', 'isEnabled', true) && !this.commitStepService.getReadyToCommitStatus() && this.participantsService.getCollabMode() === _participantsService.SINGLE_COLLAB_MODE;
1008
+ if (!unconfirmedStepsData || !this.getConnected() || enableStepsMergingForSinglePlayer) {
1005
1009
  return;
1006
1010
  }
1007
1011
  } else {
@@ -1043,7 +1047,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
1043
1047
  // If we are going to commit unconfirmed steps
1044
1048
  // we need to lock them to ensure they don't get
1045
1049
  // mutated in: `packages/editor/editor-plugin-collab-edit/src/pm-plugins/mergeUnconfirmed.ts`
1046
- if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true)) {
1050
+ if ((0, _experiments.editorExperiment)('platform_editor_offline_editing_web', true) || (0, _expValEquals.expValEquals)('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
1047
1051
  unconfirmedStepsData.origins.forEach(function (origin) {
1048
1052
  if (origin instanceof _state.Transaction) {
1049
1053
  return origin.setMeta('mergeIsLocked', true);
@@ -1061,8 +1065,7 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
1061
1065
  // Avoid reference issues using a
1062
1066
  // method outside of the provider
1063
1067
  // scope
1064
- (0, _commitStep.commitStepQueue)({
1065
- broadcast: this.broadcast,
1068
+ this.commitStepService.commitStepQueue({
1066
1069
  // Ignored via go/ees005
1067
1070
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1068
1071
  userId: this.getUserId(),
@@ -1072,9 +1075,6 @@ var DocumentService = exports.DocumentService = /*#__PURE__*/function () {
1072
1075
  steps: unconfirmedSteps,
1073
1076
  version: version,
1074
1077
  onStepsAdded: this.onStepsAdded,
1075
- onErrorHandled: this.onErrorHandled,
1076
- analyticsHelper: this.analyticsHelper,
1077
- emit: this.providerEmitCallback,
1078
1078
  __livePage: this.options.__livePage,
1079
1079
  hasRecovered: this.hasRecovered,
1080
1080
  collabMode: this.participantsService.getCollabMode(),
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.ParticipantsService = void 0;
7
+ exports.SINGLE_COLLAB_MODE = exports.ParticipantsService = exports.MULTI_COLLAB_MODE = void 0;
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
9
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
10
10
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
@@ -22,6 +22,8 @@ var logger = (0, _utils.createLogger)('PresenceService', 'pink');
22
22
  var SEND_PRESENCE_INTERVAL = 150 * 1000; // 150 seconds
23
23
  var DEFAULT_FETCH_USERS_INTERVAL = 500; // 0.5 second
24
24
  var UNIDENTIFIED = 'unidentified';
25
+ var SINGLE_COLLAB_MODE = exports.SINGLE_COLLAB_MODE = 'single';
26
+ var MULTI_COLLAB_MODE = exports.MULTI_COLLAB_MODE = 'collab';
25
27
 
26
28
  /**
27
29
  * This service is responsible for handling presence and participant events, as well as sending them on to the editor or NCS.
@@ -660,7 +662,7 @@ var ParticipantsService = exports.ParticipantsService = /*#__PURE__*/function ()
660
662
  return _this.participantsState.getAIProviderParticipants();
661
663
  });
662
664
  (0, _defineProperty2.default)(this, "getCollabMode", function () {
663
- return _this.participantsState.size() > 1 ? 'collab' : 'single';
665
+ return _this.participantsState.size() > 1 ? MULTI_COLLAB_MODE : SINGLE_COLLAB_MODE;
664
666
  });
665
667
  this.analyticsHelper = analyticsHelper;
666
668
  this.participantsState = participantsState;
@@ -4,8 +4,10 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.readyToCommit = exports.lastBroadcastRequestAcked = exports.commitStepQueue = exports.RESET_READYTOCOMMIT_INTERVAL_MS = void 0;
7
+ exports.RESET_READYTOCOMMIT_INTERVAL_MS = exports.CommitStepService = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
10
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
9
11
  var _countBy = _interopRequireDefault(require("lodash/countBy"));
10
12
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
11
13
  var _featureGateJsClient = _interopRequireDefault(require("@atlaskit/feature-gate-js-client"));
@@ -16,200 +18,233 @@ var _utils = require("../helpers/utils");
16
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; }
17
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
  var logger = (0, _utils.createLogger)('commit-step', 'black');
19
- var readyToCommit = exports.readyToCommit = true;
20
- var lastBroadcastRequestAcked = exports.lastBroadcastRequestAcked = true;
21
21
  var RESET_READYTOCOMMIT_INTERVAL_MS = exports.RESET_READYTOCOMMIT_INTERVAL_MS = 5000;
22
- var commitStepQueue = exports.commitStepQueue = function commitStepQueue(_ref) {
23
- var broadcast = _ref.broadcast,
24
- steps = _ref.steps,
25
- version = _ref.version,
26
- userId = _ref.userId,
27
- clientId = _ref.clientId,
28
- onStepsAdded = _ref.onStepsAdded,
29
- onErrorHandled = _ref.onErrorHandled,
30
- analyticsHelper = _ref.analyticsHelper,
31
- emit = _ref.emit,
32
- __livePage = _ref.__livePage,
33
- hasRecovered = _ref.hasRecovered,
34
- collabMode = _ref.collabMode,
35
- reason = _ref.reason,
36
- numberOfStepCommitsSent = _ref.numberOfStepCommitsSent,
37
- setNumberOfCommitsSent = _ref.setNumberOfCommitsSent;
38
- // this timer is for waiting to send the next batch in between acks from the BE
39
- var commitWaitTimer;
40
- // if publishing and not waiting for an ACK, then clear the commit timer and proceed, skipping the timer
41
- if (reason === 'publish' && lastBroadcastRequestAcked) {
42
- if ((0, _platformFeatureFlags.fg)('skip_collab_provider_delay_on_publish')) {
43
- clearTimeout(commitWaitTimer);
44
- exports.readyToCommit = readyToCommit = true;
45
- } // no-op if fg is turned off
46
- }
47
- if (!readyToCommit) {
48
- logger('Not ready to commit, skip');
49
- return;
22
+ var CommitStepService = exports.CommitStepService = /*#__PURE__*/function () {
23
+ /**
24
+ * @param broadcast - Callback for broadcasting events to other clients
25
+ * @param analyticsHelper - Helper for analytics events
26
+ * @param emit - Callback for emitting events to listeners on the provider
27
+ * @param onErrorHandled - Callback to handle
28
+ */
29
+ function CommitStepService(broadcast, analyticsHelper,
30
+ // Ignored via go/ees005
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ emit, onErrorHandled) {
33
+ (0, _classCallCheck2.default)(this, CommitStepService);
34
+ this.broadcast = broadcast;
35
+ this.analyticsHelper = analyticsHelper;
36
+ this.emit = emit;
37
+ this.onErrorHandled = onErrorHandled;
38
+ this.readyToCommit = true;
39
+ this.lastBroadcastRequestAcked = true;
50
40
  }
51
- // Block other sending request, before ACK
52
- exports.readyToCommit = readyToCommit = false;
53
- exports.lastBroadcastRequestAcked = lastBroadcastRequestAcked = false;
41
+ return (0, _createClass2.default)(CommitStepService, [{
42
+ key: "commitStepQueue",
43
+ value: function commitStepQueue(_ref) {
44
+ var _this = this;
45
+ var steps = _ref.steps,
46
+ version = _ref.version,
47
+ userId = _ref.userId,
48
+ clientId = _ref.clientId,
49
+ onStepsAdded = _ref.onStepsAdded,
50
+ __livePage = _ref.__livePage,
51
+ hasRecovered = _ref.hasRecovered,
52
+ collabMode = _ref.collabMode,
53
+ reason = _ref.reason,
54
+ numberOfStepCommitsSent = _ref.numberOfStepCommitsSent,
55
+ setNumberOfCommitsSent = _ref.setNumberOfCommitsSent;
56
+ // this timer is for waiting to send the next batch in between acks from the BE
57
+ var commitWaitTimer;
58
+ // if publishing and not waiting for an ACK, then clear the commit timer and proceed, skipping the timer
59
+ if (reason === 'publish' && this.lastBroadcastRequestAcked) {
60
+ if ((0, _platformFeatureFlags.fg)('skip_collab_provider_delay_on_publish')) {
61
+ clearTimeout(commitWaitTimer);
62
+ this.readyToCommit = true;
63
+ } // no-op if fg is turned off
64
+ }
65
+ if (!this.readyToCommit) {
66
+ logger('Not ready to commit, skip');
67
+ return;
68
+ }
69
+ // Block other sending request, before ACK
70
+ this.readyToCommit = false;
71
+ this.lastBroadcastRequestAcked = false;
54
72
 
55
- // this timer is a fallback for if an ACK from BE is lost - stop the queue from getting indefinitely locked
56
- var fallbackTimer = setTimeout(function () {
57
- exports.readyToCommit = readyToCommit = true;
58
- exports.lastBroadcastRequestAcked = lastBroadcastRequestAcked = true;
59
- logger('reset readyToCommit by timer');
60
- }, RESET_READYTOCOMMIT_INTERVAL_MS);
61
- var stepsWithClientAndUserId = steps.map(function (step) {
62
- return _objectSpread(_objectSpread({}, step.toJSON()), {}, {
63
- clientId: clientId,
64
- userId: userId
65
- });
66
- });
73
+ // this timer is a fallback for if an ACK from BE is lost - stop the queue from getting indefinitely locked
74
+ var fallbackTimer = setTimeout(function () {
75
+ _this.readyToCommit = true;
76
+ _this.lastBroadcastRequestAcked = true;
77
+ logger('reset readyToCommit by timer');
78
+ }, RESET_READYTOCOMMIT_INTERVAL_MS);
79
+ var stepsWithClientAndUserId = steps.map(function (step) {
80
+ return _objectSpread(_objectSpread({}, step.toJSON()), {}, {
81
+ clientId: clientId,
82
+ userId: userId
83
+ });
84
+ });
67
85
 
68
- // Mutate steps to ignore expand expand/collapse changes in live pages
69
- // This is expected to be a temporary divergence from standard editor behaviour
70
- // and will be removed in Q4 when editor and live view pages align on a single
71
- // behaviour for expands.
72
- // While not recommended by the Editor content services team, this has been discussed
73
- // as being low risk as the expand change via a setAttrs step;
74
- // - doesn't impact any indexes,
75
- // - is setup for last write wins,
76
- // - and is just a boolean -- so no real risk of data loss.
77
- if (__livePage) {
78
- stepsWithClientAndUserId = stepsWithClientAndUserId.map(function (step) {
79
- if (isExpandChangeStep(step)) {
80
- // The title is also updated via this step, which we do want to send to the server.
81
- // so we strip out the __expanded attribute from the step.
82
- return _objectSpread(_objectSpread({}, step), {}, {
83
- attrs: {
84
- title: step.attrs.title
86
+ // Mutate steps to ignore expand expand/collapse changes in live pages
87
+ // This is expected to be a temporary divergence from standard editor behaviour
88
+ // and will be removed in Q4 when editor and live view pages align on a single
89
+ // behaviour for expands.
90
+ // While not recommended by the Editor content services team, this has been discussed
91
+ // as being low risk as the expand change via a setAttrs step;
92
+ // - doesn't impact any indexes,
93
+ // - is setup for last write wins,
94
+ // - and is just a boolean -- so no real risk of data loss.
95
+ if (__livePage) {
96
+ stepsWithClientAndUserId = stepsWithClientAndUserId.map(function (step) {
97
+ if (_this.isExpandChangeStep(step)) {
98
+ // The title is also updated via this step, which we do want to send to the server.
99
+ // so we strip out the __expanded attribute from the step.
100
+ return _objectSpread(_objectSpread({}, step), {}, {
101
+ attrs: {
102
+ title: step.attrs.title
103
+ }
104
+ });
85
105
  }
106
+ return step;
86
107
  });
87
108
  }
88
- return step;
89
- });
90
- }
91
109
 
92
- // tag unconfirmed steps sent after page has been recovered during client's editing session
93
- if (hasRecovered) {
94
- stepsWithClientAndUserId = stepsWithClientAndUserId.map(function (step) {
95
- step.metadata = _objectSpread(_objectSpread({}, step.metadata), {}, {
96
- unconfirmedStepAfterRecovery: true
110
+ // tag unconfirmed steps sent after page has been recovered during client's editing session
111
+ if (hasRecovered) {
112
+ stepsWithClientAndUserId = stepsWithClientAndUserId.map(function (step) {
113
+ step.metadata = _objectSpread(_objectSpread({}, step.metadata), {}, {
114
+ unconfirmedStepAfterRecovery: true
115
+ });
116
+ return step;
117
+ });
118
+ }
119
+ var start = new Date().getTime();
120
+ var ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG = 'Error while adding steps - Invalid Acknowledgement';
121
+ var ADD_STEPS_BROADCAST_ERROR_MSG = 'Error while adding steps - Broadcast threw exception';
122
+ this.emit('commit-status', {
123
+ status: 'attempt',
124
+ version: version
97
125
  });
98
- return step;
99
- });
100
- }
101
- var start = new Date().getTime();
102
- var ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG = 'Error while adding steps - Invalid Acknowledgement';
103
- var ADD_STEPS_BROADCAST_ERROR_MSG = 'Error while adding steps - Broadcast threw exception';
104
- emit('commit-status', {
105
- status: 'attempt',
106
- version: version
107
- });
108
- try {
109
- var _FeatureGates$getExpe;
110
- var n = (_FeatureGates$getExpe = _featureGateJsClient.default.getExperimentValue('platform_editor_step_validation_on_connect', 'steps', 0)) !== null && _FeatureGates$getExpe !== void 0 ? _FeatureGates$getExpe : 0;
111
- var isExperimentEnabled = n > 0;
112
- // skip validation if FG is on and we have already sent n steps, or if FG is off
113
- var skipValidation = isExperimentEnabled ? numberOfStepCommitsSent >= n : true;
114
- broadcast('steps:commit', {
115
- collabMode: collabMode,
116
- steps: stepsWithClientAndUserId,
117
- version: version,
118
- userId: userId,
119
- skipValidation: skipValidation
120
- }, function (response) {
121
- exports.lastBroadcastRequestAcked = lastBroadcastRequestAcked = true;
122
- var latency = new Date().getTime() - start;
123
- // this most closely replicates the BE ack delay behaviour. 500ms hardcoded + 180ms network delay (tested on hello)
124
- // more context: https://hello.atlassian.net/wiki/spaces/CEPS/pages/5020556010/Spike+Moving+BE+delay+to+the+FE
125
- // to be switched over to backpressure delay sent from the BE in https://hello.jira.atlassian.cloud/browse/CEPS-1030
126
- var delay = latency < 680 ? 680 - latency : 1;
127
- if (response.delay) {
128
- delay = response.delay;
129
- } // if backpressure delay is sent, overwrite it
130
-
131
- clearTimeout(fallbackTimer); // clear the fallback timer, ack was successfully sent/recieved
132
- commitWaitTimer = setTimeout(function () {
133
- // unlock the queue after waiting for delay
134
- exports.readyToCommit = readyToCommit = true;
135
- logger('reset readyToCommit');
136
- }, delay);
137
- if (response.type === _types.AcknowledgementResponseTypes.SUCCESS) {
138
- onStepsAdded({
126
+ try {
127
+ var _FeatureGates$getExpe;
128
+ var n = (_FeatureGates$getExpe = _featureGateJsClient.default.getExperimentValue('platform_editor_step_validation_on_connect', 'steps', 0)) !== null && _FeatureGates$getExpe !== void 0 ? _FeatureGates$getExpe : 0;
129
+ var isExperimentEnabled = n > 0;
130
+ // skip validation if FG is on and we have already sent n steps, or if FG is off
131
+ var skipValidation = isExperimentEnabled ? numberOfStepCommitsSent >= n : true;
132
+ this.broadcast('steps:commit', {
133
+ collabMode: collabMode,
139
134
  steps: stepsWithClientAndUserId,
140
- version: response.version
141
- });
142
- sendSuccessAnalytics(latency, stepsWithClientAndUserId, analyticsHelper);
143
- emit('commit-status', {
144
- status: 'success',
145
- version: response.version
146
- });
147
- } else if (response.type === _types.AcknowledgementResponseTypes.ERROR) {
148
- onErrorHandled(response.error);
149
- sendFailureAnalytics(response, latency, analyticsHelper);
150
- emit('commit-status', {
151
- status: 'failure',
152
- version: version
135
+ version: version,
136
+ userId: userId,
137
+ skipValidation: skipValidation
138
+ }, function (response) {
139
+ _this.lastBroadcastRequestAcked = true;
140
+ var latency = new Date().getTime() - start;
141
+ // this most closely replicates the BE ack delay behaviour. 500ms hardcoded + 180ms network delay (tested on hello)
142
+ // more context: https://hello.atlassian.net/wiki/spaces/CEPS/pages/5020556010/Spike+Moving+BE+delay+to+the+FE
143
+ // to be switched over to backpressure delay sent from the BE in https://hello.jira.atlassian.cloud/browse/CEPS-1030
144
+ var delay = latency < 680 ? 680 - latency : 1;
145
+ if (response.delay) {
146
+ delay = response.delay;
147
+ } // if backpressure delay is sent, overwrite it
148
+
149
+ clearTimeout(fallbackTimer); // clear the fallback timer, ack was successfully sent/recieved
150
+ commitWaitTimer = setTimeout(function () {
151
+ // unlock the queue after waiting for delay
152
+ _this.readyToCommit = true;
153
+ logger('reset readyToCommit');
154
+ }, delay);
155
+ if (response.type === _types.AcknowledgementResponseTypes.SUCCESS) {
156
+ onStepsAdded({
157
+ steps: stepsWithClientAndUserId,
158
+ version: response.version
159
+ });
160
+ _this.sendSuccessAnalytics(latency, stepsWithClientAndUserId);
161
+ _this.emit('commit-status', {
162
+ status: 'success',
163
+ version: response.version
164
+ });
165
+ } else if (response.type === _types.AcknowledgementResponseTypes.ERROR) {
166
+ _this.onErrorHandled(response.error);
167
+ _this.sendFailureAnalytics(response, latency);
168
+ _this.emit('commit-status', {
169
+ status: 'failure',
170
+ version: version
171
+ });
172
+ // eslint-disable-next-line no-console
173
+ console.error(ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
174
+ } else {
175
+ var _this$analyticsHelper;
176
+ (_this$analyticsHelper = _this.analyticsHelper) === null || _this$analyticsHelper === void 0 || _this$analyticsHelper.sendErrorEvent(
177
+ // @ts-expect-error We didn't type the invalid type case
178
+ new Error("Response type: ".concat((response === null || response === void 0 ? void 0 : response.type) || 'No response type')), ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
179
+ _this.emit('commit-status', {
180
+ status: 'failure',
181
+ version: version
182
+ });
183
+ // eslint-disable-next-line no-console
184
+ console.error(ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
185
+ }
153
186
  });
154
- // eslint-disable-next-line no-console
155
- console.error(ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
156
- } else {
157
- analyticsHelper === null || analyticsHelper === void 0 || analyticsHelper.sendErrorEvent(
158
- // @ts-expect-error We didn't type the invalid type case
159
- new Error("Response type: ".concat((response === null || response === void 0 ? void 0 : response.type) || 'No response type')), ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
160
- emit('commit-status', {
187
+ if (isExperimentEnabled && numberOfStepCommitsSent < n) {
188
+ setNumberOfCommitsSent(numberOfStepCommitsSent + 1);
189
+ }
190
+ } catch (error) {
191
+ var _this$analyticsHelper2;
192
+ // if the broadcast failed for any reason, we shouldn't keep the queue locked as the BE has not recieved any message
193
+ this.readyToCommit = true;
194
+ (_this$analyticsHelper2 = this.analyticsHelper) === null || _this$analyticsHelper2 === void 0 || _this$analyticsHelper2.sendErrorEvent(error, ADD_STEPS_BROADCAST_ERROR_MSG);
195
+ this.emit('commit-status', {
161
196
  status: 'failure',
162
197
  version: version
163
198
  });
164
199
  // eslint-disable-next-line no-console
165
- console.error(ADD_STEPS_ACKNOWLEDGEMENT_ERROR_MSG);
200
+ console.error(ADD_STEPS_BROADCAST_ERROR_MSG);
166
201
  }
167
- });
168
- if (isExperimentEnabled && numberOfStepCommitsSent < n) {
169
- setNumberOfCommitsSent(numberOfStepCommitsSent + 1);
170
202
  }
171
- } catch (error) {
172
- // if the broadcast failed for any reason, we shouldn't keep the queue locked as the BE has not recieved any message
173
- exports.readyToCommit = readyToCommit = true;
174
- analyticsHelper === null || analyticsHelper === void 0 || analyticsHelper.sendErrorEvent(error, ADD_STEPS_BROADCAST_ERROR_MSG);
175
- emit('commit-status', {
176
- status: 'failure',
177
- version: version
178
- });
179
- // eslint-disable-next-line no-console
180
- console.error(ADD_STEPS_BROADCAST_ERROR_MSG);
181
- }
182
- };
183
- function isExpandChangeStep(step) {
184
- // Ignored via go/ees005
185
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
- if (step.stepType === 'setAttrs' && '__expanded' in step.attrs) {
187
- return true;
188
- }
189
- return false;
190
- }
191
- function sendSuccessAnalytics(latency, stepsWithClientAndUserId, analyticsHelper) {
192
- // Sample only 10% of add steps events to avoid overwhelming the analytics
193
- if (Math.random() < 0.1) {
194
- analyticsHelper === null || analyticsHelper === void 0 || analyticsHelper.sendActionEvent(_const.EVENT_ACTION.ADD_STEPS, _const.EVENT_STATUS.SUCCESS_10x_SAMPLED, {
195
- type: _const.ADD_STEPS_TYPE.ACCEPTED,
196
- latency: latency,
197
- stepType: (0, _countBy.default)(stepsWithClientAndUserId,
203
+ }, {
204
+ key: "isExpandChangeStep",
205
+ value: function isExpandChangeStep(step) {
198
206
  // Ignored via go/ees005
199
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
200
- function (stepWithClientAndUserId) {
201
- return stepWithClientAndUserId.stepType;
202
- })
203
- });
204
- }
205
- }
206
- function sendFailureAnalytics(response, latency, analyticsHelper) {
207
- analyticsHelper === null || analyticsHelper === void 0 || analyticsHelper.sendActionEvent(_const.EVENT_ACTION.ADD_STEPS, _const.EVENT_STATUS.FAILURE, {
208
- // User tried committing steps but they were rejected because:
209
- // - HEAD_VERSION_UPDATE_FAILED: the collab service's latest stored step tail version didn't correspond to the head version of the first step submitted
210
- // - VERSION_NUMBER_ALREADY_EXISTS: while storing the steps there was a conflict meaning someone else wrote steps into the database more quickly
211
- type: response.error.data.code === _ncsErrors.NCS_ERROR_CODE.HEAD_VERSION_UPDATE_FAILED || response.error.data.code === _ncsErrors.NCS_ERROR_CODE.VERSION_NUMBER_ALREADY_EXISTS ? _const.ADD_STEPS_TYPE.REJECTED : _const.ADD_STEPS_TYPE.ERROR,
212
- latency: latency
213
- });
214
- analyticsHelper === null || analyticsHelper === void 0 || analyticsHelper.sendErrorEvent(response.error, 'Error while adding steps - Acknowledgement Error');
215
- }
207
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
+ if (step.stepType === 'setAttrs' && '__expanded' in step.attrs) {
209
+ return true;
210
+ }
211
+ return false;
212
+ }
213
+ }, {
214
+ key: "sendSuccessAnalytics",
215
+ value: function sendSuccessAnalytics(latency, stepsWithClientAndUserId) {
216
+ // Sample only 10% of add steps events to avoid overwhelming the analytics
217
+ if (Math.random() < 0.1) {
218
+ var _this$analyticsHelper3;
219
+ (_this$analyticsHelper3 = this.analyticsHelper) === null || _this$analyticsHelper3 === void 0 || _this$analyticsHelper3.sendActionEvent(_const.EVENT_ACTION.ADD_STEPS, _const.EVENT_STATUS.SUCCESS_10x_SAMPLED, {
220
+ type: _const.ADD_STEPS_TYPE.ACCEPTED,
221
+ latency: latency,
222
+ stepType: (0, _countBy.default)(stepsWithClientAndUserId,
223
+ // Ignored via go/ees005
224
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
225
+ function (stepWithClientAndUserId) {
226
+ return stepWithClientAndUserId.stepType;
227
+ })
228
+ });
229
+ }
230
+ }
231
+ }, {
232
+ key: "sendFailureAnalytics",
233
+ value: function sendFailureAnalytics(response, latency) {
234
+ var _this$analyticsHelper4, _this$analyticsHelper5;
235
+ (_this$analyticsHelper4 = this.analyticsHelper) === null || _this$analyticsHelper4 === void 0 || _this$analyticsHelper4.sendActionEvent(_const.EVENT_ACTION.ADD_STEPS, _const.EVENT_STATUS.FAILURE, {
236
+ // User tried committing steps but they were rejected because:
237
+ // - HEAD_VERSION_UPDATE_FAILED: the collab service's latest stored step tail version didn't correspond to the head version of the first step submitted
238
+ // - VERSION_NUMBER_ALREADY_EXISTS: while storing the steps there was a conflict meaning someone else wrote steps into the database more quickly
239
+ type: response.error.data.code === _ncsErrors.NCS_ERROR_CODE.HEAD_VERSION_UPDATE_FAILED || response.error.data.code === _ncsErrors.NCS_ERROR_CODE.VERSION_NUMBER_ALREADY_EXISTS ? _const.ADD_STEPS_TYPE.REJECTED : _const.ADD_STEPS_TYPE.ERROR,
240
+ latency: latency
241
+ });
242
+ (_this$analyticsHelper5 = this.analyticsHelper) === null || _this$analyticsHelper5 === void 0 || _this$analyticsHelper5.sendErrorEvent(response.error, 'Error while adding steps - Acknowledgement Error');
243
+ }
244
+ }, {
245
+ key: "getReadyToCommitStatus",
246
+ value: function getReadyToCommitStatus() {
247
+ return this.readyToCommit;
248
+ }
249
+ }]);
250
+ }();
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.version = exports.nextMajorVersion = exports.name = void 0;
7
7
  var name = exports.name = "@atlaskit/collab-provider";
8
- var version = exports.version = "10.20.1";
8
+ var version = exports.version = "10.20.2";
9
9
  var nextMajorVersion = exports.nextMajorVersion = function nextMajorVersion() {
10
10
  return [Number(version.split('.')[0]) + 1, 0, 0].join('.');
11
11
  };
@@ -5,12 +5,14 @@ import { getCollabState, sendableSteps } from '@atlaskit/prosemirror-collab';
5
5
  import { Transaction } from '@atlaskit/editor-prosemirror/state';
6
6
  import { JSONTransformer } from '@atlaskit/editor-json-transformer';
7
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
+ import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
8
9
  import { ACK_MAX_TRY, EVENT_ACTION, EVENT_STATUS, CatchupEventReason } from '../helpers/const';
9
10
  import { MEASURE_NAME, startMeasure, stopMeasure } from '../analytics/performance';
10
11
  import { INTERNAL_ERROR_CODE } from '../errors/internal-errors';
11
12
  import { createLogger, getDocAdfWithObfuscationFromJSON, getObfuscatedSteps, getStepUGCFreeDetails, sleep } from '../helpers/utils';
13
+ import { SINGLE_COLLAB_MODE } from '../participants/participants-service';
12
14
  import { MAX_STEP_REJECTED_ERROR, MAX_STEP_REJECTED_ERROR_AGGRESSIVE } from '../provider';
13
- import { commitStepQueue } from '../provider/commit-step';
15
+ import { CommitStepService } from '../provider/commit-step';
14
16
  import { CantSyncUpError, UpdateDocumentError } from '../errors/custom-errors';
15
17
  import { catchupv2 } from './catchupv2';
16
18
  import { StepQueueState } from './step-queue-state';
@@ -694,6 +696,7 @@ export class DocumentService {
694
696
  this.getConnected = getConnected;
695
697
  this.stepQueue = new StepQueueState();
696
698
  this.onErrorHandled = onErrorHandled;
699
+ this.commitStepService = new CommitStepService(this.broadcast, this.analyticsHelper, this.providerEmitCallback, this.onErrorHandled);
697
700
  }
698
701
  getVersionFromCollabState(state, resource) {
699
702
  const collabState = getCollabState(state);
@@ -867,8 +870,9 @@ export class DocumentService {
867
870
  const version = this.getVersionFromCollabState(newState, 'collab-provider: send');
868
871
 
869
872
  // Don't send any steps before we're ready.
870
- if (editorExperiment('platform_editor_offline_editing_web', true)) {
871
- if (!unconfirmedStepsData || !this.getConnected()) {
873
+ if (editorExperiment('platform_editor_offline_editing_web', true) || expValEquals('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
874
+ const enableStepsMergingForSinglePlayer = expValEquals('platform_editor_enable_single_player_step_merging', 'isEnabled', true) && !this.commitStepService.getReadyToCommitStatus() && this.participantsService.getCollabMode() === SINGLE_COLLAB_MODE;
875
+ if (!unconfirmedStepsData || !this.getConnected() || enableStepsMergingForSinglePlayer) {
872
876
  return;
873
877
  }
874
878
  } else {
@@ -910,7 +914,7 @@ export class DocumentService {
910
914
  // If we are going to commit unconfirmed steps
911
915
  // we need to lock them to ensure they don't get
912
916
  // mutated in: `packages/editor/editor-plugin-collab-edit/src/pm-plugins/mergeUnconfirmed.ts`
913
- if (editorExperiment('platform_editor_offline_editing_web', true)) {
917
+ if (editorExperiment('platform_editor_offline_editing_web', true) || expValEquals('platform_editor_enable_single_player_step_merging', 'isEnabled', true)) {
914
918
  unconfirmedStepsData.origins.forEach(origin => {
915
919
  if (origin instanceof Transaction) {
916
920
  return origin.setMeta('mergeIsLocked', true);
@@ -928,8 +932,7 @@ export class DocumentService {
928
932
  // Avoid reference issues using a
929
933
  // method outside of the provider
930
934
  // scope
931
- commitStepQueue({
932
- broadcast: this.broadcast,
935
+ this.commitStepService.commitStepQueue({
933
936
  // Ignored via go/ees005
934
937
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
935
938
  userId: this.getUserId(),
@@ -939,9 +942,6 @@ export class DocumentService {
939
942
  steps: unconfirmedSteps,
940
943
  version,
941
944
  onStepsAdded: this.onStepsAdded,
942
- onErrorHandled: this.onErrorHandled,
943
- analyticsHelper: this.analyticsHelper,
944
- emit: this.providerEmitCallback,
945
945
  __livePage: this.options.__livePage,
946
946
  hasRecovered: this.hasRecovered,
947
947
  collabMode: this.participantsService.getCollabMode(),
@@ -9,6 +9,8 @@ const logger = createLogger('PresenceService', 'pink');
9
9
  const SEND_PRESENCE_INTERVAL = 150 * 1000; // 150 seconds
10
10
  const DEFAULT_FETCH_USERS_INTERVAL = 500; // 0.5 second
11
11
  const UNIDENTIFIED = 'unidentified';
12
+ export const SINGLE_COLLAB_MODE = 'single';
13
+ export const MULTI_COLLAB_MODE = 'collab';
12
14
 
13
15
  /**
14
16
  * This service is responsible for handling presence and participant events, as well as sending them on to the editor or NCS.
@@ -532,7 +534,7 @@ export class ParticipantsService {
532
534
  return this.participantsState.getAIProviderParticipants();
533
535
  });
534
536
  _defineProperty(this, "getCollabMode", () => {
535
- return this.participantsState.size() > 1 ? 'collab' : 'single';
537
+ return this.participantsState.size() > 1 ? MULTI_COLLAB_MODE : SINGLE_COLLAB_MODE;
536
538
  });
537
539
  this.analyticsHelper = analyticsHelper;
538
540
  this.participantsState = participantsState;