@featurevisor/core
Version:
Core package of Featurevisor for Node.js usage
388 lines • 16.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.findUsagePlugin = void 0;
exports.findAllUsageInFeatures = findAllUsageInFeatures;
exports.findAllUsageInSegments = findAllUsageInSegments;
exports.findFeatureUsage = findFeatureUsage;
exports.findSegmentUsage = findSegmentUsage;
exports.findAttributeUsage = findAttributeUsage;
exports.findUnusedSegments = findUnusedSegments;
exports.findUnusedAttributes = findUnusedAttributes;
exports.findUsageInProject = findUsageInProject;
const extractKeys_1 = require("../utils/extractKeys");
async function findAllUsageInFeatures(deps) {
const { datasource, projectConfig } = deps;
const usageInFeatures = {};
const featureKeys = await datasource.listFeatures();
for (const featureKey of featureKeys) {
const feature = await datasource.readFeature(featureKey);
usageInFeatures[featureKey] = {
features: new Set(),
segments: new Set(),
attributes: new Set(),
};
// required
if (feature.required) {
feature.required.forEach((required) => {
if (typeof required === "string") {
usageInFeatures[featureKey].features.add(required);
}
else if (typeof required === "object" && required.key) {
usageInFeatures[featureKey].features.add(required.key);
}
});
}
// bucketBy
if (feature.bucketBy) {
if (typeof feature.bucketBy === "string") {
usageInFeatures[featureKey].attributes.add(feature.bucketBy);
}
else if (Array.isArray(feature.bucketBy)) {
feature.bucketBy.forEach((b) => usageInFeatures[featureKey].attributes.add(b));
}
else if (typeof feature.bucketBy === "object" && feature.bucketBy.or) {
feature.bucketBy.or.forEach((b) => usageInFeatures[featureKey].attributes.add(b));
}
}
// variable overrides inside variations
if (feature.variations) {
feature.variations.forEach((variation) => {
if (variation.variableOverrides) {
Object.keys(variation.variableOverrides).forEach((variableKey) => {
const overrides = variation.variableOverrides?.[variableKey];
if (overrides) {
overrides.forEach((override) => {
if (override.segments) {
(0, extractKeys_1.extractSegmentKeysFromGroupSegments)(override.segments).forEach((segmentKey) => usageInFeatures[featureKey].segments.add(segmentKey));
}
if (override.conditions) {
(0, extractKeys_1.extractAttributeKeysFromConditions)(override.conditions).forEach((attributeKey) => usageInFeatures[featureKey].attributes.add(attributeKey));
}
});
}
});
}
});
}
// with environments
if (Array.isArray(projectConfig.environments)) {
projectConfig.environments.forEach((environment) => {
// force
if (feature.force && feature.force[environment]) {
feature.force[environment].forEach((force) => {
if (force.segments) {
(0, extractKeys_1.extractSegmentKeysFromGroupSegments)(force.segments).forEach((segmentKey) => usageInFeatures[featureKey].segments.add(segmentKey));
}
if (force.conditions) {
(0, extractKeys_1.extractAttributeKeysFromConditions)(force.conditions).forEach((attributeKey) => usageInFeatures[featureKey].attributes.add(attributeKey));
}
});
}
// rules
if (feature.rules && feature.rules[environment]) {
feature.rules[environment].forEach((rule) => {
(0, extractKeys_1.extractSegmentKeysFromGroupSegments)(rule.segments).forEach((segmentKey) => usageInFeatures[featureKey].segments.add(segmentKey));
});
}
});
}
// no environments
if (projectConfig.environments === false) {
// force
if (Array.isArray(feature.force)) {
feature.force.forEach((force) => {
if (force.segments) {
(0, extractKeys_1.extractSegmentKeysFromGroupSegments)(force.segments).forEach((segmentKey) => usageInFeatures[featureKey].segments.add(segmentKey));
}
if (force.conditions) {
(0, extractKeys_1.extractAttributeKeysFromConditions)(force.conditions).forEach((attributeKey) => usageInFeatures[featureKey].attributes.add(attributeKey));
}
});
}
// rules
if (Array.isArray(feature.rules)) {
feature.rules.forEach((rule) => {
(0, extractKeys_1.extractSegmentKeysFromGroupSegments)(rule.segments).forEach((segmentKey) => usageInFeatures[featureKey].segments.add(segmentKey));
});
}
}
}
return usageInFeatures;
}
async function findAllUsageInSegments(deps) {
const { datasource } = deps;
const usageInSegments = {};
const segmentKeys = await datasource.listSegments();
for (const segmentKey of segmentKeys) {
const segment = await datasource.readSegment(segmentKey);
usageInSegments[segmentKey] = {
attributes: new Set(),
};
(0, extractKeys_1.extractAttributeKeysFromConditions)(segment.conditions).forEach((attributeKey) => {
usageInSegments[segmentKey].attributes.add(attributeKey);
});
}
return usageInSegments;
}
async function findFeatureUsage(usageInFeatures, searchFeatureKey) {
const usedInFeatures = new Set();
for (const featureKey in usageInFeatures) {
if (usageInFeatures[featureKey].features.has(searchFeatureKey)) {
usedInFeatures.add(featureKey);
}
}
return usedInFeatures;
}
async function findSegmentUsage(usageInFeatures, segmentKey) {
const usedInFeatures = new Set();
for (const featureKey in usageInFeatures) {
if (usageInFeatures[featureKey].segments.has(segmentKey)) {
usedInFeatures.add(featureKey);
}
}
return usedInFeatures;
}
async function findAttributeUsage(usageInFeatures, usageInSegments, attributeKey) {
const usedIn = {
features: new Set(),
segments: new Set(),
};
for (const featureKey in usageInFeatures) {
if (usageInFeatures[featureKey].attributes.has(attributeKey)) {
usedIn.features.add(featureKey);
}
}
for (const segmentKey in usageInSegments) {
if (usageInSegments[segmentKey].attributes.has(attributeKey)) {
usedIn.segments.add(segmentKey);
}
}
return usedIn;
}
async function findUnusedSegments(deps, usageInFeatures) {
const { datasource } = deps;
const unusedSegments = new Set();
const allSegmentKeys = await datasource.listSegments();
const usedSegmentKeys = new Set();
for (const featureKey in usageInFeatures) {
usageInFeatures[featureKey].segments.forEach((segmentKey) => {
usedSegmentKeys.add(segmentKey);
});
}
allSegmentKeys.forEach((segmentKey) => {
if (!usedSegmentKeys.has(segmentKey)) {
unusedSegments.add(segmentKey);
}
});
return unusedSegments;
}
async function findUnusedAttributes(deps, usageInFeatures, usageInSegments) {
const { datasource } = deps;
const unusedAttributes = new Set();
const allAttributeKeys = await datasource.listAttributes();
const usedAttributeKeys = new Set();
for (const featureKey in usageInFeatures) {
usageInFeatures[featureKey].attributes.forEach((attributeKey) => {
usedAttributeKeys.add(attributeKey);
});
}
for (const segmentKey in usageInSegments) {
usageInSegments[segmentKey].attributes.forEach((attributeKey) => {
usedAttributeKeys.add(attributeKey);
});
}
allAttributeKeys.forEach((attributeKey) => {
if (!usedAttributeKeys.has(attributeKey)) {
unusedAttributes.add(attributeKey);
}
});
return unusedAttributes;
}
async function findUsageInProject(deps, options) {
const { datasource } = deps;
console.log("");
const usageInFeatures = await findAllUsageInFeatures(deps);
const usageInSegments = await findAllUsageInSegments(deps);
// feature
if (options.feature) {
const usedInFeatures = await findFeatureUsage(usageInFeatures, options.feature);
if (usedInFeatures.size === 0) {
console.log(`Feature "${options.feature}" is not used in any features.`);
}
else {
console.log(`Feature "${options.feature}" is used in the following features:\n`);
for (const featureKey of Array.from(usedInFeatures)) {
if (options.authors) {
const entries = await datasource.listHistoryEntries("feature", featureKey);
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
console.log(` - ${featureKey} (Authors: ${authors.join(", ")})`);
}
else {
console.log(` - ${featureKey}`);
}
}
}
return;
}
// segment
if (options.segment) {
const usedInFeatures = await findSegmentUsage(usageInFeatures, options.segment);
if (usedInFeatures.size === 0) {
console.log(`Segment "${options.segment}" is not used in any features.`);
}
else {
console.log(`Segment "${options.segment}" is used in the following features:\n`);
for (const featureKey of Array.from(usedInFeatures)) {
if (options.authors) {
const entries = await datasource.listHistoryEntries("feature", featureKey);
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
console.log(` - ${featureKey} (Authors: ${authors.join(", ")})`);
}
else {
console.log(` - ${featureKey}`);
}
}
}
return;
}
// attribute
if (options.attribute) {
const usedIn = await findAttributeUsage(usageInFeatures, usageInSegments, options.attribute);
if (usedIn.features.size === 0 && usedIn.segments.size === 0) {
console.log(`Attribute "${options.attribute}" is not used in any features or segments.`);
return;
}
if (usedIn.segments.size > 0) {
console.log(`Attribute "${options.attribute}" is used in the following segments:\n`);
for (const segmentKey of Array.from(usedIn.segments)) {
if (options.authors) {
const entries = await datasource.listHistoryEntries("segment", segmentKey);
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
console.log(` - ${segmentKey} (Authors: ${authors.join(", ")})`);
}
else {
console.log(` - ${segmentKey}`);
}
}
// features affected by above segments
const affectedFeatures = new Set();
for (const segmentKey of Array.from(usedIn.segments)) {
const featureKeys = await findSegmentUsage(usageInFeatures, segmentKey);
featureKeys.forEach((featureKey) => affectedFeatures.add(featureKey));
}
if (affectedFeatures.size > 0) {
console.log(`\nSegments above are used in the following features:\n`);
for (const featureKey of Array.from(affectedFeatures)) {
if (options.authors) {
const entries = await datasource.listHistoryEntries("feature", featureKey);
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
console.log(` - ${featureKey} (Authors: ${authors.join(", ")})`);
}
else {
console.log(` - ${featureKey}`);
}
}
console.log("");
}
}
if (usedIn.features.size > 0) {
console.log(`Attribute "${options.attribute}" is used directly in the following features:\n`);
for (const featureKey of Array.from(usedIn.features)) {
if (options.authors) {
const entries = await datasource.listHistoryEntries("feature", featureKey);
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
console.log(` - ${featureKey} (Authors: ${authors.join(", ")})`);
}
else {
console.log(` - ${featureKey}`);
}
}
console.log("");
}
return;
}
// unused segments
if (options.unusedSegments) {
const unusedSegments = await findUnusedSegments(deps, usageInFeatures);
if (unusedSegments.size === 0) {
console.log("No unused segments found.");
}
else {
console.log("Unused segments:\n");
for (const segmentKey of Array.from(unusedSegments)) {
if (options.authors) {
const entries = await datasource.listHistoryEntries("segment", segmentKey);
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
console.log(` - ${segmentKey} (Authors: ${authors.join(", ")})`);
}
else {
console.log(` - ${segmentKey}`);
}
}
}
return;
}
// unused attributes
if (options.unusedAttributes) {
const unusedAttributes = await findUnusedAttributes(deps, usageInFeatures, usageInSegments);
if (unusedAttributes.size === 0) {
console.log("No unused attributes found.");
}
else {
console.log("Unused attributes:\n");
for (const attributeKey of Array.from(unusedAttributes)) {
if (options.authors) {
const entries = await datasource.listHistoryEntries("attribute", attributeKey);
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
console.log(` - ${attributeKey} (Authors: ${authors.join(", ")})`);
}
else {
console.log(` - ${attributeKey}`);
}
}
}
return;
}
console.log("Please specify a segment or attribute.");
}
exports.findUsagePlugin = {
command: "find-usage",
handler: async ({ rootDirectoryPath, projectConfig, datasource, parsed }) => {
await findUsageInProject({
rootDirectoryPath,
projectConfig,
datasource,
options: parsed,
}, {
feature: parsed.feature,
segment: parsed.segment,
attribute: parsed.attribute,
unusedSegments: parsed.unusedSegments,
unusedAttributes: parsed.unusedAttributes,
authors: parsed.authors,
});
},
examples: [
{
command: "find-usage --segment=<segmentKey>",
description: "Find usage of a segment",
},
{
command: "find-usage --attribute=<attributeKey>",
description: "Find usage of an attribute",
},
{
command: "find-usage --unused-segments",
description: "Find unused segments",
},
{
command: "find-usage --unused-attributes",
description: "Find unused attributes",
},
{
command: "find-usage --authors",
description: "List authors of the usage",
},
],
};
//# sourceMappingURL=index.js.map