@featurevisor/core 1.11.1 → 1.13.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/.eslintcache +1 -1
- package/CHANGELOG.md +22 -0
- package/LICENSE +1 -1
- package/coverage/clover.xml +69 -69
- package/coverage/coverage-final.json +2 -2
- package/coverage/lcov-report/index.html +7 -7
- package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
- package/coverage/lcov-report/lib/builder/index.html +5 -5
- package/coverage/lcov-report/lib/builder/revision.js.html +1 -1
- package/coverage/lcov-report/lib/builder/traffic.js.html +5 -11
- package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
- package/coverage/lcov-report/lib/tester/index.html +1 -1
- package/coverage/lcov-report/lib/tester/matrix.js.html +1 -1
- package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
- package/coverage/lcov-report/src/builder/index.html +5 -5
- package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/src/builder/traffic.ts.html +6 -15
- package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
- package/coverage/lcov-report/src/tester/index.html +1 -1
- package/coverage/lcov-report/src/tester/matrix.ts.html +1 -1
- package/coverage/lcov.info +113 -117
- package/lib/builder/buildDatafile.js +23 -6
- package/lib/builder/buildDatafile.js.map +1 -1
- package/lib/builder/traffic.js +1 -3
- package/lib/builder/traffic.js.map +1 -1
- package/lib/config/projectConfig.d.ts +1 -0
- package/lib/config/projectConfig.js +3 -1
- package/lib/config/projectConfig.js.map +1 -1
- package/lib/find-usage/index.d.ts +17 -0
- package/lib/find-usage/index.js +379 -0
- package/lib/find-usage/index.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/builder/buildDatafile.ts +16 -6
- package/src/builder/traffic.ts +2 -5
- package/src/config/projectConfig.ts +4 -1
- package/src/find-usage/index.ts +279 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { Condition, FeatureKey, SegmentKey, AttributeKey } from "@featurevisor/types";
|
|
2
|
+
|
|
3
|
+
import { Dependencies } from "../dependencies";
|
|
4
|
+
import {
|
|
5
|
+
extractAttributeKeysFromConditions,
|
|
6
|
+
extractSegmentKeysFromGroupSegments,
|
|
7
|
+
} from "../utils/extractKeys";
|
|
8
|
+
|
|
9
|
+
export async function findSegmentUsage(
|
|
10
|
+
deps: Dependencies,
|
|
11
|
+
segmentKey: SegmentKey,
|
|
12
|
+
): Promise<Set<FeatureKey>> {
|
|
13
|
+
const { datasource, projectConfig } = deps;
|
|
14
|
+
|
|
15
|
+
const featureKeys = await datasource.listFeatures();
|
|
16
|
+
|
|
17
|
+
const usedInFeatures = new Set<FeatureKey>();
|
|
18
|
+
|
|
19
|
+
for (const featureKey of featureKeys) {
|
|
20
|
+
const feature = await datasource.readFeature(featureKey);
|
|
21
|
+
const segmentKeys = new Set<SegmentKey>();
|
|
22
|
+
|
|
23
|
+
// variable overrides inside variations
|
|
24
|
+
projectConfig.environments.forEach((environment) => {
|
|
25
|
+
if (feature.variations) {
|
|
26
|
+
feature.variations.forEach((variation) => {
|
|
27
|
+
if (variation.variables) {
|
|
28
|
+
variation.variables.forEach((variable) => {
|
|
29
|
+
if (variable.overrides) {
|
|
30
|
+
variable.overrides.forEach((override) => {
|
|
31
|
+
if (override.segments) {
|
|
32
|
+
extractSegmentKeysFromGroupSegments(override.segments).forEach((segmentKey) =>
|
|
33
|
+
segmentKeys.add(segmentKey),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// force
|
|
44
|
+
if (feature.environments[environment].force) {
|
|
45
|
+
feature.environments[environment].force?.forEach((force) => {
|
|
46
|
+
if (force.segments) {
|
|
47
|
+
extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) =>
|
|
48
|
+
segmentKeys.add(segmentKey),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// rules
|
|
55
|
+
if (feature.environments[environment].rules) {
|
|
56
|
+
feature.environments[environment].rules?.forEach((rule) => {
|
|
57
|
+
extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) =>
|
|
58
|
+
segmentKeys.add(segmentKey),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (segmentKeys.has(segmentKey)) {
|
|
65
|
+
usedInFeatures.add(featureKey);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return usedInFeatures;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface AttributeUsage {
|
|
73
|
+
features: Set<FeatureKey>;
|
|
74
|
+
segments: Set<SegmentKey>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function findAttributeUsage(
|
|
78
|
+
deps: Dependencies,
|
|
79
|
+
attributeKey: AttributeKey,
|
|
80
|
+
): Promise<AttributeUsage> {
|
|
81
|
+
const { datasource, projectConfig } = deps;
|
|
82
|
+
|
|
83
|
+
const usedIn: AttributeUsage = {
|
|
84
|
+
features: new Set<FeatureKey>(),
|
|
85
|
+
segments: new Set<SegmentKey>(),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// features
|
|
89
|
+
const featureKeys = await datasource.listFeatures();
|
|
90
|
+
for (const featureKey of featureKeys) {
|
|
91
|
+
const feature = await datasource.readFeature(featureKey);
|
|
92
|
+
const attributeKeys = new Set<AttributeKey>();
|
|
93
|
+
|
|
94
|
+
// variable overrides inside variations
|
|
95
|
+
projectConfig.environments.forEach((environment) => {
|
|
96
|
+
if (feature.variations) {
|
|
97
|
+
feature.variations.forEach((variation) => {
|
|
98
|
+
if (variation.variables) {
|
|
99
|
+
variation.variables.forEach((variable) => {
|
|
100
|
+
if (variable.overrides) {
|
|
101
|
+
variable.overrides.forEach((override) => {
|
|
102
|
+
if (override.conditions) {
|
|
103
|
+
extractAttributeKeysFromConditions(override.conditions).forEach(
|
|
104
|
+
(attributeKey) => attributeKeys.add(attributeKey),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// force
|
|
115
|
+
if (feature.environments[environment].force) {
|
|
116
|
+
feature.environments[environment].force?.forEach((force) => {
|
|
117
|
+
if (force.conditions) {
|
|
118
|
+
extractAttributeKeysFromConditions(force.conditions).forEach((attributeKey) =>
|
|
119
|
+
attributeKeys.add(attributeKey),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (attributeKeys.has(attributeKey)) {
|
|
127
|
+
usedIn.features.add(featureKey);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// segments
|
|
132
|
+
const segmentKeys = await datasource.listSegments();
|
|
133
|
+
for (const segmentKey of segmentKeys) {
|
|
134
|
+
const segment = await datasource.readSegment(segmentKey);
|
|
135
|
+
const attributeKeys = new Set<AttributeKey>();
|
|
136
|
+
|
|
137
|
+
extractAttributeKeysFromConditions(segment.conditions as Condition | Condition[]).forEach(
|
|
138
|
+
(attributeKey) => {
|
|
139
|
+
attributeKeys.add(attributeKey);
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (attributeKeys.has(attributeKey)) {
|
|
144
|
+
usedIn.segments.add(segmentKey);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return usedIn;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function findUnusedSegments(deps: Dependencies): Promise<Set<SegmentKey>> {
|
|
152
|
+
const { datasource } = deps;
|
|
153
|
+
|
|
154
|
+
const segmentKeys = await datasource.listSegments();
|
|
155
|
+
const unusedSegments = new Set<SegmentKey>();
|
|
156
|
+
|
|
157
|
+
for (const segmentKey of segmentKeys) {
|
|
158
|
+
const usedInFeatures = await findSegmentUsage(deps, segmentKey);
|
|
159
|
+
|
|
160
|
+
if (usedInFeatures.size === 0) {
|
|
161
|
+
unusedSegments.add(segmentKey);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return unusedSegments;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function findUnusedAttributes(deps: Dependencies): Promise<Set<AttributeKey>> {
|
|
169
|
+
const { datasource } = deps;
|
|
170
|
+
|
|
171
|
+
const attributeKeys = await datasource.listAttributes();
|
|
172
|
+
const unusedAttributes = new Set<AttributeKey>();
|
|
173
|
+
|
|
174
|
+
for (const attributeKey of attributeKeys) {
|
|
175
|
+
const usedIn = await findAttributeUsage(deps, attributeKey);
|
|
176
|
+
|
|
177
|
+
if (usedIn.features.size === 0 && usedIn.segments.size === 0) {
|
|
178
|
+
unusedAttributes.add(attributeKey);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return unusedAttributes;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface FindUsageOptions {
|
|
186
|
+
segment?: string;
|
|
187
|
+
attribute?: string;
|
|
188
|
+
|
|
189
|
+
unusedSegments?: boolean;
|
|
190
|
+
unusedAttributes?: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function findUsageInProject(deps: Dependencies, options: FindUsageOptions) {
|
|
194
|
+
console.log("");
|
|
195
|
+
|
|
196
|
+
// segment
|
|
197
|
+
if (options.segment) {
|
|
198
|
+
const usedInFeatures = await findSegmentUsage(deps, options.segment);
|
|
199
|
+
|
|
200
|
+
if (usedInFeatures.size === 0) {
|
|
201
|
+
console.log(`Segment "${options.segment}" is not used in any features.`);
|
|
202
|
+
} else {
|
|
203
|
+
console.log(`Segment "${options.segment}" is used in the following features:\n`);
|
|
204
|
+
|
|
205
|
+
usedInFeatures.forEach((featureKey) => {
|
|
206
|
+
console.log(` - ${featureKey}`);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// attribute
|
|
214
|
+
if (options.attribute) {
|
|
215
|
+
const usedIn = await findAttributeUsage(deps, options.attribute);
|
|
216
|
+
|
|
217
|
+
if (usedIn.features.size === 0 && usedIn.segments.size === 0) {
|
|
218
|
+
console.log(`Attribute "${options.attribute}" is not used in any features or segments.`);
|
|
219
|
+
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (usedIn.features.size > 0) {
|
|
224
|
+
console.log(`Attribute "${options.attribute}" is used in the following features:\n`);
|
|
225
|
+
|
|
226
|
+
usedIn.features.forEach((featureKey) => {
|
|
227
|
+
console.log(` - ${featureKey}`);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
console.log("");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (usedIn.segments.size > 0) {
|
|
234
|
+
console.log(`Attribute "${options.attribute}" is used in the following segments:\n`);
|
|
235
|
+
|
|
236
|
+
usedIn.segments.forEach((segmentKey) => {
|
|
237
|
+
console.log(` - ${segmentKey}`);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// unused segments
|
|
245
|
+
if (options.unusedSegments) {
|
|
246
|
+
const unusedSegments = await findUnusedSegments(deps);
|
|
247
|
+
|
|
248
|
+
if (unusedSegments.size === 0) {
|
|
249
|
+
console.log("No unused segments found.");
|
|
250
|
+
} else {
|
|
251
|
+
console.log("Unused segments:\n");
|
|
252
|
+
|
|
253
|
+
unusedSegments.forEach((segmentKey) => {
|
|
254
|
+
console.log(` - ${segmentKey}`);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// unused attributes
|
|
262
|
+
if (options.unusedAttributes) {
|
|
263
|
+
const unusedAttributes = await findUnusedAttributes(deps);
|
|
264
|
+
|
|
265
|
+
if (unusedAttributes.size === 0) {
|
|
266
|
+
console.log("No unused attributes found.");
|
|
267
|
+
} else {
|
|
268
|
+
console.log("Unused attributes:\n");
|
|
269
|
+
|
|
270
|
+
unusedAttributes.forEach((attributeKey) => {
|
|
271
|
+
console.log(` - ${attributeKey}`);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
console.log("Please specify a segment or attribute.");
|
|
279
|
+
}
|