@becollective/utils 1.5.5 → 1.7.1
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/bundle.js +20825 -5
- package/index.js +8 -2
- package/jestconfig.json +11 -0
- package/package.json +18 -13
- package/rollup.config.js +6 -0
- package/setup.ts +7 -0
- package/src/FeatureFlag.ts +50 -0
- package/tests/featureFlag.test.ts +102 -0
- package/tsconfig.json +17 -0
- package/tslint.json +100 -0
- package/testUtils/setup.js +0 -21
- /package/{src → tests}/date-time.test.js +0 -0
- /package/{src → tests}/money.test.js +0 -0
- /package/{src → tests}/password.test.js +0 -0
package/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { password } from './src/password';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isUnderSixteen,
|
|
4
|
+
getAge,
|
|
5
|
+
datesByThemselves,
|
|
6
|
+
getShiftText,
|
|
7
|
+
} from './src/date-time';
|
|
3
8
|
import { getCurrencyFromCurrencyCode, makeMoneyString } from './src/money';
|
|
4
9
|
import { readableOpportunityType } from './src/opportunity';
|
|
5
10
|
import { getTimeInfo } from './src/opportunityUser';
|
|
6
11
|
import { getHomeLocalityFromLocationList } from './src/locality';
|
|
7
|
-
|
|
12
|
+
import FeatureFlag from './src/FeatureFlag';
|
|
8
13
|
const util = {
|
|
9
14
|
getAge,
|
|
10
15
|
getCurrencyFromCurrencyCode,
|
|
@@ -18,6 +23,7 @@ const util = {
|
|
|
18
23
|
getTimeInfo,
|
|
19
24
|
getHomeLocalityFromLocationList,
|
|
20
25
|
getShiftText,
|
|
26
|
+
FeatureFlag,
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
module.exports = util;
|
package/jestconfig.json
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@becollective/utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Common utilities",
|
|
5
5
|
"main": "bundle.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "NODE_ENV=localtest
|
|
7
|
+
"test": "NODE_ENV=localtest jest --config jestconfig.json --detectOpenHandles --verbose --forceExit",
|
|
8
8
|
"build": "rollup -c",
|
|
9
9
|
"prepare": "npm run build"
|
|
10
10
|
},
|
|
@@ -13,23 +13,28 @@
|
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@babel/core": "^7.1.6",
|
|
15
15
|
"@babel/preset-env": "^7.1.6",
|
|
16
|
-
"
|
|
17
|
-
"rollup": "^
|
|
16
|
+
"@rollup/plugin-commonjs": "^18.1.0",
|
|
17
|
+
"@rollup/plugin-json": "^4.1.0",
|
|
18
|
+
"@rollup/plugin-typescript": "^8.2.1",
|
|
19
|
+
"@types/jest": "^24.0.19",
|
|
20
|
+
"@types/mongodb": "^3.5.16",
|
|
21
|
+
"@types/node": "^15.0.1",
|
|
22
|
+
"@types/pino": "^5.8.12",
|
|
23
|
+
"jest": "^24.9.0",
|
|
24
|
+
"prettier": "^1.18.2",
|
|
25
|
+
"rollup": "^2.47.0",
|
|
18
26
|
"rollup-plugin-babel": "^4.0.3",
|
|
19
|
-
"rollup-plugin-node-resolve": "^3.4.0"
|
|
27
|
+
"rollup-plugin-node-resolve": "^3.4.0",
|
|
28
|
+
"ts-jest": "^24.3.0",
|
|
29
|
+
"tslint": "^5.20.0",
|
|
30
|
+
"tslint-config-prettier": "^1.18.0",
|
|
31
|
+
"typescript": "^3.6.4"
|
|
20
32
|
},
|
|
21
33
|
"dependencies": {
|
|
22
34
|
"@becollective/constants": "^3.11.0",
|
|
35
|
+
"axios": "^0.21.1",
|
|
23
36
|
"lodash": "^4.17.15",
|
|
24
37
|
"moment": "^2.24.0",
|
|
25
38
|
"moment-timezone": "^0.5.26"
|
|
26
|
-
},
|
|
27
|
-
"jest": {
|
|
28
|
-
"testMatch": [
|
|
29
|
-
"**/*.test.js"
|
|
30
|
-
],
|
|
31
|
-
"setupFiles": [
|
|
32
|
-
"./testUtils/setup.js"
|
|
33
|
-
]
|
|
34
39
|
}
|
|
35
40
|
}
|
package/rollup.config.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import resolve from 'rollup-plugin-node-resolve';
|
|
2
2
|
import babel from 'rollup-plugin-babel';
|
|
3
|
+
import typescript from '@rollup/plugin-typescript';
|
|
4
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
5
|
+
import json from '@rollup/plugin-json';
|
|
3
6
|
|
|
4
7
|
export default {
|
|
5
8
|
input: 'index.js',
|
|
@@ -8,6 +11,9 @@ export default {
|
|
|
8
11
|
format: 'cjs'
|
|
9
12
|
},
|
|
10
13
|
plugins: [
|
|
14
|
+
json(),
|
|
15
|
+
commonjs(),
|
|
16
|
+
typescript(),
|
|
11
17
|
resolve(),
|
|
12
18
|
babel({
|
|
13
19
|
exclude: 'node_modules/**', // only transpile our source code
|
package/setup.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { memoize, get as lodashGet } from 'lodash';
|
|
3
|
+
|
|
4
|
+
const fetchFeatureActive = async (
|
|
5
|
+
feature: string,
|
|
6
|
+
options: { env: string; region?: string },
|
|
7
|
+
): Promise<boolean> => {
|
|
8
|
+
const getFlagByFeatureUrl = `https://lb-central.becollective.com/api/v2/feature-flags/${feature}`;
|
|
9
|
+
let isFeatureActive = false;
|
|
10
|
+
const featureFlags = await axios.get(getFlagByFeatureUrl);
|
|
11
|
+
const environments = lodashGet(featureFlags, 'data.Environments');
|
|
12
|
+
if (environments) {
|
|
13
|
+
isFeatureActive = environments.includes(options.env);
|
|
14
|
+
}
|
|
15
|
+
return isFeatureActive;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const memoizedFeatureActive = memoize(fetchFeatureActive, (feature, options) =>
|
|
19
|
+
JSON.stringify({ feature, options }),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
class FeatureFlag {
|
|
23
|
+
ttl: number;
|
|
24
|
+
expiry: number;
|
|
25
|
+
constructor(ttl?) {
|
|
26
|
+
this.ttl = ttl || 1000 * 60 * 5; // default to 5 minutes
|
|
27
|
+
this.expiry = Date.now() + ttl;
|
|
28
|
+
}
|
|
29
|
+
async isFeatureActive(
|
|
30
|
+
feature: string,
|
|
31
|
+
options: { env: string; region?: string },
|
|
32
|
+
): Promise<boolean> {
|
|
33
|
+
// Lazy expiration upon function call
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
if (now >= this.expiry) {
|
|
36
|
+
memoizedFeatureActive.cache.clear();
|
|
37
|
+
this.expiry = now + this.ttl;
|
|
38
|
+
}
|
|
39
|
+
return await memoizedFeatureActive(feature, options);
|
|
40
|
+
}
|
|
41
|
+
clearCache() {
|
|
42
|
+
memoizedFeatureActive.cache.clear();
|
|
43
|
+
}
|
|
44
|
+
setCacheTtl(ttl: number) {
|
|
45
|
+
this.ttl = ttl;
|
|
46
|
+
this.expiry = Date.now() + ttl;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default FeatureFlag;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import FeatureFlag from '../src/FeatureFlag';
|
|
3
|
+
|
|
4
|
+
const featureFlag = new FeatureFlag();
|
|
5
|
+
jest.mock('axios');
|
|
6
|
+
const mockAxios = axios as jest.Mocked<typeof axios>;
|
|
7
|
+
const feature = 'test-feature';
|
|
8
|
+
|
|
9
|
+
describe('Make feature decision based on feature flags', () => {
|
|
10
|
+
test('should return true if feature is flagged active on localtest', async () => {
|
|
11
|
+
mockAxios.get.mockReturnValue(
|
|
12
|
+
Promise.resolve({
|
|
13
|
+
data: { Environments: ['localtest', 'compose'] },
|
|
14
|
+
}),
|
|
15
|
+
);
|
|
16
|
+
const isActive = await featureFlag.isFeatureActive(feature, {
|
|
17
|
+
env: 'localtest',
|
|
18
|
+
});
|
|
19
|
+
expect(isActive).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
test('should return false if feature is NOT flagged active on localtest', async () => {
|
|
22
|
+
featureFlag.clearCache();
|
|
23
|
+
mockAxios.get.mockReturnValue(
|
|
24
|
+
Promise.resolve({
|
|
25
|
+
data: { Environments: ['dev-au'] },
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
const isActive = await featureFlag.isFeatureActive(feature, {
|
|
29
|
+
env: 'localtest',
|
|
30
|
+
});
|
|
31
|
+
expect(isActive).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
test('should return false if no result returned from feature flag', async () => {
|
|
34
|
+
featureFlag.clearCache();
|
|
35
|
+
mockAxios.get.mockReturnValue(Promise.resolve({}));
|
|
36
|
+
const isActive = await featureFlag.isFeatureActive(feature, {
|
|
37
|
+
env: 'localtest',
|
|
38
|
+
});
|
|
39
|
+
expect(isActive).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
describe('Test caching', () => {
|
|
42
|
+
test('should return same result for same parameter without fetch api', async () => {
|
|
43
|
+
featureFlag.clearCache();
|
|
44
|
+
mockAxios.get.mockReturnValue(
|
|
45
|
+
Promise.resolve({
|
|
46
|
+
data: { Environments: ['localtest', 'compose'] },
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
const isActive = await featureFlag.isFeatureActive(feature, {
|
|
50
|
+
env: 'localtest',
|
|
51
|
+
});
|
|
52
|
+
expect(mockAxios.get).toBeCalled();
|
|
53
|
+
expect(isActive).toBe(true);
|
|
54
|
+
mockAxios.get.mockClear();
|
|
55
|
+
const secondCallResult = await featureFlag.isFeatureActive(feature, {
|
|
56
|
+
env: 'localtest',
|
|
57
|
+
});
|
|
58
|
+
expect(mockAxios.get).not.toBeCalled();
|
|
59
|
+
expect(secondCallResult).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
test('should fetch api after expiry', async () => {
|
|
62
|
+
featureFlag.clearCache();
|
|
63
|
+
featureFlag.setCacheTtl(0);
|
|
64
|
+
mockAxios.get.mockReturnValue(
|
|
65
|
+
Promise.resolve({
|
|
66
|
+
data: { Environments: ['localtest', 'compose'] },
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
const isActive = await featureFlag.isFeatureActive(feature, {
|
|
70
|
+
env: 'localtest',
|
|
71
|
+
});
|
|
72
|
+
expect(mockAxios.get).toBeCalled();
|
|
73
|
+
expect(isActive).toBe(true);
|
|
74
|
+
mockAxios.get.mockClear();
|
|
75
|
+
const secondCallResult = await featureFlag.isFeatureActive(feature, {
|
|
76
|
+
env: 'localtest',
|
|
77
|
+
});
|
|
78
|
+
expect(mockAxios.get).toBeCalled();
|
|
79
|
+
expect(secondCallResult).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
test('should fetch api for different parameter', async () => {
|
|
82
|
+
featureFlag.clearCache();
|
|
83
|
+
featureFlag.setCacheTtl(1000 * 60 * 5);
|
|
84
|
+
mockAxios.get.mockReturnValue(
|
|
85
|
+
Promise.resolve({
|
|
86
|
+
data: { Environments: ['localtest', 'compose'] },
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
const isActive = await featureFlag.isFeatureActive(feature, {
|
|
90
|
+
env: 'localtest',
|
|
91
|
+
});
|
|
92
|
+
expect(mockAxios.get).toBeCalled();
|
|
93
|
+
expect(isActive).toBe(true);
|
|
94
|
+
mockAxios.get.mockClear();
|
|
95
|
+
const secondCallResult = await featureFlag.isFeatureActive(feature, {
|
|
96
|
+
env: 'compose',
|
|
97
|
+
});
|
|
98
|
+
expect(mockAxios.get).toBeCalled();
|
|
99
|
+
expect(secondCallResult).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["es2017"],
|
|
4
|
+
"esModuleInterop": true,
|
|
5
|
+
"allowSyntheticDefaultImports": true,
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"emitDecoratorMetadata": true,
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"noUnusedLocals": false,
|
|
11
|
+
"noUnusedParameters": false,
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"target": "es2017",
|
|
14
|
+
"outDir": "lib"
|
|
15
|
+
},
|
|
16
|
+
"exclude": ["node_modules", "setup.ts"]
|
|
17
|
+
}
|
package/tslint.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["tslint:recommended", "tslint-config-prettier"],
|
|
3
|
+
"rules": {
|
|
4
|
+
"align": [
|
|
5
|
+
true,
|
|
6
|
+
"parameters",
|
|
7
|
+
"arguments",
|
|
8
|
+
"statements"
|
|
9
|
+
],
|
|
10
|
+
"ban": false,
|
|
11
|
+
"class-name": true,
|
|
12
|
+
"comment-format": [
|
|
13
|
+
true,
|
|
14
|
+
"check-space"
|
|
15
|
+
],
|
|
16
|
+
"curly": true,
|
|
17
|
+
"eofline": false,
|
|
18
|
+
"forin": true,
|
|
19
|
+
"indent": [ true, "spaces" ],
|
|
20
|
+
"interface-name": [true, "never-prefix"],
|
|
21
|
+
"jsdoc-format": true,
|
|
22
|
+
"jsx-no-lambda": false,
|
|
23
|
+
"jsx-no-multiline-js": false,
|
|
24
|
+
"label-position": true,
|
|
25
|
+
"max-line-length": [ true, 120 ],
|
|
26
|
+
"member-ordering": [
|
|
27
|
+
true,
|
|
28
|
+
{
|
|
29
|
+
"order": [
|
|
30
|
+
"public-before-private",
|
|
31
|
+
"static-before-instance",
|
|
32
|
+
"variables-before-functions"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"no-any": true,
|
|
37
|
+
"no-arg": true,
|
|
38
|
+
"no-bitwise": true,
|
|
39
|
+
"no-console": [
|
|
40
|
+
true,
|
|
41
|
+
"log",
|
|
42
|
+
"error",
|
|
43
|
+
"debug",
|
|
44
|
+
"info",
|
|
45
|
+
"time",
|
|
46
|
+
"timeEnd",
|
|
47
|
+
"trace"
|
|
48
|
+
],
|
|
49
|
+
"no-consecutive-blank-lines": true,
|
|
50
|
+
"no-construct": true,
|
|
51
|
+
"no-debugger": true,
|
|
52
|
+
"no-duplicate-variable": true,
|
|
53
|
+
"no-empty": true,
|
|
54
|
+
"no-eval": true,
|
|
55
|
+
"no-shadowed-variable": true,
|
|
56
|
+
"no-string-literal": true,
|
|
57
|
+
"no-switch-case-fall-through": true,
|
|
58
|
+
"no-trailing-whitespace": false,
|
|
59
|
+
"no-unused-expression": true,
|
|
60
|
+
"one-line": [
|
|
61
|
+
true,
|
|
62
|
+
"check-open-brace",
|
|
63
|
+
"check-whitespace"
|
|
64
|
+
],
|
|
65
|
+
"quotemark": [true, "single", "jsx-double"],
|
|
66
|
+
"radix": true,
|
|
67
|
+
"semicolon": [true, "always"],
|
|
68
|
+
"switch-default": true,
|
|
69
|
+
|
|
70
|
+
"trailing-comma": [true, {"multiline": "always", "singleline": "never"}],
|
|
71
|
+
|
|
72
|
+
"triple-equals": [ true, "allow-null-check" ],
|
|
73
|
+
"typedef": [
|
|
74
|
+
true,
|
|
75
|
+
"parameter",
|
|
76
|
+
"property-declaration"
|
|
77
|
+
],
|
|
78
|
+
"typedef-whitespace": [
|
|
79
|
+
true,
|
|
80
|
+
{
|
|
81
|
+
"call-signature": "nospace",
|
|
82
|
+
"index-signature": "nospace",
|
|
83
|
+
"parameter": "nospace",
|
|
84
|
+
"property-declaration": "nospace",
|
|
85
|
+
"variable-declaration": "nospace"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"],
|
|
89
|
+
"whitespace": [
|
|
90
|
+
true,
|
|
91
|
+
"check-branch",
|
|
92
|
+
"check-decl",
|
|
93
|
+
"check-module",
|
|
94
|
+
"check-operator",
|
|
95
|
+
"check-separator",
|
|
96
|
+
"check-type",
|
|
97
|
+
"check-typecast"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
package/testUtils/setup.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
process.env.NODE_ENV = 'localtest';
|
|
2
|
-
|
|
3
|
-
let hasListened = false;
|
|
4
|
-
if(!hasListened) {
|
|
5
|
-
hasListened = true;
|
|
6
|
-
process.on('unhandledRejection', (err) => {
|
|
7
|
-
console.debug(err.stack);
|
|
8
|
-
});
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
global.console = {
|
|
12
|
-
// These could be called in parts of the code for good reason but pollute the test output.
|
|
13
|
-
// So suppress the while testing.
|
|
14
|
-
// Ref: https://stackoverflow.com/questions/44467657/jest-better-way-to-disable-console-inside-unit-tests
|
|
15
|
-
log: jest.fn(),
|
|
16
|
-
error: jest.fn(),
|
|
17
|
-
warn: jest.fn(),
|
|
18
|
-
info: jest.fn(),
|
|
19
|
-
// Use console.debug if you need to debug while testing
|
|
20
|
-
debug: console.debug,
|
|
21
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|