UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.

198 lines • 8.18 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const metrics_helper_1 = __importDefault(require("../util/metrics-helper")); const metric_events_1 = require("../metric-events"); const constants_1 = require("../util/constants"); const feature_toggle_store_1 = __importDefault(require("./feature-toggle-store")); const ensureStringValue_1 = require("../util/ensureStringValue"); const map_values_1 = require("../util/map-values"); class FeatureToggleClientStore { constructor(db, eventBus, getLogger, inlineSegmentConstraints, flagResolver) { this.db = db; this.logger = getLogger('feature-toggle-client-store.ts'); this.inlineSegmentConstraints = inlineSegmentConstraints; this.timer = (action) => metrics_helper_1.default.wrapTimer(eventBus, metric_events_1.DB_TIME, { store: 'feature-toggle', action, }); this.flagResolver = flagResolver; } async getAll({ featureQuery, archived, isAdmin, includeStrategyIds, userId, }) { const environment = featureQuery?.environment || constants_1.DEFAULT_ENV; const stopTimer = this.timer('getFeatureAdmin'); let selectColumns = [ 'features.name as name', 'features.description as description', 'features.type as type', 'features.project as project', 'features.stale as stale', 'features.impression_data as impression_data', 'fe.variants as variants', 'features.created_at as created_at', 'features.last_seen_at as last_seen_at', 'fe.enabled as enabled', 'fe.environment as environment', 'fs.id as strategy_id', 'fs.strategy_name as strategy_name', 'fs.parameters as parameters', 'fs.constraints as constraints', 'segments.id as segment_id', 'segments.constraints as segment_constraints', ]; let query = this.db('features') .modify(feature_toggle_store_1.default.filterByArchived, archived) .leftJoin(this.db('feature_strategies') .select('*') .where({ environment }) .as('fs'), 'fs.feature_name', 'features.name') .leftJoin(this.db('feature_environments') .select('feature_name', 'enabled', 'environment', 'variants') .where({ environment }) .as('fe'), 'fe.feature_name', 'features.name') .leftJoin('feature_strategy_segment as fss', `fss.feature_strategy_id`, `fs.id`) .leftJoin('segments', `segments.id`, `fss.segment_id`); if (isAdmin) { query = query.leftJoin('feature_tag as ft', 'ft.feature_name', 'features.name'); selectColumns = [ ...selectColumns, 'ft.tag_value as tag_value', 'ft.tag_type as tag_type', ]; if (userId && this.flagResolver.isEnabled('favorites')) { query = query.leftJoin(`favorite_features`, function () { this.on('favorite_features.feature', 'features.name').andOnVal('favorite_features.user_id', '=', userId); }); selectColumns = [ ...selectColumns, this.db.raw('favorite_features.feature is not null as favorite'), ]; } } query = query.select(selectColumns); if (featureQuery) { if (featureQuery.tag) { const tagQuery = this.db .from('feature_tag') .select('feature_name') .whereIn(['tag_type', 'tag_value'], featureQuery.tag); query = query.whereIn('features.name', tagQuery); } if (featureQuery.project) { query = query.whereIn('project', featureQuery.project); } if (featureQuery.namePrefix) { query = query.where('features.name', 'like', `${featureQuery.namePrefix}%`); } } const rows = await query; stopTimer(); const featureToggles = rows.reduce((acc, r) => { let feature = acc[r.name] ?? { strategies: [], }; if (this.isUnseenStrategyRow(feature, r)) { feature.strategies.push(FeatureToggleClientStore.rowToStrategy(r)); } if (this.isNewTag(feature, r)) { this.addTag(feature, r); } if (featureQuery?.inlineSegmentConstraints && r.segment_id) { this.addSegmentToStrategy(feature, r); } else if (!featureQuery?.inlineSegmentConstraints && r.segment_id) { this.addSegmentIdsToStrategy(feature, r); } feature.impressionData = r.impression_data; feature.enabled = !!r.enabled; feature.name = r.name; feature.favorite = r.favorite; feature.description = r.description; feature.project = r.project; feature.stale = r.stale; feature.type = r.type; feature.variants = r.variants || []; feature.project = r.project; if (isAdmin) { feature.lastSeenAt = r.last_seen_at; feature.createdAt = r.created_at; } acc[r.name] = feature; return acc; }, {}); const features = Object.values(featureToggles); if (!isAdmin && !includeStrategyIds) { // We should not send strategy IDs from the client API, // as this breaks old versions of the Go SDK (at least). FeatureToggleClientStore.removeIdsFromStrategies(features); } return features; } static rowToStrategy(row) { return { id: row.strategy_id, name: row.strategy_name, constraints: row.constraints || [], parameters: (0, map_values_1.mapValues)(row.parameters || {}, ensureStringValue_1.ensureStringValue), }; } static rowToTag(row) { return { value: row.tag_value, type: row.tag_type, }; } static removeIdsFromStrategies(features) { features.forEach((feature) => { feature.strategies.forEach((strategy) => { delete strategy.id; }); }); } isUnseenStrategyRow(feature, row) { return (row.strategy_id && !feature.strategies.find((s) => s.id === row.strategy_id)); } addTag(feature, row) { const tags = feature.tags || []; const newTag = FeatureToggleClientStore.rowToTag(row); feature.tags = [...tags, newTag]; } isNewTag(feature, row) { return (row.tag_type && row.tag_value && !feature.tags?.some((tag) => tag.type === row.tag_type && tag.value === row.tag_value)); } addSegmentToStrategy(feature, row) { feature.strategies .find((s) => s.id === row.strategy_id) ?.constraints.push(...row.segment_constraints); } addSegmentIdsToStrategy(feature, row) { 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); } async getClient(featureQuery, includeStrategyIds) { return this.getAll({ featureQuery, archived: false, isAdmin: false, includeStrategyIds, }); } async getAdmin({ featureQuery, userId, archived, }) { return this.getAll({ featureQuery, archived, isAdmin: true, userId }); } } exports.default = FeatureToggleClientStore; module.exports = FeatureToggleClientStore; //# sourceMappingURL=feature-toggle-client-store.js.map