@atlaskit/editor-plugin-collab-edit 1.14.2 → 1.16.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 +20 -0
- package/dist/cjs/plugin.js +26 -4
- package/dist/cjs/pm-plugins/track-and-filter-spamming-steps.js +83 -0
- package/dist/cjs/pm-plugins/track-last-organic-change.js +5 -2
- package/dist/es2019/plugin.js +23 -5
- package/dist/es2019/pm-plugins/track-and-filter-spamming-steps.js +75 -0
- package/dist/es2019/pm-plugins/track-last-organic-change.js +6 -3
- package/dist/esm/plugin.js +27 -5
- package/dist/esm/pm-plugins/track-and-filter-spamming-steps.js +75 -0
- package/dist/esm/pm-plugins/track-last-organic-change.js +6 -3
- package/dist/types/pm-plugins/track-and-filter-spamming-steps.d.ts +14 -0
- package/dist/types/types.d.ts +7 -0
- package/dist/types-ts4.5/pm-plugins/track-and-filter-spamming-steps.d.ts +14 -0
- package/dist/types-ts4.5/types.d.ts +7 -0
- package/package.json +10 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-collab-edit
|
|
2
2
|
|
|
3
|
+
## 1.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#134153](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/134153)
|
|
8
|
+
[`88d5b34381594`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/88d5b34381594) -
|
|
9
|
+
Introduced filtering of spamming transaction
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies
|
|
14
|
+
|
|
15
|
+
## 1.15.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- [#133504](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/133504)
|
|
20
|
+
[`024439741fcdc`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/024439741fcdc) -
|
|
21
|
+
Replaced usage of `originalTransactionHasMeta` to avoid infinite loop issue
|
|
22
|
+
|
|
3
23
|
## 1.14.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/cjs/plugin.js
CHANGED
|
@@ -18,6 +18,7 @@ var _sendTransaction = require("./events/send-transaction");
|
|
|
18
18
|
var _main = require("./pm-plugins/main");
|
|
19
19
|
var _pluginKey = require("./pm-plugins/main/plugin-key");
|
|
20
20
|
var _nativeCollabProviderPlugin = require("./pm-plugins/native-collab-provider-plugin");
|
|
21
|
+
var _trackAndFilterSpammingSteps = require("./pm-plugins/track-and-filter-spamming-steps");
|
|
21
22
|
var _trackLastOrganicChange = require("./pm-plugins/track-last-organic-change");
|
|
22
23
|
var _trackNcsInitialization = require("./pm-plugins/track-ncs-initialization");
|
|
23
24
|
var _trackSteps = require("./track-steps");
|
|
@@ -173,6 +174,27 @@ var collabEditPlugin = exports.collabEditPlugin = function collabEditPlugin(_ref
|
|
|
173
174
|
name: 'collabTrackNCSInitializationPlugin',
|
|
174
175
|
plugin: _trackNcsInitialization.createPlugin
|
|
175
176
|
}]);
|
|
177
|
+
if ((0, _platformFeatureFlags.fg)('platform_editor_filter_transactions_analytics') || (0, _platformFeatureFlags.fg)('platform_editor_filter_spamming_transactions')) {
|
|
178
|
+
plugins.push({
|
|
179
|
+
name: 'trackAndFilterSpammingSteps',
|
|
180
|
+
plugin: function plugin() {
|
|
181
|
+
return (0, _trackAndFilterSpammingSteps.createPlugin)(function (tr) {
|
|
182
|
+
var _api$analytics;
|
|
183
|
+
var sanitizedSteps = tr.steps.map(function (step) {
|
|
184
|
+
return (0, _trackSteps.sanitizeStep)(step);
|
|
185
|
+
});
|
|
186
|
+
api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 || _api$analytics.fireAnalyticsEvent({
|
|
187
|
+
action: _analytics.ACTION.STEPS_FILTERED,
|
|
188
|
+
actionSubject: _analytics.ACTION_SUBJECT.COLLAB,
|
|
189
|
+
attributes: {
|
|
190
|
+
steps: sanitizedSteps
|
|
191
|
+
},
|
|
192
|
+
eventType: _analytics.EVENT_TYPE.OPERATIONAL
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
176
198
|
if ((0, _platformFeatureFlags.fg)('platform_editor_last_organic_change')) {
|
|
177
199
|
plugins.push({
|
|
178
200
|
name: 'collabTrackLastOrganicChangePlugin',
|
|
@@ -182,8 +204,8 @@ var collabEditPlugin = exports.collabEditPlugin = function collabEditPlugin(_ref
|
|
|
182
204
|
return plugins;
|
|
183
205
|
},
|
|
184
206
|
onEditorViewStateUpdated: function onEditorViewStateUpdated(props) {
|
|
185
|
-
var _api$
|
|
186
|
-
var addErrorAnalytics = (0, _analytics2.addSynchronyErrorAnalytics)(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 || (_api$
|
|
207
|
+
var _api$analytics2, _api$editorViewMode, _options$useNativePlu;
|
|
208
|
+
var addErrorAnalytics = (0, _analytics2.addSynchronyErrorAnalytics)(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions);
|
|
187
209
|
var viewMode = api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode;
|
|
188
210
|
executeProviderCode((0, _sendTransaction.sendTransaction)({
|
|
189
211
|
originalTransaction: props.originalTransaction,
|
|
@@ -195,8 +217,8 @@ var collabEditPlugin = exports.collabEditPlugin = function collabEditPlugin(_ref
|
|
|
195
217
|
}), addErrorAnalytics);
|
|
196
218
|
(0, _trackSteps.track)(_objectSpread(_objectSpread({}, props), {}, {
|
|
197
219
|
onTrackDataProcessed: function onTrackDataProcessed(steps) {
|
|
198
|
-
var _api$
|
|
199
|
-
api === null || api === void 0 || (_api$
|
|
220
|
+
var _api$analytics3;
|
|
221
|
+
api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || (_api$analytics3 = _api$analytics3.actions) === null || _api$analytics3 === void 0 || _api$analytics3.fireAnalyticsEvent({
|
|
200
222
|
action: _analytics.ACTION.STEPS_TRACKED,
|
|
201
223
|
actionSubject: _analytics.ACTION_SUBJECT.COLLAB,
|
|
202
224
|
attributes: {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createPlugin = exports.createFilterTransaction = void 0;
|
|
7
|
+
exports.generateTransactionKey = generateTransactionKey;
|
|
8
|
+
exports.trackSpammingStepsPluginKey = void 0;
|
|
9
|
+
var _steps = require("@atlaskit/adf-schema/steps");
|
|
10
|
+
var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
|
|
11
|
+
var _state = require("@atlaskit/editor-prosemirror/state");
|
|
12
|
+
var _transform = require("@atlaskit/editor-prosemirror/transform");
|
|
13
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
14
|
+
var THRESHOLD = 50; // 50 milliseconds
|
|
15
|
+
|
|
16
|
+
var createFilterTransaction = exports.createFilterTransaction = function createFilterTransaction(recentTransactionsTimestamps, trackFilteredTransaction) {
|
|
17
|
+
return function (tr) {
|
|
18
|
+
if (Boolean(tr.getMeta('appendTransaction'))) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
var isRemote = Boolean(tr.getMeta('isRemote'));
|
|
22
|
+
if (isRemote) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (tr.docChanged && tr.doc.eq(tr.before)) {
|
|
26
|
+
var transactionKey = generateTransactionKey(tr);
|
|
27
|
+
if (!transactionKey) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//Clean up tracked transactions when time over threshold
|
|
32
|
+
recentTransactionsTimestamps.forEach(function (value, key) {
|
|
33
|
+
if (tr.time - value.timestamp > THRESHOLD) {
|
|
34
|
+
// Delete tracked transaction when over threshold
|
|
35
|
+
recentTransactionsTimestamps.delete(key);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
var lastTransactionEntry = recentTransactionsTimestamps.get(transactionKey);
|
|
39
|
+
if (!lastTransactionEntry) {
|
|
40
|
+
// If no timestamp exists for the given transaction, allow transaction and add an entry
|
|
41
|
+
recentTransactionsTimestamps.set(transactionKey, {
|
|
42
|
+
timestamp: tr.time,
|
|
43
|
+
steps: tr.steps
|
|
44
|
+
});
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Track analytics for the filtered transaction
|
|
49
|
+
trackFilteredTransaction(tr);
|
|
50
|
+
if ((0, _platformFeatureFlags.fg)('platform_editor_filter_spamming_transactions')) {
|
|
51
|
+
// Filter transaction
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return true; // Allow the transaction
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Helper function to create a u ique transaction key
|
|
60
|
+
function generateTransactionKey(tr) {
|
|
61
|
+
return tr.steps.map(function (step) {
|
|
62
|
+
if (step instanceof _transform.RemoveNodeMarkStep || step instanceof _transform.AddNodeMarkStep || step instanceof _steps.SetAttrsStep || step instanceof _transform.AttrStep || step instanceof _steps.AnalyticsStep) {
|
|
63
|
+
if (step.pos !== undefined) {
|
|
64
|
+
return "".concat(step.pos);
|
|
65
|
+
}
|
|
66
|
+
} else if (step instanceof _transform.AddMarkStep || step instanceof _transform.RemoveMarkStep || step instanceof _transform.ReplaceAroundStep || step instanceof _transform.ReplaceStep) {
|
|
67
|
+
return "from_".concat(step.from, "_to_").concat(step.to);
|
|
68
|
+
} else if (step instanceof _steps.LinkMetaStep) {
|
|
69
|
+
return "from_".concat(step.toJSON().from, "_to_").concat(step.toJSON().to);
|
|
70
|
+
} else if (step instanceof _steps.InsertTypeAheadStep) {
|
|
71
|
+
return "insertTypeAheadStep";
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}).join('_');
|
|
75
|
+
}
|
|
76
|
+
var trackSpammingStepsPluginKey = exports.trackSpammingStepsPluginKey = new _state.PluginKey('trackAndFilterSpammingStepsPluginKey');
|
|
77
|
+
var createPlugin = exports.createPlugin = function createPlugin(trackFilteredTransaction) {
|
|
78
|
+
var recentTransactionsTimestamps = new Map();
|
|
79
|
+
return new _safePlugin.SafePlugin({
|
|
80
|
+
key: trackSpammingStepsPluginKey,
|
|
81
|
+
filterTransaction: createFilterTransaction(recentTransactionsTimestamps, trackFilteredTransaction)
|
|
82
|
+
});
|
|
83
|
+
};
|
|
@@ -20,8 +20,11 @@ var createPlugin = exports.createPlugin = function createPlugin() {
|
|
|
20
20
|
};
|
|
21
21
|
},
|
|
22
22
|
apply: function apply(transaction, prevPluginState) {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
if (Boolean(transaction.getMeta('appendedTransaction'))) {
|
|
24
|
+
return prevPluginState;
|
|
25
|
+
}
|
|
26
|
+
var isRemote = Boolean(transaction.getMeta('isRemote'));
|
|
27
|
+
var isDocumentReplaceFromRemote = isRemote && Boolean(transaction.getMeta('replaceDocument'));
|
|
25
28
|
if (isDocumentReplaceFromRemote) {
|
|
26
29
|
return prevPluginState;
|
|
27
30
|
}
|
package/dist/es2019/plugin.js
CHANGED
|
@@ -7,9 +7,10 @@ import { sendTransaction } from './events/send-transaction';
|
|
|
7
7
|
import { createPlugin } from './pm-plugins/main';
|
|
8
8
|
import { pluginKey as mainPluginKey } from './pm-plugins/main/plugin-key';
|
|
9
9
|
import { nativeCollabProviderPlugin } from './pm-plugins/native-collab-provider-plugin';
|
|
10
|
+
import { createPlugin as trackSpammingStepsPlugin } from './pm-plugins/track-and-filter-spamming-steps';
|
|
10
11
|
import { createPlugin as createLastOrganicChangePlugin, trackLastOrganicChangePluginKey } from './pm-plugins/track-last-organic-change';
|
|
11
12
|
import { createPlugin as createTrackNCSInitializationPlugin, trackNCSInitializationPluginKey } from './pm-plugins/track-ncs-initialization';
|
|
12
|
-
import { track } from './track-steps';
|
|
13
|
+
import { sanitizeStep, track } from './track-steps';
|
|
13
14
|
import { getAvatarColor } from './utils';
|
|
14
15
|
const providerBuilder = collabEditProviderPromise => async (codeToExecute, onError) => {
|
|
15
16
|
try {
|
|
@@ -128,6 +129,23 @@ export const collabEditPlugin = ({
|
|
|
128
129
|
name: 'collabTrackNCSInitializationPlugin',
|
|
129
130
|
plugin: createTrackNCSInitializationPlugin
|
|
130
131
|
}];
|
|
132
|
+
if (fg('platform_editor_filter_transactions_analytics') || fg('platform_editor_filter_spamming_transactions')) {
|
|
133
|
+
plugins.push({
|
|
134
|
+
name: 'trackAndFilterSpammingSteps',
|
|
135
|
+
plugin: () => trackSpammingStepsPlugin(tr => {
|
|
136
|
+
var _api$analytics, _api$analytics$action;
|
|
137
|
+
const sanitizedSteps = tr.steps.map(step => sanitizeStep(step));
|
|
138
|
+
api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : (_api$analytics$action = _api$analytics.actions) === null || _api$analytics$action === void 0 ? void 0 : _api$analytics$action.fireAnalyticsEvent({
|
|
139
|
+
action: ACTION.STEPS_FILTERED,
|
|
140
|
+
actionSubject: ACTION_SUBJECT.COLLAB,
|
|
141
|
+
attributes: {
|
|
142
|
+
steps: sanitizedSteps
|
|
143
|
+
},
|
|
144
|
+
eventType: EVENT_TYPE.OPERATIONAL
|
|
145
|
+
});
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
}
|
|
131
149
|
if (fg('platform_editor_last_organic_change')) {
|
|
132
150
|
plugins.push({
|
|
133
151
|
name: 'collabTrackLastOrganicChangePlugin',
|
|
@@ -137,8 +155,8 @@ export const collabEditPlugin = ({
|
|
|
137
155
|
return plugins;
|
|
138
156
|
},
|
|
139
157
|
onEditorViewStateUpdated(props) {
|
|
140
|
-
var _api$
|
|
141
|
-
const addErrorAnalytics = addSynchronyErrorAnalytics(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 ? void 0 : (_api$
|
|
158
|
+
var _api$analytics2, _api$editorViewMode, _api$editorViewMode$s, _options$useNativePlu;
|
|
159
|
+
const addErrorAnalytics = addSynchronyErrorAnalytics(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions);
|
|
142
160
|
const viewMode = api === null || api === void 0 ? void 0 : (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 ? void 0 : (_api$editorViewMode$s = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode$s === void 0 ? void 0 : _api$editorViewMode$s.mode;
|
|
143
161
|
executeProviderCode(sendTransaction({
|
|
144
162
|
originalTransaction: props.originalTransaction,
|
|
@@ -151,8 +169,8 @@ export const collabEditPlugin = ({
|
|
|
151
169
|
track({
|
|
152
170
|
...props,
|
|
153
171
|
onTrackDataProcessed: steps => {
|
|
154
|
-
var _api$
|
|
155
|
-
api === null || api === void 0 ? void 0 : (_api$
|
|
172
|
+
var _api$analytics3, _api$analytics3$actio;
|
|
173
|
+
api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : (_api$analytics3$actio = _api$analytics3.actions) === null || _api$analytics3$actio === void 0 ? void 0 : _api$analytics3$actio.fireAnalyticsEvent({
|
|
156
174
|
action: ACTION.STEPS_TRACKED,
|
|
157
175
|
actionSubject: ACTION_SUBJECT.COLLAB,
|
|
158
176
|
attributes: {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { AnalyticsStep, InsertTypeAheadStep, LinkMetaStep, SetAttrsStep } from '@atlaskit/adf-schema/steps';
|
|
2
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
3
|
+
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
+
import { AddMarkStep, AddNodeMarkStep, AttrStep, RemoveMarkStep, RemoveNodeMarkStep, ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
5
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
6
|
+
const THRESHOLD = 50; // 50 milliseconds
|
|
7
|
+
|
|
8
|
+
export const createFilterTransaction = (recentTransactionsTimestamps, trackFilteredTransaction) => {
|
|
9
|
+
return tr => {
|
|
10
|
+
if (Boolean(tr.getMeta('appendTransaction'))) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
const isRemote = Boolean(tr.getMeta('isRemote'));
|
|
14
|
+
if (isRemote) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (tr.docChanged && tr.doc.eq(tr.before)) {
|
|
18
|
+
const transactionKey = generateTransactionKey(tr);
|
|
19
|
+
if (!transactionKey) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//Clean up tracked transactions when time over threshold
|
|
24
|
+
recentTransactionsTimestamps.forEach((value, key) => {
|
|
25
|
+
if (tr.time - value.timestamp > THRESHOLD) {
|
|
26
|
+
// Delete tracked transaction when over threshold
|
|
27
|
+
recentTransactionsTimestamps.delete(key);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const lastTransactionEntry = recentTransactionsTimestamps.get(transactionKey);
|
|
31
|
+
if (!lastTransactionEntry) {
|
|
32
|
+
// If no timestamp exists for the given transaction, allow transaction and add an entry
|
|
33
|
+
recentTransactionsTimestamps.set(transactionKey, {
|
|
34
|
+
timestamp: tr.time,
|
|
35
|
+
steps: tr.steps
|
|
36
|
+
});
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Track analytics for the filtered transaction
|
|
41
|
+
trackFilteredTransaction(tr);
|
|
42
|
+
if (fg('platform_editor_filter_spamming_transactions')) {
|
|
43
|
+
// Filter transaction
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return true; // Allow the transaction
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Helper function to create a u ique transaction key
|
|
52
|
+
export function generateTransactionKey(tr) {
|
|
53
|
+
return tr.steps.map(step => {
|
|
54
|
+
if (step instanceof RemoveNodeMarkStep || step instanceof AddNodeMarkStep || step instanceof SetAttrsStep || step instanceof AttrStep || step instanceof AnalyticsStep) {
|
|
55
|
+
if (step.pos !== undefined) {
|
|
56
|
+
return `${step.pos}`;
|
|
57
|
+
}
|
|
58
|
+
} else if (step instanceof AddMarkStep || step instanceof RemoveMarkStep || step instanceof ReplaceAroundStep || step instanceof ReplaceStep) {
|
|
59
|
+
return `from_${step.from}_to_${step.to}`;
|
|
60
|
+
} else if (step instanceof LinkMetaStep) {
|
|
61
|
+
return `from_${step.toJSON().from}_to_${step.toJSON().to}`;
|
|
62
|
+
} else if (step instanceof InsertTypeAheadStep) {
|
|
63
|
+
return `insertTypeAheadStep`;
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
}).join('_');
|
|
67
|
+
}
|
|
68
|
+
export const trackSpammingStepsPluginKey = new PluginKey('trackAndFilterSpammingStepsPluginKey');
|
|
69
|
+
export const createPlugin = trackFilteredTransaction => {
|
|
70
|
+
const recentTransactionsTimestamps = new Map();
|
|
71
|
+
return new SafePlugin({
|
|
72
|
+
key: trackSpammingStepsPluginKey,
|
|
73
|
+
filterTransaction: createFilterTransaction(recentTransactionsTimestamps, trackFilteredTransaction)
|
|
74
|
+
});
|
|
75
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isDirtyTransaction } from '@atlaskit/editor-common/collab';
|
|
2
2
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
3
3
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
-
import { isOrganicChange
|
|
4
|
+
import { isOrganicChange } from '../utils';
|
|
5
5
|
export const trackLastOrganicChangePluginKey = new PluginKey('collabTrackLastOrganicChangePlugin');
|
|
6
6
|
export const createPlugin = () => {
|
|
7
7
|
return new SafePlugin({
|
|
@@ -14,8 +14,11 @@ export const createPlugin = () => {
|
|
|
14
14
|
};
|
|
15
15
|
},
|
|
16
16
|
apply(transaction, prevPluginState) {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if (Boolean(transaction.getMeta('appendedTransaction'))) {
|
|
18
|
+
return prevPluginState;
|
|
19
|
+
}
|
|
20
|
+
const isRemote = Boolean(transaction.getMeta('isRemote'));
|
|
21
|
+
const isDocumentReplaceFromRemote = isRemote && Boolean(transaction.getMeta('replaceDocument'));
|
|
19
22
|
if (isDocumentReplaceFromRemote) {
|
|
20
23
|
return prevPluginState;
|
|
21
24
|
}
|
package/dist/esm/plugin.js
CHANGED
|
@@ -13,9 +13,10 @@ import { sendTransaction } from './events/send-transaction';
|
|
|
13
13
|
import { createPlugin } from './pm-plugins/main';
|
|
14
14
|
import { pluginKey as mainPluginKey } from './pm-plugins/main/plugin-key';
|
|
15
15
|
import { nativeCollabProviderPlugin } from './pm-plugins/native-collab-provider-plugin';
|
|
16
|
+
import { createPlugin as trackSpammingStepsPlugin } from './pm-plugins/track-and-filter-spamming-steps';
|
|
16
17
|
import { createPlugin as createLastOrganicChangePlugin, trackLastOrganicChangePluginKey } from './pm-plugins/track-last-organic-change';
|
|
17
18
|
import { createPlugin as createTrackNCSInitializationPlugin, trackNCSInitializationPluginKey } from './pm-plugins/track-ncs-initialization';
|
|
18
|
-
import { track } from './track-steps';
|
|
19
|
+
import { sanitizeStep, track } from './track-steps';
|
|
19
20
|
import { getAvatarColor } from './utils';
|
|
20
21
|
var providerBuilder = function providerBuilder(collabEditProviderPromise) {
|
|
21
22
|
return /*#__PURE__*/function () {
|
|
@@ -166,6 +167,27 @@ export var collabEditPlugin = function collabEditPlugin(_ref4) {
|
|
|
166
167
|
name: 'collabTrackNCSInitializationPlugin',
|
|
167
168
|
plugin: createTrackNCSInitializationPlugin
|
|
168
169
|
}]);
|
|
170
|
+
if (fg('platform_editor_filter_transactions_analytics') || fg('platform_editor_filter_spamming_transactions')) {
|
|
171
|
+
plugins.push({
|
|
172
|
+
name: 'trackAndFilterSpammingSteps',
|
|
173
|
+
plugin: function plugin() {
|
|
174
|
+
return trackSpammingStepsPlugin(function (tr) {
|
|
175
|
+
var _api$analytics;
|
|
176
|
+
var sanitizedSteps = tr.steps.map(function (step) {
|
|
177
|
+
return sanitizeStep(step);
|
|
178
|
+
});
|
|
179
|
+
api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || (_api$analytics = _api$analytics.actions) === null || _api$analytics === void 0 || _api$analytics.fireAnalyticsEvent({
|
|
180
|
+
action: ACTION.STEPS_FILTERED,
|
|
181
|
+
actionSubject: ACTION_SUBJECT.COLLAB,
|
|
182
|
+
attributes: {
|
|
183
|
+
steps: sanitizedSteps
|
|
184
|
+
},
|
|
185
|
+
eventType: EVENT_TYPE.OPERATIONAL
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
169
191
|
if (fg('platform_editor_last_organic_change')) {
|
|
170
192
|
plugins.push({
|
|
171
193
|
name: 'collabTrackLastOrganicChangePlugin',
|
|
@@ -175,8 +197,8 @@ export var collabEditPlugin = function collabEditPlugin(_ref4) {
|
|
|
175
197
|
return plugins;
|
|
176
198
|
},
|
|
177
199
|
onEditorViewStateUpdated: function onEditorViewStateUpdated(props) {
|
|
178
|
-
var _api$
|
|
179
|
-
var addErrorAnalytics = addSynchronyErrorAnalytics(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 || (_api$
|
|
200
|
+
var _api$analytics2, _api$editorViewMode, _options$useNativePlu;
|
|
201
|
+
var addErrorAnalytics = addSynchronyErrorAnalytics(props.newEditorState, props.newEditorState.tr, featureFlags, api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions);
|
|
180
202
|
var viewMode = api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode;
|
|
181
203
|
executeProviderCode(sendTransaction({
|
|
182
204
|
originalTransaction: props.originalTransaction,
|
|
@@ -188,8 +210,8 @@ export var collabEditPlugin = function collabEditPlugin(_ref4) {
|
|
|
188
210
|
}), addErrorAnalytics);
|
|
189
211
|
track(_objectSpread(_objectSpread({}, props), {}, {
|
|
190
212
|
onTrackDataProcessed: function onTrackDataProcessed(steps) {
|
|
191
|
-
var _api$
|
|
192
|
-
api === null || api === void 0 || (_api$
|
|
213
|
+
var _api$analytics3;
|
|
214
|
+
api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || (_api$analytics3 = _api$analytics3.actions) === null || _api$analytics3 === void 0 || _api$analytics3.fireAnalyticsEvent({
|
|
193
215
|
action: ACTION.STEPS_TRACKED,
|
|
194
216
|
actionSubject: ACTION_SUBJECT.COLLAB,
|
|
195
217
|
attributes: {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { AnalyticsStep, InsertTypeAheadStep, LinkMetaStep, SetAttrsStep } from '@atlaskit/adf-schema/steps';
|
|
2
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
3
|
+
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
+
import { AddMarkStep, AddNodeMarkStep, AttrStep, RemoveMarkStep, RemoveNodeMarkStep, ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
|
|
5
|
+
import { fg } from '@atlaskit/platform-feature-flags';
|
|
6
|
+
var THRESHOLD = 50; // 50 milliseconds
|
|
7
|
+
|
|
8
|
+
export var createFilterTransaction = function createFilterTransaction(recentTransactionsTimestamps, trackFilteredTransaction) {
|
|
9
|
+
return function (tr) {
|
|
10
|
+
if (Boolean(tr.getMeta('appendTransaction'))) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
var isRemote = Boolean(tr.getMeta('isRemote'));
|
|
14
|
+
if (isRemote) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (tr.docChanged && tr.doc.eq(tr.before)) {
|
|
18
|
+
var transactionKey = generateTransactionKey(tr);
|
|
19
|
+
if (!transactionKey) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//Clean up tracked transactions when time over threshold
|
|
24
|
+
recentTransactionsTimestamps.forEach(function (value, key) {
|
|
25
|
+
if (tr.time - value.timestamp > THRESHOLD) {
|
|
26
|
+
// Delete tracked transaction when over threshold
|
|
27
|
+
recentTransactionsTimestamps.delete(key);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
var lastTransactionEntry = recentTransactionsTimestamps.get(transactionKey);
|
|
31
|
+
if (!lastTransactionEntry) {
|
|
32
|
+
// If no timestamp exists for the given transaction, allow transaction and add an entry
|
|
33
|
+
recentTransactionsTimestamps.set(transactionKey, {
|
|
34
|
+
timestamp: tr.time,
|
|
35
|
+
steps: tr.steps
|
|
36
|
+
});
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Track analytics for the filtered transaction
|
|
41
|
+
trackFilteredTransaction(tr);
|
|
42
|
+
if (fg('platform_editor_filter_spamming_transactions')) {
|
|
43
|
+
// Filter transaction
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return true; // Allow the transaction
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Helper function to create a u ique transaction key
|
|
52
|
+
export function generateTransactionKey(tr) {
|
|
53
|
+
return tr.steps.map(function (step) {
|
|
54
|
+
if (step instanceof RemoveNodeMarkStep || step instanceof AddNodeMarkStep || step instanceof SetAttrsStep || step instanceof AttrStep || step instanceof AnalyticsStep) {
|
|
55
|
+
if (step.pos !== undefined) {
|
|
56
|
+
return "".concat(step.pos);
|
|
57
|
+
}
|
|
58
|
+
} else if (step instanceof AddMarkStep || step instanceof RemoveMarkStep || step instanceof ReplaceAroundStep || step instanceof ReplaceStep) {
|
|
59
|
+
return "from_".concat(step.from, "_to_").concat(step.to);
|
|
60
|
+
} else if (step instanceof LinkMetaStep) {
|
|
61
|
+
return "from_".concat(step.toJSON().from, "_to_").concat(step.toJSON().to);
|
|
62
|
+
} else if (step instanceof InsertTypeAheadStep) {
|
|
63
|
+
return "insertTypeAheadStep";
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
}).join('_');
|
|
67
|
+
}
|
|
68
|
+
export var trackSpammingStepsPluginKey = new PluginKey('trackAndFilterSpammingStepsPluginKey');
|
|
69
|
+
export var createPlugin = function createPlugin(trackFilteredTransaction) {
|
|
70
|
+
var recentTransactionsTimestamps = new Map();
|
|
71
|
+
return new SafePlugin({
|
|
72
|
+
key: trackSpammingStepsPluginKey,
|
|
73
|
+
filterTransaction: createFilterTransaction(recentTransactionsTimestamps, trackFilteredTransaction)
|
|
74
|
+
});
|
|
75
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isDirtyTransaction } from '@atlaskit/editor-common/collab';
|
|
2
2
|
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
3
3
|
import { PluginKey } from '@atlaskit/editor-prosemirror/state';
|
|
4
|
-
import { isOrganicChange
|
|
4
|
+
import { isOrganicChange } from '../utils';
|
|
5
5
|
export var trackLastOrganicChangePluginKey = new PluginKey('collabTrackLastOrganicChangePlugin');
|
|
6
6
|
export var createPlugin = function createPlugin() {
|
|
7
7
|
return new SafePlugin({
|
|
@@ -14,8 +14,11 @@ export var createPlugin = function createPlugin() {
|
|
|
14
14
|
};
|
|
15
15
|
},
|
|
16
16
|
apply: function apply(transaction, prevPluginState) {
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
if (Boolean(transaction.getMeta('appendedTransaction'))) {
|
|
18
|
+
return prevPluginState;
|
|
19
|
+
}
|
|
20
|
+
var isRemote = Boolean(transaction.getMeta('isRemote'));
|
|
21
|
+
var isDocumentReplaceFromRemote = isRemote && Boolean(transaction.getMeta('replaceDocument'));
|
|
19
22
|
if (isDocumentReplaceFromRemote) {
|
|
20
23
|
return prevPluginState;
|
|
21
24
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
2
|
+
import { PluginKey, type Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
3
|
+
import { type Step } from '@atlaskit/editor-prosemirror/transform';
|
|
4
|
+
import type { TrackSpammingStepsMetadata } from '../types';
|
|
5
|
+
export type RecentTransactionTimestamp = {
|
|
6
|
+
timestamp: number;
|
|
7
|
+
steps: Step[];
|
|
8
|
+
};
|
|
9
|
+
export type RecentTransactionTimestamps = Map<string, RecentTransactionTimestamp>;
|
|
10
|
+
export type TrackFilteredTransaction = (tr: Transaction) => void;
|
|
11
|
+
export declare const createFilterTransaction: (recentTransactionsTimestamps: RecentTransactionTimestamps, trackFilteredTransaction: TrackFilteredTransaction) => (tr: Transaction) => boolean;
|
|
12
|
+
export declare function generateTransactionKey(tr: Transaction): string;
|
|
13
|
+
export declare const trackSpammingStepsPluginKey: PluginKey<TrackSpammingStepsMetadata>;
|
|
14
|
+
export declare const createPlugin: (trackFilteredTransaction: TrackFilteredTransaction) => SafePlugin<TrackSpammingStepsMetadata>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/t
|
|
|
3
3
|
import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
|
|
4
4
|
import type { EditorViewModePlugin } from '@atlaskit/editor-plugin-editor-viewmode';
|
|
5
5
|
import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
|
|
6
|
+
import { type Step } from '@atlaskit/editor-prosemirror/dist/types/transform';
|
|
6
7
|
import type { Mark } from '@atlaskit/editor-prosemirror/model';
|
|
7
8
|
import type { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
8
9
|
export type { InviteToEditComponentProps, InviteToEditButtonProps, CollabInviteToEditProps, CollabAnalyticsProps, } from '@atlaskit/editor-common/collab';
|
|
@@ -26,6 +27,12 @@ export type LastOrganicChangeMetadata = {
|
|
|
26
27
|
lastLocalOrganicChangeAt: null | number;
|
|
27
28
|
lastRemoteOrganicChangeAt: null | number;
|
|
28
29
|
};
|
|
30
|
+
export type TrackSpammingStepsMetadata = {
|
|
31
|
+
recentTransactionsTimestemps: Map<string, {
|
|
32
|
+
timestamp: number;
|
|
33
|
+
steps: Step[];
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
29
36
|
export type CollabEditPluginSharedState = {
|
|
30
37
|
initialised: CollabInitializedMetadata & LastOrganicChangeMetadata;
|
|
31
38
|
activeParticipants: ReadOnlyParticipants | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
|
|
2
|
+
import { PluginKey, type Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
3
|
+
import { type Step } from '@atlaskit/editor-prosemirror/transform';
|
|
4
|
+
import type { TrackSpammingStepsMetadata } from '../types';
|
|
5
|
+
export type RecentTransactionTimestamp = {
|
|
6
|
+
timestamp: number;
|
|
7
|
+
steps: Step[];
|
|
8
|
+
};
|
|
9
|
+
export type RecentTransactionTimestamps = Map<string, RecentTransactionTimestamp>;
|
|
10
|
+
export type TrackFilteredTransaction = (tr: Transaction) => void;
|
|
11
|
+
export declare const createFilterTransaction: (recentTransactionsTimestamps: RecentTransactionTimestamps, trackFilteredTransaction: TrackFilteredTransaction) => (tr: Transaction) => boolean;
|
|
12
|
+
export declare function generateTransactionKey(tr: Transaction): string;
|
|
13
|
+
export declare const trackSpammingStepsPluginKey: PluginKey<TrackSpammingStepsMetadata>;
|
|
14
|
+
export declare const createPlugin: (trackFilteredTransaction: TrackFilteredTransaction) => SafePlugin<TrackSpammingStepsMetadata>;
|
|
@@ -3,6 +3,7 @@ import type { NextEditorPlugin, OptionalPlugin } from '@atlaskit/editor-common/t
|
|
|
3
3
|
import type { AnalyticsPlugin } from '@atlaskit/editor-plugin-analytics';
|
|
4
4
|
import type { EditorViewModePlugin } from '@atlaskit/editor-plugin-editor-viewmode';
|
|
5
5
|
import type { FeatureFlagsPlugin } from '@atlaskit/editor-plugin-feature-flags';
|
|
6
|
+
import { type Step } from '@atlaskit/editor-prosemirror/dist/types/transform';
|
|
6
7
|
import type { Mark } from '@atlaskit/editor-prosemirror/model';
|
|
7
8
|
import type { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
8
9
|
export type { InviteToEditComponentProps, InviteToEditButtonProps, CollabInviteToEditProps, CollabAnalyticsProps, } from '@atlaskit/editor-common/collab';
|
|
@@ -26,6 +27,12 @@ export type LastOrganicChangeMetadata = {
|
|
|
26
27
|
lastLocalOrganicChangeAt: null | number;
|
|
27
28
|
lastRemoteOrganicChangeAt: null | number;
|
|
28
29
|
};
|
|
30
|
+
export type TrackSpammingStepsMetadata = {
|
|
31
|
+
recentTransactionsTimestemps: Map<string, {
|
|
32
|
+
timestamp: number;
|
|
33
|
+
steps: Step[];
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
29
36
|
export type CollabEditPluginSharedState = {
|
|
30
37
|
initialised: CollabInitializedMetadata & LastOrganicChangeMetadata;
|
|
31
38
|
activeParticipants: ReadOnlyParticipants | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/editor-plugin-collab-edit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Collab Edit plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@atlaskit/adf-schema": "^40.8.1",
|
|
36
36
|
"@atlaskit/custom-steps": "^0.7.0",
|
|
37
|
-
"@atlaskit/editor-common": "^88.
|
|
37
|
+
"@atlaskit/editor-common": "^88.2.0",
|
|
38
38
|
"@atlaskit/editor-plugin-analytics": "1.8.1",
|
|
39
39
|
"@atlaskit/editor-plugin-editor-viewmode": "^2.1.0",
|
|
40
40
|
"@atlaskit/editor-plugin-feature-flags": "^1.2.0",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@af/integration-testing": "*",
|
|
53
53
|
"@af/visual-regression": "*",
|
|
54
|
-
"@atlaskit/editor-plugin-mentions": "^2.
|
|
55
|
-
"@atlaskit/editor-plugin-text-formatting": "^1.
|
|
54
|
+
"@atlaskit/editor-plugin-mentions": "^2.5.0",
|
|
55
|
+
"@atlaskit/editor-plugin-text-formatting": "^1.14.0",
|
|
56
56
|
"@atlaskit/editor-plugin-type-ahead": "^1.8.0",
|
|
57
57
|
"@atlaskit/editor-plugin-unsupported-content": "^1.8.0",
|
|
58
58
|
"@atlaskit/editor-test-helpers": "^18.31.0",
|
|
@@ -102,6 +102,12 @@
|
|
|
102
102
|
"platform-feature-flags": {
|
|
103
103
|
"platform_editor_last_organic_change": {
|
|
104
104
|
"type": "boolean"
|
|
105
|
+
},
|
|
106
|
+
"platform_editor_filter_transactions_analytics": {
|
|
107
|
+
"type": "boolean"
|
|
108
|
+
},
|
|
109
|
+
"platform_editor_filter_spamming_transactions": {
|
|
110
|
+
"type": "boolean"
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
}
|