UNPKG

unleash-server

Version:

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

232 lines • 10.6 kB
import { CRUDStore } from '../../db/crud/crud-store.js'; import { defaultToRow } from '../../db/crud/default-mappings.js'; const TABLE = 'release_plan_definitions'; const selectColumns = [ 'rpd.id AS planId', 'rpd.discriminator AS planDiscriminator', 'rpd.name AS planName', 'rpd.description as planDescription', 'rpd.feature_name as planFeatureName', 'rpd.environment as planEnvironment', 'rpd.created_by_user_id as planCreatedByUserId', 'rpd.created_at as planCreatedAt', 'rpd.active_milestone_id as planActiveMilestoneId', 'rpd.release_plan_template_id as planTemplateId', 'mi.id AS milestoneId', 'mi.name AS milestoneName', 'mi.sort_order AS milestoneSortOrder', 'ms.id AS strategyId', 'ms.sort_order AS strategySortOrder', 'ms.title AS strategyTitle', 'ms.strategy_name AS strategyName', 'ms.parameters AS strategyParameters', 'ms.constraints AS strategyConstraints', 'ms.variants AS strategyVariants', 'mss.segment_id AS segmentId', ]; const processReleasePlanRows = (templateRows) => templateRows.reduce((acc, { planId, planDiscriminator, planName, planDescription, planFeatureName, planEnvironment, planCreatedByUserId, planCreatedAt, planActiveMilestoneId, planTemplateId, milestoneId, milestoneName, milestoneSortOrder, strategyId, strategySortOrder, strategyTitle, strategyName, strategyParameters, strategyConstraints, strategyVariants, segmentId, }) => { let plan = acc.find(({ id }) => id === planId); if (!plan) { plan = { id: planId, discriminator: planDiscriminator, name: planName, description: planDescription, featureName: planFeatureName, environment: planEnvironment, createdByUserId: planCreatedByUserId, createdAt: planCreatedAt, activeMilestoneId: planActiveMilestoneId, releasePlanTemplateId: planTemplateId, milestones: [], }; acc.push(plan); } if (!milestoneId) { return acc; } let milestone = plan.milestones.find(({ id }) => id === milestoneId); if (!milestone) { milestone = { id: milestoneId, name: milestoneName, sortOrder: milestoneSortOrder, strategies: [], releasePlanDefinitionId: planId, }; plan.milestones.push(milestone); } if (!strategyId) { return acc; } let strategy = milestone.strategies?.find(({ id }) => id === strategyId); if (!strategy) { strategy = { id: strategyId, milestoneId: milestoneId, sortOrder: strategySortOrder, title: strategyTitle, strategyName: strategyName, parameters: strategyParameters ?? {}, constraints: strategyConstraints, variants: strategyVariants ?? [], segments: [], }; milestone.strategies = [ ...(milestone.strategies || []), strategy, ]; } if (segmentId) { strategy.segments = [...(strategy.segments || []), segmentId]; } return acc; }, []); const processMilestoneStrategyRows = (rows) => { return rows.map((row) => { return { id: row.id, sortOrder: row.sort_order, title: row.title, strategyName: row.strategy_name, parameters: row.parameters, constraints: row.constraints, variants: row.variants, segments: [], }; }); }; export class ReleasePlanStore extends CRUDStore { constructor(db, config) { super(TABLE, db, config, { fromRow: (row) => { return { id: row.id, discriminator: row.discriminator, name: row.name, description: row.description, featureName: row.feature_name, environment: row.environment, createdByUserId: row.created_by_user_id, createdAt: row.created_at, activeMilestoneId: row.active_milestone_id, releasePlanTemplateId: row.release_plan_template_id, milestones: [], }; }, toRow: (item) => ({ ...defaultToRow(item), discriminator: 'plan', }), }); } async count(query) { let countQuery = this.db(this.tableName) .where('discriminator', 'plan') .count('*'); if (query) { countQuery = countQuery.where(this.toRow(query)); } const { count } = (await countQuery.first()) ?? { count: 0 }; return Number(count); } async getByFeatureFlagEnvironmentAndPlanId(featureName, environment, planId) { const endTimer = this.timer('getByFeatureFlagEnvironmentAndPlanId'); const rows = await this.db(`${this.tableName} AS rpd`) .where('rpd.discriminator', 'plan') .andWhere('rpd.feature_name', featureName) .andWhere('rpd.environment', environment) .andWhere('rpd.id', planId) .leftJoin('milestones AS mi', 'mi.release_plan_definition_id', 'rpd.id') .leftJoin('milestone_strategies AS ms', 'ms.milestone_id', 'mi.id') .leftJoin('milestone_strategy_segments AS mss', 'mss.milestone_strategy_id', 'ms.id') .orderBy('mi.sort_order', 'asc') .orderBy('ms.sort_order', 'asc') .select(selectColumns); endTimer(); return processReleasePlanRows(rows)[0]; } async getByPlanId(planId) { const endTimer = this.timer('getByPlanId'); const rows = await this.db(`${this.tableName} AS rpd`) .where('rpd.discriminator', 'plan') .andWhere('rpd.id', planId) .leftJoin('milestones AS mi', 'mi.release_plan_definition_id', 'rpd.id') .leftJoin('milestone_strategies AS ms', 'ms.milestone_id', 'mi.id') .leftJoin('milestone_strategy_segments AS mss', 'mss.milestone_strategy_id', 'ms.id') .orderBy('mi.sort_order', 'asc') .orderBy('ms.sort_order', 'asc') .select(selectColumns); endTimer(); const releasePlans = processReleasePlanRows(rows); if (releasePlans.length === 0) { return; } return releasePlans[0]; } async activateStrategiesForMilestone(planId, auditUser) { const endTimer = this.timer('activateStrategiesForMilestone'); const rows = await this.db.raw(` INSERT INTO feature_strategies(id, feature_name, project_name, environment, strategy_name, parameters, constraints, sort_order, title, variants, created_by_user_id, milestone_id) SELECT ms.id, rpd.feature_name, feature.project, rpd.environment, ms.strategy_name, ms.parameters, ms.constraints, ms.sort_order, ms.title, ms.variants, :userId, ms.milestone_id FROM milestone_strategies AS ms LEFT JOIN milestones AS m ON m.id = ms.milestone_id LEFT JOIN release_plan_definitions AS rpd ON rpd.active_milestone_id = m.id AND rpd.discriminator = 'plan' LEFT JOIN features AS feature ON rpd.feature_name = feature.name WHERE rpd.id = :planId AND rpd.discriminator = 'plan' ON CONFLICT DO NOTHING RETURNING * `, { userId: auditUser.id, planId }); endTimer(); return processMilestoneStrategyRows(rows.rows); } async deactivateStrategiesForMilestone(templateId) { const endTimer = this.timer('deactivateStrategiesForMilestone'); const deletedRows = await this.db.raw(`DELETE FROM feature_strategies WHERE milestone_id = (SELECT active_milestone_id FROM release_plan_definitions WHERE id = :templateId) RETURNING *`, { templateId }); endTimer(); return processMilestoneStrategyRows(deletedRows.rows); } async getActiveStrategiesForPlan(planId) { const endTimer = this.timer('getActiveStrategiesForPlan'); const rows = await this.db .select('id', 'strategy_name', 'sort_order', 'title', 'parameters', 'variants', 'constraints') .from('feature_strategies') .whereRaw(`milestone_id IN (SELECT active_milestone_id FROM release_plan_definitions WHERE id = :id)`, { id: planId }); endTimer(); return processMilestoneStrategyRows(rows); } async activateStrategySegmentsForMilestone(milestone_id) { const endTimer = this.timer('activateStrategySegmentsForMilestone'); const rows = await this.db.raw(` INSERT INTO feature_strategy_segment(feature_strategy_id, segment_id) SELECT milestone_strategy_id, segment_id FROM milestone_strategy_segments WHERE milestone_strategy_id IN (SELECT id FROM milestone_strategies WHERE milestone_id = :milestone_id) RETURNING segment_id `, { milestone_id }); endTimer(); return rows.rows.map((row) => row.segment_id); } async featureAndEnvironmentHasPlan(featureName, environment) { const endTimer = this.timer('featureAndEnvironmentHasPlan'); const result = await this.db.raw(`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE discriminator = 'plan' AND feature_name = :featureName AND environment = :environment) AS present`, { featureName, environment }); const { present } = result.rows[0]; endTimer(); return present; } async getByEnvironmentAndProjects(environment, projects) { const endTimer = this.timer('getByEnvironmentAndProjects'); const rows = await this.db(`${this.tableName} AS rpd`) .join('features AS f', 'f.name', 'rpd.feature_name') .where('rpd.discriminator', 'plan') .andWhere('rpd.environment', environment) .whereIn('f.project', projects) .leftJoin('milestones AS mi', 'mi.release_plan_definition_id', 'rpd.id') .leftJoin('milestone_strategies AS ms', 'ms.milestone_id', 'mi.id') .leftJoin('milestone_strategy_segments AS mss', 'mss.milestone_strategy_id', 'ms.id') .orderBy('mi.sort_order', 'asc') .orderBy('ms.sort_order', 'asc') .select(selectColumns); endTimer(); return processReleasePlanRows(rows); } } //# sourceMappingURL=release-plan-store.js.map