mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
refactor: create builder class for converting rows to avoid duplication (#5050)
Create a builder for creating the data structures for feature toggle list and playground api
This commit is contained in:
parent
1d27cfdc54
commit
9e493f56a0
@ -0,0 +1,191 @@
|
|||||||
|
import {
|
||||||
|
PartialDeep,
|
||||||
|
IFeatureToggleClient,
|
||||||
|
IStrategyConfig,
|
||||||
|
FeatureToggle,
|
||||||
|
IFeatureToggleQuery,
|
||||||
|
ITag,
|
||||||
|
} from '../../../types';
|
||||||
|
|
||||||
|
import { mapValues, ensureStringValue } from '../../../util';
|
||||||
|
import { FeatureConfigurationClient } from '../types/feature-toggle-strategies-store-type';
|
||||||
|
|
||||||
|
export class FeatureToggleRowConverter {
|
||||||
|
isUnseenStrategyRow = (
|
||||||
|
feature: PartialDeep<IFeatureToggleClient>,
|
||||||
|
row: Record<string, any>,
|
||||||
|
): boolean => {
|
||||||
|
return (
|
||||||
|
row.strategy_id &&
|
||||||
|
!feature.strategies?.find(
|
||||||
|
(strategy) => strategy?.id === row.strategy_id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
isNewTag = (
|
||||||
|
feature: PartialDeep<IFeatureToggleClient>,
|
||||||
|
row: Record<string, any>,
|
||||||
|
): boolean => {
|
||||||
|
return (
|
||||||
|
row.tag_type &&
|
||||||
|
row.tag_value &&
|
||||||
|
!feature.tags?.some(
|
||||||
|
(tag) =>
|
||||||
|
tag?.type === row.tag_type && tag?.value === row.tag_value,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
addSegmentToStrategy = (
|
||||||
|
feature: PartialDeep<IFeatureToggleClient>,
|
||||||
|
row: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
feature.strategies
|
||||||
|
?.find((strategy) => strategy?.id === row.strategy_id)
|
||||||
|
?.constraints?.push(...row.segment_constraints);
|
||||||
|
};
|
||||||
|
|
||||||
|
addSegmentIdsToStrategy = (
|
||||||
|
feature: PartialDeep<IFeatureToggleClient>,
|
||||||
|
row: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
const strategy = feature.strategies?.find(
|
||||||
|
(strategy) => strategy?.id === row.strategy_id,
|
||||||
|
);
|
||||||
|
if (!strategy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!strategy.segments) {
|
||||||
|
strategy.segments = [];
|
||||||
|
}
|
||||||
|
strategy.segments.push(row.segment_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
rowToStrategy = (row: Record<string, any>): IStrategyConfig => {
|
||||||
|
const strategy: IStrategyConfig = {
|
||||||
|
id: row.strategy_id,
|
||||||
|
name: row.strategy_name,
|
||||||
|
title: row.strategy_title,
|
||||||
|
constraints: row.constraints || [],
|
||||||
|
parameters: mapValues(row.parameters || {}, ensureStringValue),
|
||||||
|
sortOrder: row.sort_order,
|
||||||
|
};
|
||||||
|
strategy.variants = row.strategy_variants || [];
|
||||||
|
return strategy;
|
||||||
|
};
|
||||||
|
|
||||||
|
addTag = (feature: Record<string, any>, row: Record<string, any>): void => {
|
||||||
|
const tags = feature.tags || [];
|
||||||
|
const newTag = this.rowToTag(row);
|
||||||
|
feature.tags = [...tags, newTag];
|
||||||
|
};
|
||||||
|
|
||||||
|
rowToTag = (row: Record<string, any>): ITag => {
|
||||||
|
return {
|
||||||
|
value: row.tag_value,
|
||||||
|
type: row.tag_type,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
formatToggles = (result: IFeatureToggleQuery) =>
|
||||||
|
Object.values(result).map(({ strategies, ...rest }) => ({
|
||||||
|
...rest,
|
||||||
|
strategies: strategies
|
||||||
|
?.sort((strategy1, strategy2) => {
|
||||||
|
if (
|
||||||
|
typeof strategy1.sortOrder === 'number' &&
|
||||||
|
typeof strategy2.sortOrder === 'number'
|
||||||
|
) {
|
||||||
|
return strategy1.sortOrder - strategy2.sortOrder;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
})
|
||||||
|
.map(({ title, sortOrder, ...strategy }) => ({
|
||||||
|
...strategy,
|
||||||
|
...(title ? { title } : {}),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
createBaseFeature = (
|
||||||
|
row: any,
|
||||||
|
feature: PartialDeep<IFeatureToggleClient>,
|
||||||
|
featureQuery?: IFeatureToggleQuery,
|
||||||
|
) => {
|
||||||
|
feature.impressionData = row.impression_data;
|
||||||
|
feature.enabled = !!row.enabled;
|
||||||
|
feature.name = row.name;
|
||||||
|
feature.description = row.description;
|
||||||
|
feature.project = row.project;
|
||||||
|
feature.stale = row.stale;
|
||||||
|
feature.type = row.type;
|
||||||
|
feature.lastSeenAt = row.last_seen_at;
|
||||||
|
feature.variants = row.variants || [];
|
||||||
|
feature.project = row.project;
|
||||||
|
|
||||||
|
if (this.isUnseenStrategyRow(feature, row) && !row.strategy_disabled) {
|
||||||
|
feature.strategies?.push(this.rowToStrategy(row));
|
||||||
|
}
|
||||||
|
if (this.isNewTag(feature, row)) {
|
||||||
|
this.addTag(feature, row);
|
||||||
|
}
|
||||||
|
if (featureQuery?.inlineSegmentConstraints && row.segment_id) {
|
||||||
|
this.addSegmentToStrategy(feature, row);
|
||||||
|
} else if (!featureQuery?.inlineSegmentConstraints && row.segment_id) {
|
||||||
|
this.addSegmentIdsToStrategy(feature, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return feature;
|
||||||
|
};
|
||||||
|
|
||||||
|
buildFeatureToggleListFromRows = (
|
||||||
|
rows: any[],
|
||||||
|
featureQuery?: IFeatureToggleQuery,
|
||||||
|
): FeatureToggle[] => {
|
||||||
|
const result = rows.reduce((acc, r) => {
|
||||||
|
let feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
|
||||||
|
strategies: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
feature = this.createBaseFeature(r, feature, featureQuery);
|
||||||
|
|
||||||
|
feature.createdAt = r.created_at;
|
||||||
|
feature.favorite = r.favorite;
|
||||||
|
|
||||||
|
acc[r.name] = feature;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return this.formatToggles(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
buildPlaygroundFeaturesFromRows = (
|
||||||
|
rows: any[],
|
||||||
|
dependentFeaturesEnabled: boolean,
|
||||||
|
featureQuery?: IFeatureToggleQuery,
|
||||||
|
): FeatureConfigurationClient[] => {
|
||||||
|
const result = rows.reduce((acc, r) => {
|
||||||
|
let feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
|
||||||
|
strategies: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
feature = this.createBaseFeature(r, feature, featureQuery);
|
||||||
|
|
||||||
|
if (r.parent && dependentFeaturesEnabled) {
|
||||||
|
feature.dependencies = feature.dependencies || [];
|
||||||
|
feature.dependencies.push({
|
||||||
|
feature: r.parent,
|
||||||
|
enabled: r.parent_enabled,
|
||||||
|
...(r.parent_enabled
|
||||||
|
? { variants: r.parent_variants }
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[r.name] = feature;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return this.formatToggles(result);
|
||||||
|
};
|
||||||
|
}
|
@ -17,12 +17,9 @@ import { NameExistsError } from '../../error';
|
|||||||
import { DEFAULT_ENV } from '../../../lib/util';
|
import { DEFAULT_ENV } from '../../../lib/util';
|
||||||
|
|
||||||
import { FeatureToggleListBuilder } from './query-builders/feature-toggle-list-builder';
|
import { FeatureToggleListBuilder } from './query-builders/feature-toggle-list-builder';
|
||||||
import {
|
|
||||||
buildFeatureToggleListFromRows,
|
|
||||||
buildPlaygroundFeaturesFromRows,
|
|
||||||
} from './feature-toggle-utils';
|
|
||||||
import { FeatureConfigurationClient } from './types/feature-toggle-strategies-store-type';
|
import { FeatureConfigurationClient } from './types/feature-toggle-strategies-store-type';
|
||||||
import { IFlagResolver } from '../../../lib/types';
|
import { IFlagResolver } from '../../../lib/types';
|
||||||
|
import { FeatureToggleRowConverter } from './converters/feature-toggle-row-converter';
|
||||||
|
|
||||||
export type EnvironmentFeatureNames = { [key: string]: string[] };
|
export type EnvironmentFeatureNames = { [key: string]: string[] };
|
||||||
|
|
||||||
@ -65,9 +62,12 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
|||||||
|
|
||||||
private timer: Function;
|
private timer: Function;
|
||||||
|
|
||||||
|
private featureToggleRowConverter: FeatureToggleRowConverter;
|
||||||
|
|
||||||
constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
|
constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.logger = getLogger('feature-toggle-store.ts');
|
this.logger = getLogger('feature-toggle-store.ts');
|
||||||
|
this.featureToggleRowConverter = new FeatureToggleRowConverter();
|
||||||
this.timer = (action) =>
|
this.timer = (action) =>
|
||||||
metricsHelper.wrapTimer(eventBus, DB_TIME, {
|
metricsHelper.wrapTimer(eventBus, DB_TIME, {
|
||||||
store: 'feature-toggle',
|
store: 'feature-toggle',
|
||||||
@ -147,7 +147,10 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
|||||||
builder.getSelectColumns(),
|
builder.getSelectColumns(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return buildFeatureToggleListFromRows(rows, featureQuery);
|
return this.featureToggleRowConverter.buildFeatureToggleListFromRows(
|
||||||
|
rows,
|
||||||
|
featureQuery,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPlaygroundFeatures(
|
async getPlaygroundFeatures(
|
||||||
@ -171,7 +174,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
|||||||
builder.getSelectColumns(),
|
builder.getSelectColumns(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return buildPlaygroundFeaturesFromRows(
|
return this.featureToggleRowConverter.buildPlaygroundFeaturesFromRows(
|
||||||
rows,
|
rows,
|
||||||
dependentFeaturesEnabled,
|
dependentFeaturesEnabled,
|
||||||
featureQuery,
|
featureQuery,
|
||||||
|
@ -1,219 +0,0 @@
|
|||||||
import {
|
|
||||||
PartialDeep,
|
|
||||||
IFeatureToggleClient,
|
|
||||||
IStrategyConfig,
|
|
||||||
ITag,
|
|
||||||
IFeatureToggleQuery,
|
|
||||||
FeatureToggle,
|
|
||||||
} from '../../types';
|
|
||||||
import { mapValues, ensureStringValue } from '../../util';
|
|
||||||
import { FeatureConfigurationClient } from './types/feature-toggle-strategies-store-type';
|
|
||||||
|
|
||||||
const isUnseenStrategyRow = (
|
|
||||||
feature: PartialDeep<IFeatureToggleClient>,
|
|
||||||
row: Record<string, any>,
|
|
||||||
): boolean => {
|
|
||||||
return (
|
|
||||||
row.strategy_id &&
|
|
||||||
!feature.strategies?.find((s) => s?.id === row.strategy_id)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isNewTag = (
|
|
||||||
feature: PartialDeep<IFeatureToggleClient>,
|
|
||||||
row: Record<string, any>,
|
|
||||||
): boolean => {
|
|
||||||
return (
|
|
||||||
row.tag_type &&
|
|
||||||
row.tag_value &&
|
|
||||||
!feature.tags?.some(
|
|
||||||
(tag) => tag?.type === row.tag_type && tag?.value === row.tag_value,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addSegmentToStrategy = (
|
|
||||||
feature: PartialDeep<IFeatureToggleClient>,
|
|
||||||
row: Record<string, any>,
|
|
||||||
) => {
|
|
||||||
feature.strategies
|
|
||||||
?.find((s) => s?.id === row.strategy_id)
|
|
||||||
?.constraints?.push(...row.segment_constraints);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addSegmentIdsToStrategy = (
|
|
||||||
feature: PartialDeep<IFeatureToggleClient>,
|
|
||||||
row: Record<string, any>,
|
|
||||||
) => {
|
|
||||||
const strategy = feature.strategies?.find((s) => s?.id === row.strategy_id);
|
|
||||||
if (!strategy) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!strategy.segments) {
|
|
||||||
strategy.segments = [];
|
|
||||||
}
|
|
||||||
strategy.segments.push(row.segment_id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const rowToStrategy = (row: Record<string, any>): IStrategyConfig => {
|
|
||||||
const strategy: IStrategyConfig = {
|
|
||||||
id: row.strategy_id,
|
|
||||||
name: row.strategy_name,
|
|
||||||
title: row.strategy_title,
|
|
||||||
constraints: row.constraints || [],
|
|
||||||
parameters: mapValues(row.parameters || {}, ensureStringValue),
|
|
||||||
sortOrder: row.sort_order,
|
|
||||||
};
|
|
||||||
strategy.variants = row.strategy_variants || [];
|
|
||||||
return strategy;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addTag = (
|
|
||||||
feature: Record<string, any>,
|
|
||||||
row: Record<string, any>,
|
|
||||||
): void => {
|
|
||||||
const tags = feature.tags || [];
|
|
||||||
const newTag = rowToTag(row);
|
|
||||||
feature.tags = [...tags, newTag];
|
|
||||||
};
|
|
||||||
|
|
||||||
const rowToTag = (row: Record<string, any>): ITag => {
|
|
||||||
return {
|
|
||||||
value: row.tag_value,
|
|
||||||
type: row.tag_type,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildFeatureToggleListFromRows = (
|
|
||||||
rows: any[],
|
|
||||||
featureQuery?: IFeatureToggleQuery,
|
|
||||||
): FeatureToggle[] => {
|
|
||||||
let result = rows.reduce((acc, r) => {
|
|
||||||
const feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
|
|
||||||
strategies: [],
|
|
||||||
};
|
|
||||||
if (isUnseenStrategyRow(feature, r) && !r.strategy_disabled) {
|
|
||||||
feature.strategies?.push(rowToStrategy(r));
|
|
||||||
}
|
|
||||||
if (isNewTag(feature, r)) {
|
|
||||||
addTag(feature, r);
|
|
||||||
}
|
|
||||||
if (featureQuery?.inlineSegmentConstraints && r.segment_id) {
|
|
||||||
addSegmentToStrategy(feature, r);
|
|
||||||
} else if (!featureQuery?.inlineSegmentConstraints && r.segment_id) {
|
|
||||||
addSegmentIdsToStrategy(feature, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.impressionData = r.impression_data;
|
|
||||||
feature.enabled = !!r.enabled;
|
|
||||||
feature.name = r.name;
|
|
||||||
feature.description = r.description;
|
|
||||||
feature.project = r.project;
|
|
||||||
feature.stale = r.stale;
|
|
||||||
feature.type = r.type;
|
|
||||||
feature.lastSeenAt = r.last_seen_at;
|
|
||||||
feature.variants = r.variants || [];
|
|
||||||
feature.project = r.project;
|
|
||||||
feature.createdAt = r.created_at;
|
|
||||||
feature.favorite = r.favorite;
|
|
||||||
|
|
||||||
feature.lastSeenAt = r.last_seen_at;
|
|
||||||
|
|
||||||
acc[r.name] = feature;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
result = Object.values(result).map(({ strategies, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
strategies: strategies
|
|
||||||
?.sort((strategy1, strategy2) => {
|
|
||||||
if (
|
|
||||||
typeof strategy1.sortOrder === 'number' &&
|
|
||||||
typeof strategy2.sortOrder === 'number'
|
|
||||||
) {
|
|
||||||
return strategy1.sortOrder - strategy2.sortOrder;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
})
|
|
||||||
.map(({ title, sortOrder, ...strategy }) => ({
|
|
||||||
...strategy,
|
|
||||||
...(title ? { title } : {}),
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildPlaygroundFeaturesFromRows = (
|
|
||||||
rows: any[],
|
|
||||||
dependentFeaturesEnabled: boolean,
|
|
||||||
featureQuery?: IFeatureToggleQuery,
|
|
||||||
): FeatureConfigurationClient[] => {
|
|
||||||
let result = rows.reduce((acc, r) => {
|
|
||||||
const feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
|
|
||||||
strategies: [],
|
|
||||||
};
|
|
||||||
if (isUnseenStrategyRow(feature, r) && !r.strategy_disabled) {
|
|
||||||
feature.strategies?.push(rowToStrategy(r));
|
|
||||||
}
|
|
||||||
if (isNewTag(feature, r)) {
|
|
||||||
addTag(feature, r);
|
|
||||||
}
|
|
||||||
if (featureQuery?.inlineSegmentConstraints && r.segment_id) {
|
|
||||||
addSegmentToStrategy(feature, r);
|
|
||||||
} else if (!featureQuery?.inlineSegmentConstraints && r.segment_id) {
|
|
||||||
addSegmentIdsToStrategy(feature, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.impressionData = r.impression_data;
|
|
||||||
feature.enabled = !!r.enabled;
|
|
||||||
feature.name = r.name;
|
|
||||||
feature.description = r.description;
|
|
||||||
feature.project = r.project;
|
|
||||||
feature.stale = r.stale;
|
|
||||||
feature.type = r.type;
|
|
||||||
feature.lastSeenAt = r.last_seen_at;
|
|
||||||
feature.variants = r.variants || [];
|
|
||||||
feature.project = r.project;
|
|
||||||
feature.lastSeenAt = r.last_seen_at;
|
|
||||||
|
|
||||||
if (r.parent && dependentFeaturesEnabled) {
|
|
||||||
feature.dependencies = feature.dependencies || [];
|
|
||||||
feature.dependencies.push({
|
|
||||||
feature: r.parent,
|
|
||||||
enabled: r.parent_enabled,
|
|
||||||
...(r.parent_enabled ? { variants: r.parent_variants } : {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
acc[r.name] = feature;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
result = Object.values(result).map(({ strategies, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
strategies: strategies
|
|
||||||
?.sort((strategy1, strategy2) => {
|
|
||||||
if (
|
|
||||||
typeof strategy1.sortOrder === 'number' &&
|
|
||||||
typeof strategy2.sortOrder === 'number'
|
|
||||||
) {
|
|
||||||
return strategy1.sortOrder - strategy2.sortOrder;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
})
|
|
||||||
.map(({ title, sortOrder, ...strategy }) => ({
|
|
||||||
...strategy,
|
|
||||||
...(title ? { title } : {}),
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Difference {
|
|
||||||
index: (string | number)[];
|
|
||||||
reason: string;
|
|
||||||
valueA: any;
|
|
||||||
valueB: any;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user