@atlaskit/tmp-editor-statsig 1.0.0 → 1.2.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 +14 -1
- package/dist/cjs/experiments-config.js +56 -0
- package/dist/cjs/experiments.js +123 -0
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/test-runner.js +83 -0
- package/dist/es2019/experiments-config.js +50 -0
- package/dist/es2019/experiments.js +110 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/test-runner.js +65 -0
- package/dist/esm/experiments-config.js +50 -0
- package/dist/esm/experiments.js +115 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/test-runner.js +77 -0
- package/dist/types/experiments-config.d.ts +43 -0
- package/dist/types/experiments.d.ts +66 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/test-runner.d.ts +41 -0
- package/dist/types-ts4.5/experiments-config.d.ts +43 -0
- package/dist/types-ts4.5/experiments.d.ts +66 -0
- package/dist/types-ts4.5/index.d.ts +1 -0
- package/dist/types-ts4.5/test-runner.d.ts +41 -0
- package/experiments/package.json +14 -0
- package/package.json +9 -9
- package/test-runner/package.json +14 -0
- package/dist/cjs/feature-gate-js-client.js +0 -21
- package/dist/cjs/feature-gates-react.js +0 -110
- package/dist/es2019/feature-gate-js-client.js +0 -14
- package/dist/es2019/feature-gates-react.js +0 -84
- package/dist/esm/feature-gate-js-client.js +0 -14
- package/dist/esm/feature-gates-react.js +0 -101
- package/dist/types/feature-gate-js-client.d.ts +0 -12
- package/dist/types/feature-gates-react.d.ts +0 -19
- package/dist/types-ts4.5/feature-gate-js-client.d.ts +0 -12
- package/dist/types-ts4.5/feature-gates-react.d.ts +0 -19
- package/feature-gate-js-client/package.json +0 -14
- package/feature-gates-react/package.json +0 -14
package/CHANGELOG.md
CHANGED
|
@@ -1 +1,14 @@
|
|
|
1
|
-
# @atlaskit/editor-statsig-tmp
|
|
1
|
+
# @atlaskit/editor-statsig-tmp
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#131878](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/131878)
|
|
8
|
+
[`705fe39cae267`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/705fe39cae267) -
|
|
9
|
+
[ED-24597] Update to log `platform_editor_basic_text_transformations` exposure event only for
|
|
10
|
+
users meet all of 3 checks:
|
|
11
|
+
|
|
12
|
+
- Are enrolled to the experiment
|
|
13
|
+
- Have AI disabled
|
|
14
|
+
- Make top level text selection
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.editorExperimentsConfig = void 0;
|
|
7
|
+
function isBoolean(value) {
|
|
8
|
+
return typeof value === 'boolean';
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* When adding a new experiment, you need to add it here.
|
|
12
|
+
* Please follow the pattern established in the examples and any
|
|
13
|
+
* existing experiments.
|
|
14
|
+
*/
|
|
15
|
+
var editorExperimentsConfig = exports.editorExperimentsConfig = {
|
|
16
|
+
// Added 2024-08-08
|
|
17
|
+
'example-boolean': {
|
|
18
|
+
productKeys: {
|
|
19
|
+
confluence: 'confluence_editor_experiment_test_new_package_boolean'
|
|
20
|
+
},
|
|
21
|
+
param: 'isEnabled',
|
|
22
|
+
typeGuard: isBoolean,
|
|
23
|
+
// Note -- you need to set the type to boolean for the default value
|
|
24
|
+
defaultValue: false
|
|
25
|
+
},
|
|
26
|
+
// Added 2024-08-08
|
|
27
|
+
'example-multivariate': {
|
|
28
|
+
productKeys: {
|
|
29
|
+
confluence: 'confluence_editor_experiment_test_new_package_multivariate'
|
|
30
|
+
},
|
|
31
|
+
param: 'variant',
|
|
32
|
+
typeGuard: isBoolean,
|
|
33
|
+
// Note -- you need to specify the type of the default value as the union of all possible values
|
|
34
|
+
// This is used to provide type safety on consumption
|
|
35
|
+
defaultValue: 'one'
|
|
36
|
+
},
|
|
37
|
+
// Added 2024-08-08
|
|
38
|
+
'test-new-experiments-package': {
|
|
39
|
+
productKeys: {
|
|
40
|
+
confluence: 'confluence_editor_experiment_test_new_package',
|
|
41
|
+
jira: 'jira_editor_experiment_test_new_package'
|
|
42
|
+
},
|
|
43
|
+
param: 'isEnabled',
|
|
44
|
+
typeGuard: isBoolean,
|
|
45
|
+
defaultValue: false
|
|
46
|
+
},
|
|
47
|
+
// Add 2024-08-14
|
|
48
|
+
'basic-text-transformations': {
|
|
49
|
+
productKeys: {
|
|
50
|
+
confluence: 'platform_editor_basic_text_transformations'
|
|
51
|
+
},
|
|
52
|
+
param: 'isEnabled',
|
|
53
|
+
typeGuard: isBoolean,
|
|
54
|
+
defaultValue: false
|
|
55
|
+
}
|
|
56
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.editorExperiment = editorExperiment;
|
|
8
|
+
exports.setupEditorExperiments = setupEditorExperiments;
|
|
9
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
11
|
+
var _featureGateJsClient = _interopRequireDefault(require("@atlaskit/feature-gate-js-client"));
|
|
12
|
+
var _experimentsConfig = require("./experiments-config");
|
|
13
|
+
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; }
|
|
14
|
+
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; }
|
|
15
|
+
var _overrides = {};
|
|
16
|
+
var _product;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* This function is used to set up the editor experiments for testing purposes.
|
|
20
|
+
* It should be called before running code that depends on editor experiments.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* setupEditorExperiments('confluence', {
|
|
25
|
+
* 'experiment-name': 'value',
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function setupEditorExperiments(product,
|
|
30
|
+
/**
|
|
31
|
+
* Overrides are used to set the value of an experiment for testing purposes.
|
|
32
|
+
* This is useful when you want to test a specific value of an experiment.
|
|
33
|
+
*/
|
|
34
|
+
overrides) {
|
|
35
|
+
if (overrides) {
|
|
36
|
+
// When setting up overrides, we want to ensure that experiments don't end up with invalid
|
|
37
|
+
// values.
|
|
38
|
+
// For production usage -- this is done via the feature flag client which takes the type
|
|
39
|
+
// and performs equivalent logic.
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
overrides = Object.entries(overrides).reduce(function (acc, _ref) {
|
|
42
|
+
var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
|
|
43
|
+
key = _ref2[0],
|
|
44
|
+
value = _ref2[1];
|
|
45
|
+
var config = _experimentsConfig.editorExperimentsConfig[key];
|
|
46
|
+
acc = _objectSpread(_objectSpread({}, acc), {}, (0, _defineProperty2.default)({}, key, config.typeGuard(value) ? value : config.defaultValue));
|
|
47
|
+
return acc;
|
|
48
|
+
}, {});
|
|
49
|
+
_overrides = overrides;
|
|
50
|
+
}
|
|
51
|
+
_product = product;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check the value of an editor experiment.
|
|
56
|
+
*
|
|
57
|
+
* Note: By default this will not fire an [exposure event](https://hello.atlassian.net/wiki/spaces/~732385844/pages/3187295823/Exposure+Events+101).
|
|
58
|
+
*
|
|
59
|
+
* You need explicitly call it using the exposure property when you need an exposure event to be fired (all experiments should fire exposure events).
|
|
60
|
+
*
|
|
61
|
+
* @example Boolean experiment
|
|
62
|
+
* ```ts
|
|
63
|
+
* if (editorExperiment('example-boolean', true)) {
|
|
64
|
+
* // Run code for on variant
|
|
65
|
+
* } else {
|
|
66
|
+
* // Run code for off variant
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @example Multivariate experiment
|
|
71
|
+
* ```ts
|
|
72
|
+
* switch (true) {
|
|
73
|
+
* case editorExperiment('example-multivariate', 'one'):
|
|
74
|
+
* // Run code for variant one
|
|
75
|
+
* break;
|
|
76
|
+
* case editorExperiment('example-multivariate', 'two'):
|
|
77
|
+
* // Run code for variant two
|
|
78
|
+
* break;
|
|
79
|
+
* case editorExperiment('example-multivariate', 'three'):
|
|
80
|
+
* // Run code for variant three
|
|
81
|
+
* break;
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
*```
|
|
85
|
+
|
|
86
|
+
@example Experiment with exposure event
|
|
87
|
+
* ```ts
|
|
88
|
+
* // Inside feature surface where either the control or variant should be shown
|
|
89
|
+
* if (editorExperiment('example-boolean', true, { exposure: true })) {
|
|
90
|
+
* // Run code for on variant
|
|
91
|
+
* } else {
|
|
92
|
+
* // Run code for off variant
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
function editorExperiment(experimentName, expectedExperimentValue) {
|
|
97
|
+
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
|
|
98
|
+
exposure: false
|
|
99
|
+
};
|
|
100
|
+
var experimentConfig = _experimentsConfig.editorExperimentsConfig[experimentName];
|
|
101
|
+
if (_overrides[experimentName] !== undefined) {
|
|
102
|
+
// This will be hit in the case of a test setting an override
|
|
103
|
+
return _overrides[experimentName] === expectedExperimentValue;
|
|
104
|
+
}
|
|
105
|
+
if (!_product) {
|
|
106
|
+
// This will be hit in the case of a product not having setup the editor experiment tooling
|
|
107
|
+
return experimentConfig.defaultValue === expectedExperimentValue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Typescript is complaining here about accessing the productKeys property
|
|
111
|
+
var experimentKey = experimentConfig.productKeys[_product];
|
|
112
|
+
if (!experimentKey) {
|
|
113
|
+
// This will be hit in the case of an experiment not being set up for the product
|
|
114
|
+
return _experimentsConfig.editorExperimentsConfig[experimentName].defaultValue === expectedExperimentValue;
|
|
115
|
+
}
|
|
116
|
+
var experimentValue = _featureGateJsClient.default.getExperimentValue(
|
|
117
|
+
// @ts-ignore
|
|
118
|
+
experimentKey, experimentConfig.param, experimentConfig.defaultValue, {
|
|
119
|
+
typeGuard: experimentConfig.typeGuard,
|
|
120
|
+
fireExperimentExposure: options.exposure
|
|
121
|
+
});
|
|
122
|
+
return expectedExperimentValue === experimentValue;
|
|
123
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.eeTest = eeTest;
|
|
8
|
+
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
9
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
|
+
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
11
|
+
var _experiments = require("./experiments");
|
|
12
|
+
var _experimentsConfig = require("./experiments-config");
|
|
13
|
+
// This is loosely based on the `ffTest` util from `@atlassian/feature-flags-test-utils` package.
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* This is a utility function for testing editor experiments.
|
|
17
|
+
*
|
|
18
|
+
* @example Boolean experiment
|
|
19
|
+
* ```ts
|
|
20
|
+
* eeTest('example-boolean', {
|
|
21
|
+
* true: () => {
|
|
22
|
+
* expect(editorExperiment('example-boolean', true)).toBe(true);
|
|
23
|
+
* expect(editorExperiment('example-boolean', false)).toBe(false);
|
|
24
|
+
* },
|
|
25
|
+
* false: () => {
|
|
26
|
+
* expect(editorExperiment('example-boolean', false)).toBe(true);
|
|
27
|
+
* expect(editorExperiment('example-boolean', true)).toBe(false);
|
|
28
|
+
* },
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Multivariate experiment
|
|
33
|
+
* ```ts
|
|
34
|
+
* eeTest('example-multivariate', {
|
|
35
|
+
* one: () => {
|
|
36
|
+
* expect(editorExperiment('example-multivariate', 'one')).toBe(true);
|
|
37
|
+
* expect(editorExperiment('example-multivariate', 'two')).toBe(false);
|
|
38
|
+
* },
|
|
39
|
+
* two: () => {
|
|
40
|
+
* expect(editorExperiment('example-multivariate', 'two')).toBe(true);
|
|
41
|
+
* expect(editorExperiment('example-multivariate', 'one')).toBe(false);
|
|
42
|
+
* },
|
|
43
|
+
* three: () => {
|
|
44
|
+
* expect(editorExperiment('example-multivariate', 'three')).toBe(true);
|
|
45
|
+
* expect(editorExperiment('example-multivariate', 'one')).toBe(false);
|
|
46
|
+
* },
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function eeTest(experimentName, cases, otherExperiments) {
|
|
51
|
+
(0, _experiments.setupEditorExperiments)('test', {});
|
|
52
|
+
describe("eeTest: ".concat(experimentName), function () {
|
|
53
|
+
afterEach(function () {
|
|
54
|
+
(0, _experiments.setupEditorExperiments)('test', otherExperiments !== null && otherExperiments !== void 0 ? otherExperiments : {});
|
|
55
|
+
});
|
|
56
|
+
var isBooleanExperiment = typeof _experimentsConfig.editorExperimentsConfig[experimentName].defaultValue === 'boolean';
|
|
57
|
+
if (isBooleanExperiment && Object.keys(cases).length !== 2) {
|
|
58
|
+
throw new Error("Expected exactly 2 cases for boolean experiment ".concat(experimentName, ", got ").concat(Object.keys(cases).length));
|
|
59
|
+
}
|
|
60
|
+
test.each(Object.keys(cases))("".concat(experimentName, ": %s"), /*#__PURE__*/function () {
|
|
61
|
+
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(value) {
|
|
62
|
+
var testCaseKey, convertedValue, testCase;
|
|
63
|
+
return _regenerator.default.wrap(function _callee$(_context) {
|
|
64
|
+
while (1) switch (_context.prev = _context.next) {
|
|
65
|
+
case 0:
|
|
66
|
+
testCaseKey = value; // For boolean experiments, we need to convert the 'on' and 'off' cases to boolean `true` and `false` values.
|
|
67
|
+
convertedValue = isBooleanExperiment ? testCaseKey === 'true' ? true : false : testCaseKey;
|
|
68
|
+
(0, _experiments.setupEditorExperiments)('test', (0, _defineProperty2.default)({}, experimentName, convertedValue));
|
|
69
|
+
testCase = cases[testCaseKey]; // @ts-ignore
|
|
70
|
+
_context.next = 6;
|
|
71
|
+
return Promise.resolve(testCase());
|
|
72
|
+
case 6:
|
|
73
|
+
case "end":
|
|
74
|
+
return _context.stop();
|
|
75
|
+
}
|
|
76
|
+
}, _callee);
|
|
77
|
+
}));
|
|
78
|
+
return function (_x) {
|
|
79
|
+
return _ref.apply(this, arguments);
|
|
80
|
+
};
|
|
81
|
+
}());
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function isBoolean(value) {
|
|
2
|
+
return typeof value === 'boolean';
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* When adding a new experiment, you need to add it here.
|
|
6
|
+
* Please follow the pattern established in the examples and any
|
|
7
|
+
* existing experiments.
|
|
8
|
+
*/
|
|
9
|
+
export const editorExperimentsConfig = {
|
|
10
|
+
// Added 2024-08-08
|
|
11
|
+
'example-boolean': {
|
|
12
|
+
productKeys: {
|
|
13
|
+
confluence: 'confluence_editor_experiment_test_new_package_boolean'
|
|
14
|
+
},
|
|
15
|
+
param: 'isEnabled',
|
|
16
|
+
typeGuard: isBoolean,
|
|
17
|
+
// Note -- you need to set the type to boolean for the default value
|
|
18
|
+
defaultValue: false
|
|
19
|
+
},
|
|
20
|
+
// Added 2024-08-08
|
|
21
|
+
'example-multivariate': {
|
|
22
|
+
productKeys: {
|
|
23
|
+
confluence: 'confluence_editor_experiment_test_new_package_multivariate'
|
|
24
|
+
},
|
|
25
|
+
param: 'variant',
|
|
26
|
+
typeGuard: isBoolean,
|
|
27
|
+
// Note -- you need to specify the type of the default value as the union of all possible values
|
|
28
|
+
// This is used to provide type safety on consumption
|
|
29
|
+
defaultValue: 'one'
|
|
30
|
+
},
|
|
31
|
+
// Added 2024-08-08
|
|
32
|
+
'test-new-experiments-package': {
|
|
33
|
+
productKeys: {
|
|
34
|
+
confluence: 'confluence_editor_experiment_test_new_package',
|
|
35
|
+
jira: 'jira_editor_experiment_test_new_package'
|
|
36
|
+
},
|
|
37
|
+
param: 'isEnabled',
|
|
38
|
+
typeGuard: isBoolean,
|
|
39
|
+
defaultValue: false
|
|
40
|
+
},
|
|
41
|
+
// Add 2024-08-14
|
|
42
|
+
'basic-text-transformations': {
|
|
43
|
+
productKeys: {
|
|
44
|
+
confluence: 'platform_editor_basic_text_transformations'
|
|
45
|
+
},
|
|
46
|
+
param: 'isEnabled',
|
|
47
|
+
typeGuard: isBoolean,
|
|
48
|
+
defaultValue: false
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import FeatureGates from '@atlaskit/feature-gate-js-client';
|
|
2
|
+
import { editorExperimentsConfig } from './experiments-config';
|
|
3
|
+
let _overrides = {};
|
|
4
|
+
let _product;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This function is used to set up the editor experiments for testing purposes.
|
|
8
|
+
* It should be called before running code that depends on editor experiments.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* setupEditorExperiments('confluence', {
|
|
13
|
+
* 'experiment-name': 'value',
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function setupEditorExperiments(product,
|
|
18
|
+
/**
|
|
19
|
+
* Overrides are used to set the value of an experiment for testing purposes.
|
|
20
|
+
* This is useful when you want to test a specific value of an experiment.
|
|
21
|
+
*/
|
|
22
|
+
overrides) {
|
|
23
|
+
if (overrides) {
|
|
24
|
+
// When setting up overrides, we want to ensure that experiments don't end up with invalid
|
|
25
|
+
// values.
|
|
26
|
+
// For production usage -- this is done via the feature flag client which takes the type
|
|
27
|
+
// and performs equivalent logic.
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
overrides = Object.entries(overrides).reduce((acc, [key, value]) => {
|
|
30
|
+
const config = editorExperimentsConfig[key];
|
|
31
|
+
acc = {
|
|
32
|
+
...acc,
|
|
33
|
+
[key]: config.typeGuard(value) ? value : config.defaultValue
|
|
34
|
+
};
|
|
35
|
+
return acc;
|
|
36
|
+
}, {});
|
|
37
|
+
_overrides = overrides;
|
|
38
|
+
}
|
|
39
|
+
_product = product;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check the value of an editor experiment.
|
|
44
|
+
*
|
|
45
|
+
* Note: By default this will not fire an [exposure event](https://hello.atlassian.net/wiki/spaces/~732385844/pages/3187295823/Exposure+Events+101).
|
|
46
|
+
*
|
|
47
|
+
* You need explicitly call it using the exposure property when you need an exposure event to be fired (all experiments should fire exposure events).
|
|
48
|
+
*
|
|
49
|
+
* @example Boolean experiment
|
|
50
|
+
* ```ts
|
|
51
|
+
* if (editorExperiment('example-boolean', true)) {
|
|
52
|
+
* // Run code for on variant
|
|
53
|
+
* } else {
|
|
54
|
+
* // Run code for off variant
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example Multivariate experiment
|
|
59
|
+
* ```ts
|
|
60
|
+
* switch (true) {
|
|
61
|
+
* case editorExperiment('example-multivariate', 'one'):
|
|
62
|
+
* // Run code for variant one
|
|
63
|
+
* break;
|
|
64
|
+
* case editorExperiment('example-multivariate', 'two'):
|
|
65
|
+
* // Run code for variant two
|
|
66
|
+
* break;
|
|
67
|
+
* case editorExperiment('example-multivariate', 'three'):
|
|
68
|
+
* // Run code for variant three
|
|
69
|
+
* break;
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
*```
|
|
73
|
+
|
|
74
|
+
@example Experiment with exposure event
|
|
75
|
+
* ```ts
|
|
76
|
+
* // Inside feature surface where either the control or variant should be shown
|
|
77
|
+
* if (editorExperiment('example-boolean', true, { exposure: true })) {
|
|
78
|
+
* // Run code for on variant
|
|
79
|
+
* } else {
|
|
80
|
+
* // Run code for off variant
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function editorExperiment(experimentName, expectedExperimentValue, options = {
|
|
85
|
+
exposure: false
|
|
86
|
+
}) {
|
|
87
|
+
const experimentConfig = editorExperimentsConfig[experimentName];
|
|
88
|
+
if (_overrides[experimentName] !== undefined) {
|
|
89
|
+
// This will be hit in the case of a test setting an override
|
|
90
|
+
return _overrides[experimentName] === expectedExperimentValue;
|
|
91
|
+
}
|
|
92
|
+
if (!_product) {
|
|
93
|
+
// This will be hit in the case of a product not having setup the editor experiment tooling
|
|
94
|
+
return experimentConfig.defaultValue === expectedExperimentValue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Typescript is complaining here about accessing the productKeys property
|
|
98
|
+
const experimentKey = experimentConfig.productKeys[_product];
|
|
99
|
+
if (!experimentKey) {
|
|
100
|
+
// This will be hit in the case of an experiment not being set up for the product
|
|
101
|
+
return editorExperimentsConfig[experimentName].defaultValue === expectedExperimentValue;
|
|
102
|
+
}
|
|
103
|
+
const experimentValue = FeatureGates.getExperimentValue(
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
experimentKey, experimentConfig.param, experimentConfig.defaultValue, {
|
|
106
|
+
typeGuard: experimentConfig.typeGuard,
|
|
107
|
+
fireExperimentExposure: options.exposure
|
|
108
|
+
});
|
|
109
|
+
return expectedExperimentValue === experimentValue;
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// This is loosely based on the `ffTest` util from `@atlassian/feature-flags-test-utils` package.
|
|
2
|
+
|
|
3
|
+
import { setupEditorExperiments } from './experiments';
|
|
4
|
+
import { editorExperimentsConfig } from './experiments-config';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This is a utility function for testing editor experiments.
|
|
8
|
+
*
|
|
9
|
+
* @example Boolean experiment
|
|
10
|
+
* ```ts
|
|
11
|
+
* eeTest('example-boolean', {
|
|
12
|
+
* true: () => {
|
|
13
|
+
* expect(editorExperiment('example-boolean', true)).toBe(true);
|
|
14
|
+
* expect(editorExperiment('example-boolean', false)).toBe(false);
|
|
15
|
+
* },
|
|
16
|
+
* false: () => {
|
|
17
|
+
* expect(editorExperiment('example-boolean', false)).toBe(true);
|
|
18
|
+
* expect(editorExperiment('example-boolean', true)).toBe(false);
|
|
19
|
+
* },
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @example Multivariate experiment
|
|
24
|
+
* ```ts
|
|
25
|
+
* eeTest('example-multivariate', {
|
|
26
|
+
* one: () => {
|
|
27
|
+
* expect(editorExperiment('example-multivariate', 'one')).toBe(true);
|
|
28
|
+
* expect(editorExperiment('example-multivariate', 'two')).toBe(false);
|
|
29
|
+
* },
|
|
30
|
+
* two: () => {
|
|
31
|
+
* expect(editorExperiment('example-multivariate', 'two')).toBe(true);
|
|
32
|
+
* expect(editorExperiment('example-multivariate', 'one')).toBe(false);
|
|
33
|
+
* },
|
|
34
|
+
* three: () => {
|
|
35
|
+
* expect(editorExperiment('example-multivariate', 'three')).toBe(true);
|
|
36
|
+
* expect(editorExperiment('example-multivariate', 'one')).toBe(false);
|
|
37
|
+
* },
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function eeTest(experimentName, cases, otherExperiments) {
|
|
42
|
+
setupEditorExperiments('test', {});
|
|
43
|
+
describe(`eeTest: ${experimentName}`, () => {
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
setupEditorExperiments('test', otherExperiments !== null && otherExperiments !== void 0 ? otherExperiments : {});
|
|
46
|
+
});
|
|
47
|
+
const isBooleanExperiment = typeof editorExperimentsConfig[experimentName].defaultValue === 'boolean';
|
|
48
|
+
if (isBooleanExperiment && Object.keys(cases).length !== 2) {
|
|
49
|
+
throw new Error(`Expected exactly 2 cases for boolean experiment ${experimentName}, got ${Object.keys(cases).length}`);
|
|
50
|
+
}
|
|
51
|
+
test.each(Object.keys(cases))(`${experimentName}: %s`, async value => {
|
|
52
|
+
const testCaseKey = value;
|
|
53
|
+
|
|
54
|
+
// For boolean experiments, we need to convert the 'on' and 'off' cases to boolean `true` and `false` values.
|
|
55
|
+
const convertedValue = isBooleanExperiment ? testCaseKey === 'true' ? true : false : testCaseKey;
|
|
56
|
+
setupEditorExperiments('test', {
|
|
57
|
+
[experimentName]: convertedValue
|
|
58
|
+
});
|
|
59
|
+
const testCase = cases[testCaseKey];
|
|
60
|
+
|
|
61
|
+
// @ts-ignore
|
|
62
|
+
await Promise.resolve(testCase());
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function isBoolean(value) {
|
|
2
|
+
return typeof value === 'boolean';
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* When adding a new experiment, you need to add it here.
|
|
6
|
+
* Please follow the pattern established in the examples and any
|
|
7
|
+
* existing experiments.
|
|
8
|
+
*/
|
|
9
|
+
export var editorExperimentsConfig = {
|
|
10
|
+
// Added 2024-08-08
|
|
11
|
+
'example-boolean': {
|
|
12
|
+
productKeys: {
|
|
13
|
+
confluence: 'confluence_editor_experiment_test_new_package_boolean'
|
|
14
|
+
},
|
|
15
|
+
param: 'isEnabled',
|
|
16
|
+
typeGuard: isBoolean,
|
|
17
|
+
// Note -- you need to set the type to boolean for the default value
|
|
18
|
+
defaultValue: false
|
|
19
|
+
},
|
|
20
|
+
// Added 2024-08-08
|
|
21
|
+
'example-multivariate': {
|
|
22
|
+
productKeys: {
|
|
23
|
+
confluence: 'confluence_editor_experiment_test_new_package_multivariate'
|
|
24
|
+
},
|
|
25
|
+
param: 'variant',
|
|
26
|
+
typeGuard: isBoolean,
|
|
27
|
+
// Note -- you need to specify the type of the default value as the union of all possible values
|
|
28
|
+
// This is used to provide type safety on consumption
|
|
29
|
+
defaultValue: 'one'
|
|
30
|
+
},
|
|
31
|
+
// Added 2024-08-08
|
|
32
|
+
'test-new-experiments-package': {
|
|
33
|
+
productKeys: {
|
|
34
|
+
confluence: 'confluence_editor_experiment_test_new_package',
|
|
35
|
+
jira: 'jira_editor_experiment_test_new_package'
|
|
36
|
+
},
|
|
37
|
+
param: 'isEnabled',
|
|
38
|
+
typeGuard: isBoolean,
|
|
39
|
+
defaultValue: false
|
|
40
|
+
},
|
|
41
|
+
// Add 2024-08-14
|
|
42
|
+
'basic-text-transformations': {
|
|
43
|
+
productKeys: {
|
|
44
|
+
confluence: 'platform_editor_basic_text_transformations'
|
|
45
|
+
},
|
|
46
|
+
param: 'isEnabled',
|
|
47
|
+
typeGuard: isBoolean,
|
|
48
|
+
defaultValue: false
|
|
49
|
+
}
|
|
50
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
|
+
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; }
|
|
4
|
+
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; }
|
|
5
|
+
import FeatureGates from '@atlaskit/feature-gate-js-client';
|
|
6
|
+
import { editorExperimentsConfig } from './experiments-config';
|
|
7
|
+
var _overrides = {};
|
|
8
|
+
var _product;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* This function is used to set up the editor experiments for testing purposes.
|
|
12
|
+
* It should be called before running code that depends on editor experiments.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* setupEditorExperiments('confluence', {
|
|
17
|
+
* 'experiment-name': 'value',
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function setupEditorExperiments(product,
|
|
22
|
+
/**
|
|
23
|
+
* Overrides are used to set the value of an experiment for testing purposes.
|
|
24
|
+
* This is useful when you want to test a specific value of an experiment.
|
|
25
|
+
*/
|
|
26
|
+
overrides) {
|
|
27
|
+
if (overrides) {
|
|
28
|
+
// When setting up overrides, we want to ensure that experiments don't end up with invalid
|
|
29
|
+
// values.
|
|
30
|
+
// For production usage -- this is done via the feature flag client which takes the type
|
|
31
|
+
// and performs equivalent logic.
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
overrides = Object.entries(overrides).reduce(function (acc, _ref) {
|
|
34
|
+
var _ref2 = _slicedToArray(_ref, 2),
|
|
35
|
+
key = _ref2[0],
|
|
36
|
+
value = _ref2[1];
|
|
37
|
+
var config = editorExperimentsConfig[key];
|
|
38
|
+
acc = _objectSpread(_objectSpread({}, acc), {}, _defineProperty({}, key, config.typeGuard(value) ? value : config.defaultValue));
|
|
39
|
+
return acc;
|
|
40
|
+
}, {});
|
|
41
|
+
_overrides = overrides;
|
|
42
|
+
}
|
|
43
|
+
_product = product;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check the value of an editor experiment.
|
|
48
|
+
*
|
|
49
|
+
* Note: By default this will not fire an [exposure event](https://hello.atlassian.net/wiki/spaces/~732385844/pages/3187295823/Exposure+Events+101).
|
|
50
|
+
*
|
|
51
|
+
* You need explicitly call it using the exposure property when you need an exposure event to be fired (all experiments should fire exposure events).
|
|
52
|
+
*
|
|
53
|
+
* @example Boolean experiment
|
|
54
|
+
* ```ts
|
|
55
|
+
* if (editorExperiment('example-boolean', true)) {
|
|
56
|
+
* // Run code for on variant
|
|
57
|
+
* } else {
|
|
58
|
+
* // Run code for off variant
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @example Multivariate experiment
|
|
63
|
+
* ```ts
|
|
64
|
+
* switch (true) {
|
|
65
|
+
* case editorExperiment('example-multivariate', 'one'):
|
|
66
|
+
* // Run code for variant one
|
|
67
|
+
* break;
|
|
68
|
+
* case editorExperiment('example-multivariate', 'two'):
|
|
69
|
+
* // Run code for variant two
|
|
70
|
+
* break;
|
|
71
|
+
* case editorExperiment('example-multivariate', 'three'):
|
|
72
|
+
* // Run code for variant three
|
|
73
|
+
* break;
|
|
74
|
+
* }
|
|
75
|
+
* }
|
|
76
|
+
*```
|
|
77
|
+
|
|
78
|
+
@example Experiment with exposure event
|
|
79
|
+
* ```ts
|
|
80
|
+
* // Inside feature surface where either the control or variant should be shown
|
|
81
|
+
* if (editorExperiment('example-boolean', true, { exposure: true })) {
|
|
82
|
+
* // Run code for on variant
|
|
83
|
+
* } else {
|
|
84
|
+
* // Run code for off variant
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function editorExperiment(experimentName, expectedExperimentValue) {
|
|
89
|
+
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
|
|
90
|
+
exposure: false
|
|
91
|
+
};
|
|
92
|
+
var experimentConfig = editorExperimentsConfig[experimentName];
|
|
93
|
+
if (_overrides[experimentName] !== undefined) {
|
|
94
|
+
// This will be hit in the case of a test setting an override
|
|
95
|
+
return _overrides[experimentName] === expectedExperimentValue;
|
|
96
|
+
}
|
|
97
|
+
if (!_product) {
|
|
98
|
+
// This will be hit in the case of a product not having setup the editor experiment tooling
|
|
99
|
+
return experimentConfig.defaultValue === expectedExperimentValue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Typescript is complaining here about accessing the productKeys property
|
|
103
|
+
var experimentKey = experimentConfig.productKeys[_product];
|
|
104
|
+
if (!experimentKey) {
|
|
105
|
+
// This will be hit in the case of an experiment not being set up for the product
|
|
106
|
+
return editorExperimentsConfig[experimentName].defaultValue === expectedExperimentValue;
|
|
107
|
+
}
|
|
108
|
+
var experimentValue = FeatureGates.getExperimentValue(
|
|
109
|
+
// @ts-ignore
|
|
110
|
+
experimentKey, experimentConfig.param, experimentConfig.defaultValue, {
|
|
111
|
+
typeGuard: experimentConfig.typeGuard,
|
|
112
|
+
fireExperimentExposure: options.exposure
|
|
113
|
+
});
|
|
114
|
+
return expectedExperimentValue === experimentValue;
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|