@atlaskit/editor-plugin-metrics 1.1.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/.eslintrc.js ADDED
@@ -0,0 +1,14 @@
1
+ module.exports = {
2
+ rules: {
3
+ '@typescript-eslint/no-duplicate-imports': 'error',
4
+ '@typescript-eslint/no-explicit-any': 'error',
5
+ },
6
+ overrides: [
7
+ {
8
+ files: ['**/__tests__/**/*.{js,ts,tsx}', '**/examples/**/*.{js,ts,tsx}'],
9
+ rules: {
10
+ '@typescript-eslint/no-explicit-any': 'off',
11
+ },
12
+ },
13
+ ],
14
+ };
package/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # @atlaskit/editor-plugin-metrics
2
+
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#100173](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/100173)
8
+ [`ab687f83ea47e`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/ab687f83ea47e) -
9
+ ED-26142 Set up metrics plugin for measuring efficiency and effectiveness in editor
10
+
11
+ ## 1.0.0
package/LICENSE.md ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2023 Atlassian Pty Ltd
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
4
+ compliance with the License. You may obtain a copy of the License at
5
+
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+
8
+ Unless required by applicable law or agreed to in writing, software distributed under the License is
9
+ distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
10
+ implied. See the License for the specific language governing permissions and limitations under the
11
+ License.
package/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # EditorPluginMetrics
2
+
3
+ Metrics plugin for @atlaskit/editor-core
4
+
5
+ ## Usage
6
+
7
+ `import EditorPluginMetrics from '@atlaskit/editor-plugin-metrics';`
8
+
9
+ Detailed docs and example usage can be found
10
+ [here](https://atlaskit.atlassian.com/packages/editor/editor-plugin-metrics).
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "metricsPlugin", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _metricsPlugin.metricsPlugin;
10
+ }
11
+ });
12
+ var _metricsPlugin = require("./metricsPlugin");
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.metricsPlugin = void 0;
7
+ var _main = require("./pm-plugins/main");
8
+ /**
9
+ * Metrics plugin to be added to an `EditorPresetBuilder` and used with `ComposableEditor`
10
+ * from `@atlaskit/editor-core`.
11
+ */
12
+ var metricsPlugin = exports.metricsPlugin = function metricsPlugin(_ref) {
13
+ var api = _ref.api;
14
+ return {
15
+ name: 'metrics',
16
+ pmPlugins: function pmPlugins() {
17
+ return [{
18
+ name: 'metrics',
19
+ plugin: function plugin() {
20
+ return (0, _main.createPlugin)(api);
21
+ }
22
+ }];
23
+ },
24
+ commands: {
25
+ stopActiveSession: function stopActiveSession() {
26
+ return function (_ref2) {
27
+ var tr = _ref2.tr;
28
+ if (!api) {
29
+ return tr;
30
+ }
31
+ var newTr = tr;
32
+ newTr.setMeta(_main.metricsKey, {
33
+ stopActiveSession: true
34
+ });
35
+ return newTr;
36
+ };
37
+ },
38
+ fireSessionAnalytics: function fireSessionAnalytics() {
39
+ return function (_ref3) {
40
+ var tr = _ref3.tr;
41
+ if (!api) {
42
+ return tr;
43
+ }
44
+ var state = api.metrics.sharedState.currentState();
45
+ if (!state) {
46
+ return tr;
47
+ }
48
+
49
+ // const eventAttributes = {
50
+ // efficiency: {
51
+ // totalActiveTime: state.activeSessionTime || 0,
52
+ // totalActionCount: state.totalActionCount || 0,
53
+ // actionTypeCount: state.actionTypeCount,
54
+ // totalInactiveTime:
55
+ // Date.now() - (state.editSessionStartTime || 0) - (state.activeSessionTime || 0),
56
+ // },
57
+ // TODO: Add effectiveness attributes
58
+ // effectiveness: {
59
+ // undoCount: 0,
60
+ // repeatedActionCount: 0,
61
+ // safeInsertCount: 0,
62
+ // },
63
+ // };
64
+
65
+ // fire analytics event
66
+ // api.analytics.actions.attachAnalyticsEvent({});
67
+ return tr;
68
+ };
69
+ }
70
+ },
71
+ getSharedState: function getSharedState(editorState) {
72
+ if (!editorState) {
73
+ return _main.initialPluginState;
74
+ }
75
+ return _main.metricsKey.getState(editorState);
76
+ }
77
+ };
78
+ };
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.metricsKey = exports.initialPluginState = exports.createPlugin = void 0;
8
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _steps = require("@atlaskit/adf-schema/steps");
10
+ var _safePlugin = require("@atlaskit/editor-common/safe-plugin");
11
+ var _utils = require("@atlaskit/editor-common/utils");
12
+ var _state = require("@atlaskit/editor-prosemirror/state");
13
+ var _activeSessionTimer = require("./utils/active-session-timer");
14
+ 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; }
15
+ 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; }
16
+ var metricsKey = exports.metricsKey = new _state.PluginKey('metricsPlugin');
17
+ var initialPluginState = exports.initialPluginState = {
18
+ editSessionStartTime: undefined,
19
+ intentToStartEditTime: undefined,
20
+ lastSelection: undefined,
21
+ activeSessionTime: 0,
22
+ totalActionCount: 0,
23
+ timeOfLastTextInput: undefined,
24
+ actionTypeCount: {
25
+ textInputCount: 0,
26
+ nodeInsertionCount: 0,
27
+ nodeAttributeChangeCount: 0,
28
+ contentMovedCount: 0,
29
+ nodeDeletionCount: 0
30
+ }
31
+ };
32
+ var createPlugin = exports.createPlugin = function createPlugin(api) {
33
+ var timer = new _activeSessionTimer.ActiveSessionTimer(api);
34
+ return new _safePlugin.SafePlugin({
35
+ key: metricsKey,
36
+ state: {
37
+ init: function init() {
38
+ return _objectSpread(_objectSpread({}, initialPluginState), {}, {
39
+ editSessionStartTime: performance.now()
40
+ });
41
+ },
42
+ apply: function apply(tr, pluginState) {
43
+ var meta = tr.getMeta(metricsKey);
44
+ var intentToStartEditTime = (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime;
45
+ if (meta && meta.stopActiveSession) {
46
+ // console.log('stopActiveSession');
47
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
48
+ intentToStartEditTime: undefined,
49
+ lastSelection: undefined
50
+ });
51
+ }
52
+ if (!intentToStartEditTime) {
53
+ return pluginState;
54
+ }
55
+ var canIgnoreTr = function canIgnoreTr() {
56
+ return !tr.steps.every(function (e) {
57
+ return e instanceof _steps.AnalyticsStep;
58
+ });
59
+ };
60
+ if (tr.docChanged && canIgnoreTr()) {
61
+ var now = performance.now();
62
+ timer.startTimer();
63
+
64
+ // If previous and current action is text insertion, then don't increase total action count
65
+ var isActionTextInput = (0, _utils.isTextInput)(tr);
66
+ if (pluginState.timeOfLastTextInput && isActionTextInput) {
67
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
68
+ activeSessionTime: pluginState.activeSessionTime + (now - pluginState.timeOfLastTextInput),
69
+ totalActionCount: pluginState.totalActionCount,
70
+ timeOfLastTextInput: now
71
+ });
72
+ }
73
+
74
+ // TODO: Add actionTypeCount
75
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
76
+ activeSessionTime: pluginState.activeSessionTime + (now - intentToStartEditTime),
77
+ totalActionCount: pluginState.totalActionCount + 1,
78
+ timeOfLastTextInput: isActionTextInput ? now : undefined
79
+ });
80
+ }
81
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
82
+ lastSelection: (meta === null || meta === void 0 ? void 0 : meta.newSelection) || pluginState.lastSelection,
83
+ intentToStartEditTime: (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime
84
+ });
85
+ }
86
+ },
87
+ view: function view() {
88
+ return {
89
+ destroy: function destroy() {
90
+ var _api$metrics;
91
+ api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$metrics = api.metrics) === null || _api$metrics === void 0 ? void 0 : _api$metrics.commands.fireSessionAnalytics());
92
+ timer.cleanupTimer();
93
+ }
94
+ };
95
+ },
96
+ props: {
97
+ handleDOMEvents: {
98
+ click: function click(view) {
99
+ var _pluginState$lastSele, _pluginState$lastSele2;
100
+ var pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
101
+ if (!pluginState || pluginState.intentToStartEditTime) {
102
+ return false;
103
+ }
104
+ var newSelection = view.state.tr.selection;
105
+ var newTr = view.state.tr;
106
+ if ((pluginState === null || pluginState === void 0 || (_pluginState$lastSele = pluginState.lastSelection) === null || _pluginState$lastSele === void 0 ? void 0 : _pluginState$lastSele.from) !== newSelection.from && (pluginState === null || pluginState === void 0 || (_pluginState$lastSele2 = pluginState.lastSelection) === null || _pluginState$lastSele2 === void 0 ? void 0 : _pluginState$lastSele2.to) !== newSelection.to) {
107
+ newTr.setMeta(metricsKey, {
108
+ intentToStartEditTime: performance.now(),
109
+ newSelection: newSelection
110
+ });
111
+ view.dispatch(newTr);
112
+ timer.startTimer();
113
+ }
114
+ return false;
115
+ }
116
+ }
117
+ }
118
+ });
119
+ };
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.ActiveSessionTimer = void 0;
8
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
9
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
10
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
+ var ActiveSessionTimer = exports.ActiveSessionTimer = /*#__PURE__*/(0, _createClass2.default)(function ActiveSessionTimer(api) {
12
+ var _this = this;
13
+ (0, _classCallCheck2.default)(this, ActiveSessionTimer);
14
+ (0, _defineProperty2.default)(this, "startTimer", function () {
15
+ if (_this.timerId) {
16
+ clearTimeout(_this.timerId);
17
+ }
18
+ _this.timerId = window.setTimeout(function () {
19
+ _this.cleanupTimer();
20
+ }, 3000);
21
+ });
22
+ (0, _defineProperty2.default)(this, "cleanupTimer", function () {
23
+ if (_this.api) {
24
+ var _this$api$metrics;
25
+ _this.api.core.actions.execute((_this$api$metrics = _this.api.metrics) === null || _this$api$metrics === void 0 ? void 0 : _this$api$metrics.commands.stopActiveSession());
26
+ }
27
+ if (_this.timerId) {
28
+ clearTimeout(_this.timerId);
29
+ _this.timerId = null;
30
+ }
31
+ });
32
+ this.timerId = null;
33
+ this.api = api;
34
+ });
@@ -0,0 +1,4 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ // Entry file in package.json
3
+
4
+ export { metricsPlugin } from './metricsPlugin';
@@ -0,0 +1,67 @@
1
+ import { createPlugin, initialPluginState, metricsKey } from './pm-plugins/main';
2
+ /**
3
+ * Metrics plugin to be added to an `EditorPresetBuilder` and used with `ComposableEditor`
4
+ * from `@atlaskit/editor-core`.
5
+ */
6
+ export const metricsPlugin = ({
7
+ api
8
+ }) => ({
9
+ name: 'metrics',
10
+ pmPlugins() {
11
+ return [{
12
+ name: 'metrics',
13
+ plugin: () => createPlugin(api)
14
+ }];
15
+ },
16
+ commands: {
17
+ stopActiveSession: () => ({
18
+ tr
19
+ }) => {
20
+ if (!api) {
21
+ return tr;
22
+ }
23
+ const newTr = tr;
24
+ newTr.setMeta(metricsKey, {
25
+ stopActiveSession: true
26
+ });
27
+ return newTr;
28
+ },
29
+ fireSessionAnalytics: () => ({
30
+ tr
31
+ }) => {
32
+ if (!api) {
33
+ return tr;
34
+ }
35
+ const state = api.metrics.sharedState.currentState();
36
+ if (!state) {
37
+ return tr;
38
+ }
39
+
40
+ // const eventAttributes = {
41
+ // efficiency: {
42
+ // totalActiveTime: state.activeSessionTime || 0,
43
+ // totalActionCount: state.totalActionCount || 0,
44
+ // actionTypeCount: state.actionTypeCount,
45
+ // totalInactiveTime:
46
+ // Date.now() - (state.editSessionStartTime || 0) - (state.activeSessionTime || 0),
47
+ // },
48
+ // TODO: Add effectiveness attributes
49
+ // effectiveness: {
50
+ // undoCount: 0,
51
+ // repeatedActionCount: 0,
52
+ // safeInsertCount: 0,
53
+ // },
54
+ // };
55
+
56
+ // fire analytics event
57
+ // api.analytics.actions.attachAnalyticsEvent({});
58
+ return tr;
59
+ }
60
+ },
61
+ getSharedState(editorState) {
62
+ if (!editorState) {
63
+ return initialPluginState;
64
+ }
65
+ return metricsKey.getState(editorState);
66
+ }
67
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,110 @@
1
+ import { AnalyticsStep } from '@atlaskit/adf-schema/steps';
2
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
+ import { isTextInput } from '@atlaskit/editor-common/utils';
4
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
+ import { ActiveSessionTimer } from './utils/active-session-timer';
6
+ export const metricsKey = new PluginKey('metricsPlugin');
7
+ export const initialPluginState = {
8
+ editSessionStartTime: undefined,
9
+ intentToStartEditTime: undefined,
10
+ lastSelection: undefined,
11
+ activeSessionTime: 0,
12
+ totalActionCount: 0,
13
+ timeOfLastTextInput: undefined,
14
+ actionTypeCount: {
15
+ textInputCount: 0,
16
+ nodeInsertionCount: 0,
17
+ nodeAttributeChangeCount: 0,
18
+ contentMovedCount: 0,
19
+ nodeDeletionCount: 0
20
+ }
21
+ };
22
+ export const createPlugin = api => {
23
+ const timer = new ActiveSessionTimer(api);
24
+ return new SafePlugin({
25
+ key: metricsKey,
26
+ state: {
27
+ init: () => {
28
+ return {
29
+ ...initialPluginState,
30
+ editSessionStartTime: performance.now()
31
+ };
32
+ },
33
+ apply(tr, pluginState) {
34
+ const meta = tr.getMeta(metricsKey);
35
+ const intentToStartEditTime = (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime;
36
+ if (meta && meta.stopActiveSession) {
37
+ // console.log('stopActiveSession');
38
+ return {
39
+ ...pluginState,
40
+ intentToStartEditTime: undefined,
41
+ lastSelection: undefined
42
+ };
43
+ }
44
+ if (!intentToStartEditTime) {
45
+ return pluginState;
46
+ }
47
+ const canIgnoreTr = () => !tr.steps.every(e => e instanceof AnalyticsStep);
48
+ if (tr.docChanged && canIgnoreTr()) {
49
+ const now = performance.now();
50
+ timer.startTimer();
51
+
52
+ // If previous and current action is text insertion, then don't increase total action count
53
+ const isActionTextInput = isTextInput(tr);
54
+ if (pluginState.timeOfLastTextInput && isActionTextInput) {
55
+ return {
56
+ ...pluginState,
57
+ activeSessionTime: pluginState.activeSessionTime + (now - pluginState.timeOfLastTextInput),
58
+ totalActionCount: pluginState.totalActionCount,
59
+ timeOfLastTextInput: now
60
+ };
61
+ }
62
+
63
+ // TODO: Add actionTypeCount
64
+ return {
65
+ ...pluginState,
66
+ activeSessionTime: pluginState.activeSessionTime + (now - intentToStartEditTime),
67
+ totalActionCount: pluginState.totalActionCount + 1,
68
+ timeOfLastTextInput: isActionTextInput ? now : undefined
69
+ };
70
+ }
71
+ return {
72
+ ...pluginState,
73
+ lastSelection: (meta === null || meta === void 0 ? void 0 : meta.newSelection) || pluginState.lastSelection,
74
+ intentToStartEditTime: (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime
75
+ };
76
+ }
77
+ },
78
+ view() {
79
+ return {
80
+ destroy() {
81
+ var _api$metrics;
82
+ api === null || api === void 0 ? void 0 : api.core.actions.execute(api === null || api === void 0 ? void 0 : (_api$metrics = api.metrics) === null || _api$metrics === void 0 ? void 0 : _api$metrics.commands.fireSessionAnalytics());
83
+ timer.cleanupTimer();
84
+ }
85
+ };
86
+ },
87
+ props: {
88
+ handleDOMEvents: {
89
+ click: view => {
90
+ var _pluginState$lastSele, _pluginState$lastSele2;
91
+ const pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
92
+ if (!pluginState || pluginState.intentToStartEditTime) {
93
+ return false;
94
+ }
95
+ const newSelection = view.state.tr.selection;
96
+ const newTr = view.state.tr;
97
+ if ((pluginState === null || pluginState === void 0 ? void 0 : (_pluginState$lastSele = pluginState.lastSelection) === null || _pluginState$lastSele === void 0 ? void 0 : _pluginState$lastSele.from) !== newSelection.from && (pluginState === null || pluginState === void 0 ? void 0 : (_pluginState$lastSele2 = pluginState.lastSelection) === null || _pluginState$lastSele2 === void 0 ? void 0 : _pluginState$lastSele2.to) !== newSelection.to) {
98
+ newTr.setMeta(metricsKey, {
99
+ intentToStartEditTime: performance.now(),
100
+ newSelection: newSelection
101
+ });
102
+ view.dispatch(newTr);
103
+ timer.startTimer();
104
+ }
105
+ return false;
106
+ }
107
+ }
108
+ }
109
+ });
110
+ };
@@ -0,0 +1,25 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ export class ActiveSessionTimer {
3
+ constructor(api) {
4
+ _defineProperty(this, "startTimer", () => {
5
+ if (this.timerId) {
6
+ clearTimeout(this.timerId);
7
+ }
8
+ this.timerId = window.setTimeout(() => {
9
+ this.cleanupTimer();
10
+ }, 3000);
11
+ });
12
+ _defineProperty(this, "cleanupTimer", () => {
13
+ if (this.api) {
14
+ var _this$api$metrics;
15
+ this.api.core.actions.execute((_this$api$metrics = this.api.metrics) === null || _this$api$metrics === void 0 ? void 0 : _this$api$metrics.commands.stopActiveSession());
16
+ }
17
+ if (this.timerId) {
18
+ clearTimeout(this.timerId);
19
+ this.timerId = null;
20
+ }
21
+ });
22
+ this.timerId = null;
23
+ this.api = api;
24
+ }
25
+ }
@@ -0,0 +1,4 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ // Entry file in package.json
3
+
4
+ export { metricsPlugin } from './metricsPlugin';
@@ -0,0 +1,72 @@
1
+ import { createPlugin, initialPluginState, metricsKey } from './pm-plugins/main';
2
+ /**
3
+ * Metrics plugin to be added to an `EditorPresetBuilder` and used with `ComposableEditor`
4
+ * from `@atlaskit/editor-core`.
5
+ */
6
+ export var metricsPlugin = function metricsPlugin(_ref) {
7
+ var api = _ref.api;
8
+ return {
9
+ name: 'metrics',
10
+ pmPlugins: function pmPlugins() {
11
+ return [{
12
+ name: 'metrics',
13
+ plugin: function plugin() {
14
+ return createPlugin(api);
15
+ }
16
+ }];
17
+ },
18
+ commands: {
19
+ stopActiveSession: function stopActiveSession() {
20
+ return function (_ref2) {
21
+ var tr = _ref2.tr;
22
+ if (!api) {
23
+ return tr;
24
+ }
25
+ var newTr = tr;
26
+ newTr.setMeta(metricsKey, {
27
+ stopActiveSession: true
28
+ });
29
+ return newTr;
30
+ };
31
+ },
32
+ fireSessionAnalytics: function fireSessionAnalytics() {
33
+ return function (_ref3) {
34
+ var tr = _ref3.tr;
35
+ if (!api) {
36
+ return tr;
37
+ }
38
+ var state = api.metrics.sharedState.currentState();
39
+ if (!state) {
40
+ return tr;
41
+ }
42
+
43
+ // const eventAttributes = {
44
+ // efficiency: {
45
+ // totalActiveTime: state.activeSessionTime || 0,
46
+ // totalActionCount: state.totalActionCount || 0,
47
+ // actionTypeCount: state.actionTypeCount,
48
+ // totalInactiveTime:
49
+ // Date.now() - (state.editSessionStartTime || 0) - (state.activeSessionTime || 0),
50
+ // },
51
+ // TODO: Add effectiveness attributes
52
+ // effectiveness: {
53
+ // undoCount: 0,
54
+ // repeatedActionCount: 0,
55
+ // safeInsertCount: 0,
56
+ // },
57
+ // };
58
+
59
+ // fire analytics event
60
+ // api.analytics.actions.attachAnalyticsEvent({});
61
+ return tr;
62
+ };
63
+ }
64
+ },
65
+ getSharedState: function getSharedState(editorState) {
66
+ if (!editorState) {
67
+ return initialPluginState;
68
+ }
69
+ return metricsKey.getState(editorState);
70
+ }
71
+ };
72
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,112 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ 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; }
3
+ 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) { _defineProperty(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; }
4
+ import { AnalyticsStep } from '@atlaskit/adf-schema/steps';
5
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
6
+ import { isTextInput } from '@atlaskit/editor-common/utils';
7
+ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
8
+ import { ActiveSessionTimer } from './utils/active-session-timer';
9
+ export var metricsKey = new PluginKey('metricsPlugin');
10
+ export var initialPluginState = {
11
+ editSessionStartTime: undefined,
12
+ intentToStartEditTime: undefined,
13
+ lastSelection: undefined,
14
+ activeSessionTime: 0,
15
+ totalActionCount: 0,
16
+ timeOfLastTextInput: undefined,
17
+ actionTypeCount: {
18
+ textInputCount: 0,
19
+ nodeInsertionCount: 0,
20
+ nodeAttributeChangeCount: 0,
21
+ contentMovedCount: 0,
22
+ nodeDeletionCount: 0
23
+ }
24
+ };
25
+ export var createPlugin = function createPlugin(api) {
26
+ var timer = new ActiveSessionTimer(api);
27
+ return new SafePlugin({
28
+ key: metricsKey,
29
+ state: {
30
+ init: function init() {
31
+ return _objectSpread(_objectSpread({}, initialPluginState), {}, {
32
+ editSessionStartTime: performance.now()
33
+ });
34
+ },
35
+ apply: function apply(tr, pluginState) {
36
+ var meta = tr.getMeta(metricsKey);
37
+ var intentToStartEditTime = (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime;
38
+ if (meta && meta.stopActiveSession) {
39
+ // console.log('stopActiveSession');
40
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
41
+ intentToStartEditTime: undefined,
42
+ lastSelection: undefined
43
+ });
44
+ }
45
+ if (!intentToStartEditTime) {
46
+ return pluginState;
47
+ }
48
+ var canIgnoreTr = function canIgnoreTr() {
49
+ return !tr.steps.every(function (e) {
50
+ return e instanceof AnalyticsStep;
51
+ });
52
+ };
53
+ if (tr.docChanged && canIgnoreTr()) {
54
+ var now = performance.now();
55
+ timer.startTimer();
56
+
57
+ // If previous and current action is text insertion, then don't increase total action count
58
+ var isActionTextInput = isTextInput(tr);
59
+ if (pluginState.timeOfLastTextInput && isActionTextInput) {
60
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
61
+ activeSessionTime: pluginState.activeSessionTime + (now - pluginState.timeOfLastTextInput),
62
+ totalActionCount: pluginState.totalActionCount,
63
+ timeOfLastTextInput: now
64
+ });
65
+ }
66
+
67
+ // TODO: Add actionTypeCount
68
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
69
+ activeSessionTime: pluginState.activeSessionTime + (now - intentToStartEditTime),
70
+ totalActionCount: pluginState.totalActionCount + 1,
71
+ timeOfLastTextInput: isActionTextInput ? now : undefined
72
+ });
73
+ }
74
+ return _objectSpread(_objectSpread({}, pluginState), {}, {
75
+ lastSelection: (meta === null || meta === void 0 ? void 0 : meta.newSelection) || pluginState.lastSelection,
76
+ intentToStartEditTime: (meta === null || meta === void 0 ? void 0 : meta.intentToStartEditTime) || pluginState.intentToStartEditTime
77
+ });
78
+ }
79
+ },
80
+ view: function view() {
81
+ return {
82
+ destroy: function destroy() {
83
+ var _api$metrics;
84
+ api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 || (_api$metrics = api.metrics) === null || _api$metrics === void 0 ? void 0 : _api$metrics.commands.fireSessionAnalytics());
85
+ timer.cleanupTimer();
86
+ }
87
+ };
88
+ },
89
+ props: {
90
+ handleDOMEvents: {
91
+ click: function click(view) {
92
+ var _pluginState$lastSele, _pluginState$lastSele2;
93
+ var pluginState = api === null || api === void 0 ? void 0 : api.metrics.sharedState.currentState();
94
+ if (!pluginState || pluginState.intentToStartEditTime) {
95
+ return false;
96
+ }
97
+ var newSelection = view.state.tr.selection;
98
+ var newTr = view.state.tr;
99
+ if ((pluginState === null || pluginState === void 0 || (_pluginState$lastSele = pluginState.lastSelection) === null || _pluginState$lastSele === void 0 ? void 0 : _pluginState$lastSele.from) !== newSelection.from && (pluginState === null || pluginState === void 0 || (_pluginState$lastSele2 = pluginState.lastSelection) === null || _pluginState$lastSele2 === void 0 ? void 0 : _pluginState$lastSele2.to) !== newSelection.to) {
100
+ newTr.setMeta(metricsKey, {
101
+ intentToStartEditTime: performance.now(),
102
+ newSelection: newSelection
103
+ });
104
+ view.dispatch(newTr);
105
+ timer.startTimer();
106
+ }
107
+ return false;
108
+ }
109
+ }
110
+ }
111
+ });
112
+ };
@@ -0,0 +1,27 @@
1
+ import _createClass from "@babel/runtime/helpers/createClass";
2
+ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
+ export var ActiveSessionTimer = /*#__PURE__*/_createClass(function ActiveSessionTimer(api) {
5
+ var _this = this;
6
+ _classCallCheck(this, ActiveSessionTimer);
7
+ _defineProperty(this, "startTimer", function () {
8
+ if (_this.timerId) {
9
+ clearTimeout(_this.timerId);
10
+ }
11
+ _this.timerId = window.setTimeout(function () {
12
+ _this.cleanupTimer();
13
+ }, 3000);
14
+ });
15
+ _defineProperty(this, "cleanupTimer", function () {
16
+ if (_this.api) {
17
+ var _this$api$metrics;
18
+ _this.api.core.actions.execute((_this$api$metrics = _this.api.metrics) === null || _this$api$metrics === void 0 ? void 0 : _this$api$metrics.commands.stopActiveSession());
19
+ }
20
+ if (_this.timerId) {
21
+ clearTimeout(_this.timerId);
22
+ _this.timerId = null;
23
+ }
24
+ });
25
+ this.timerId = null;
26
+ this.api = api;
27
+ });
@@ -0,0 +1,3 @@
1
+ export { metricsPlugin } from './metricsPlugin';
2
+ export type { MetricsPlugin } from './metricsPluginType';
3
+ export type { MetricsState } from './pm-plugins/main';
@@ -0,0 +1,6 @@
1
+ import { type MetricsPlugin } from './metricsPluginType';
2
+ /**
3
+ * Metrics plugin to be added to an `EditorPresetBuilder` and used with `ComposableEditor`
4
+ * from `@atlaskit/editor-core`.
5
+ */
6
+ export declare const metricsPlugin: MetricsPlugin;
@@ -0,0 +1,9 @@
1
+ import type { EditorCommand, NextEditorPlugin } from '@atlaskit/editor-common/types';
2
+ import type { MetricsState } from './pm-plugins/main';
3
+ export type MetricsPlugin = NextEditorPlugin<'metrics', {
4
+ sharedState: MetricsState;
5
+ commands: {
6
+ stopActiveSession: () => EditorCommand;
7
+ fireSessionAnalytics: () => EditorCommand;
8
+ };
9
+ }>;
@@ -0,0 +1,23 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { type ExtractInjectionAPI } from '@atlaskit/editor-common/types';
3
+ import { PluginKey, Selection } from '@atlaskit/editor-prosemirror/state';
4
+ import { type MetricsPlugin } from '../metricsPluginType';
5
+ export declare const metricsKey: PluginKey<any>;
6
+ export type MetricsState = {
7
+ editSessionStartTime?: number;
8
+ intentToStartEditTime?: number;
9
+ activeSessionTime: number;
10
+ totalActionCount: number;
11
+ lastSelection?: Selection;
12
+ actionTypeCount?: ActionByType;
13
+ timeOfLastTextInput?: number;
14
+ };
15
+ export type ActionByType = {
16
+ textInputCount: number;
17
+ nodeInsertionCount: number;
18
+ nodeAttributeChangeCount: number;
19
+ contentMovedCount: number;
20
+ nodeDeletionCount: number;
21
+ };
22
+ export declare const initialPluginState: MetricsState;
23
+ export declare const createPlugin: (api: ExtractInjectionAPI<MetricsPlugin> | undefined) => SafePlugin<MetricsState>;
@@ -0,0 +1,9 @@
1
+ import { type ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
+ import { type MetricsPlugin } from '../../metricsPluginType';
3
+ export declare class ActiveSessionTimer {
4
+ private timerId;
5
+ private api;
6
+ constructor(api: ExtractInjectionAPI<MetricsPlugin> | undefined);
7
+ startTimer: () => void;
8
+ cleanupTimer: () => void;
9
+ }
@@ -0,0 +1,3 @@
1
+ export { metricsPlugin } from './metricsPlugin';
2
+ export type { MetricsPlugin } from './metricsPluginType';
3
+ export type { MetricsState } from './pm-plugins/main';
@@ -0,0 +1,6 @@
1
+ import { type MetricsPlugin } from './metricsPluginType';
2
+ /**
3
+ * Metrics plugin to be added to an `EditorPresetBuilder` and used with `ComposableEditor`
4
+ * from `@atlaskit/editor-core`.
5
+ */
6
+ export declare const metricsPlugin: MetricsPlugin;
@@ -0,0 +1,9 @@
1
+ import type { EditorCommand, NextEditorPlugin } from '@atlaskit/editor-common/types';
2
+ import type { MetricsState } from './pm-plugins/main';
3
+ export type MetricsPlugin = NextEditorPlugin<'metrics', {
4
+ sharedState: MetricsState;
5
+ commands: {
6
+ stopActiveSession: () => EditorCommand;
7
+ fireSessionAnalytics: () => EditorCommand;
8
+ };
9
+ }>;
@@ -0,0 +1,23 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { type ExtractInjectionAPI } from '@atlaskit/editor-common/types';
3
+ import { PluginKey, Selection } from '@atlaskit/editor-prosemirror/state';
4
+ import { type MetricsPlugin } from '../metricsPluginType';
5
+ export declare const metricsKey: PluginKey<any>;
6
+ export type MetricsState = {
7
+ editSessionStartTime?: number;
8
+ intentToStartEditTime?: number;
9
+ activeSessionTime: number;
10
+ totalActionCount: number;
11
+ lastSelection?: Selection;
12
+ actionTypeCount?: ActionByType;
13
+ timeOfLastTextInput?: number;
14
+ };
15
+ export type ActionByType = {
16
+ textInputCount: number;
17
+ nodeInsertionCount: number;
18
+ nodeAttributeChangeCount: number;
19
+ contentMovedCount: number;
20
+ nodeDeletionCount: number;
21
+ };
22
+ export declare const initialPluginState: MetricsState;
23
+ export declare const createPlugin: (api: ExtractInjectionAPI<MetricsPlugin> | undefined) => SafePlugin<MetricsState>;
@@ -0,0 +1,9 @@
1
+ import { type ExtractInjectionAPI } from '@atlaskit/editor-common/types';
2
+ import { type MetricsPlugin } from '../../metricsPluginType';
3
+ export declare class ActiveSessionTimer {
4
+ private timerId;
5
+ private api;
6
+ constructor(api: ExtractInjectionAPI<MetricsPlugin> | undefined);
7
+ startTimer: () => void;
8
+ cleanupTimer: () => void;
9
+ }
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@atlaskit/editor-plugin-metrics",
3
+ "version": "1.1.0",
4
+ "description": "Metrics plugin for @atlaskit/editor-core",
5
+ "author": "Atlassian Pty Ltd",
6
+ "license": "Apache-2.0",
7
+ "publishConfig": {
8
+ "registry": "https://registry.npmjs.org/"
9
+ },
10
+ "atlassian": {
11
+ "team": "Editor: Jenga",
12
+ "singleton": true,
13
+ "releaseModel": "continuous",
14
+ "runReact18": true
15
+ },
16
+ "repository": "https://bitbucket.org/atlassian/atlassian-frontend",
17
+ "main": "dist/cjs/index.js",
18
+ "module": "dist/esm/index.js",
19
+ "module:es2019": "dist/es2019/index.js",
20
+ "types": "dist/types/index.d.ts",
21
+ "sideEffects": false,
22
+ "atlaskit:src": "src/index.ts",
23
+ "af:exports": {
24
+ ".": "./src/index.ts"
25
+ },
26
+ "dependencies": {
27
+ "@atlaskit/adf-schema": "^46.1.0",
28
+ "@atlaskit/editor-common": "^99.1.1",
29
+ "@atlaskit/editor-prosemirror": "6.2.1",
30
+ "@atlaskit/platform-feature-flags": "^0.3.0",
31
+ "@babel/runtime": "^7.0.0"
32
+ },
33
+ "peerDependencies": {
34
+ "react": "^16.8.0"
35
+ },
36
+ "devDependencies": {
37
+ "@testing-library/react": "^12.1.5",
38
+ "react-dom": "^16.8.0",
39
+ "typescript": "~5.4.2"
40
+ },
41
+ "techstack": {
42
+ "@atlassian/frontend": {
43
+ "import-structure": [
44
+ "atlassian-conventions"
45
+ ],
46
+ "circular-dependencies": [
47
+ "file-and-folder-level"
48
+ ],
49
+ "code-structure": [
50
+ "editor-plugin"
51
+ ]
52
+ },
53
+ "@repo/internal": {
54
+ "dom-events": "use-bind-event-listener",
55
+ "analytics": [
56
+ "analytics-next"
57
+ ],
58
+ "design-tokens": [
59
+ "color"
60
+ ],
61
+ "theming": [
62
+ "react-context"
63
+ ],
64
+ "ui-components": [
65
+ "lite-mode"
66
+ ],
67
+ "deprecation": [
68
+ "no-deprecated-imports"
69
+ ],
70
+ "styling": [
71
+ "static"
72
+ ]
73
+ }
74
+ }
75
+ }