1
0
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:
Fredrik Strand Oseberg 2023-10-16 14:19:46 +02:00 committed by GitHub
parent 1d27cfdc54
commit 9e493f56a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 200 additions and 225 deletions

View File

@ -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);
};
}

View File

@ -17,12 +17,9 @@ import { NameExistsError } from '../../error';
import { DEFAULT_ENV } from '../../../lib/util';
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 { IFlagResolver } from '../../../lib/types';
import { FeatureToggleRowConverter } from './converters/feature-toggle-row-converter';
export type EnvironmentFeatureNames = { [key: string]: string[] };
@ -65,9 +62,12 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
private timer: Function;
private featureToggleRowConverter: FeatureToggleRowConverter;
constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
this.db = db;
this.logger = getLogger('feature-toggle-store.ts');
this.featureToggleRowConverter = new FeatureToggleRowConverter();
this.timer = (action) =>
metricsHelper.wrapTimer(eventBus, DB_TIME, {
store: 'feature-toggle',
@ -147,7 +147,10 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
builder.getSelectColumns(),
);
return buildFeatureToggleListFromRows(rows, featureQuery);
return this.featureToggleRowConverter.buildFeatureToggleListFromRows(
rows,
featureQuery,
);
}
async getPlaygroundFeatures(
@ -171,7 +174,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
builder.getSelectColumns(),
);
return buildPlaygroundFeaturesFromRows(
return this.featureToggleRowConverter.buildPlaygroundFeaturesFromRows(
rows,
dependentFeaturesEnabled,
featureQuery,

View File

@ -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;
}