mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-18 13:48:58 +02:00
slim down rough idea
This commit is contained in:
parent
d18c730563
commit
e8d2990c6c
@ -1,65 +1,29 @@
|
||||
import type EventEmitter from 'events';
|
||||
import groupBy from 'lodash.groupby';
|
||||
import {
|
||||
FEATURE_CREATED,
|
||||
CLIENT_METRICS_ADDED,
|
||||
FEATURE_ARCHIVED,
|
||||
FEATURE_REVIVED,
|
||||
FEATURE_ENVIRONMENT_ENABLED,
|
||||
CLIENT_METRICS,
|
||||
} from '../events/index.js';
|
||||
import type {
|
||||
FeatureLifecycleView,
|
||||
IFeatureLifecycleStore,
|
||||
NewStage,
|
||||
} from '../features/feature-lifecycle/feature-lifecycle-store-type.js';
|
||||
import type { IClientMetricsEnv } from '../features/metrics/client-metrics/client-metrics-store-v2-type.js';
|
||||
import { STAGE_ENTERED } from '../metric-events.js';
|
||||
import type { Logger } from '../logger.js';
|
||||
import {
|
||||
FeatureCompletedEvent,
|
||||
FeatureUncompletedEvent,
|
||||
type IAuditUser,
|
||||
type IEnvironmentStore,
|
||||
type IFeatureEnvironmentStore,
|
||||
type IFlagResolver,
|
||||
type IUnleashConfig,
|
||||
type IEventStore,
|
||||
import type {
|
||||
IFlagResolver,
|
||||
IUnleashConfig,
|
||||
IEventStore,
|
||||
} from '../types/index.js';
|
||||
import type EventService from '../features/events/event-service.js';
|
||||
import type { FeatureLifecycleCompletedSchema } from '../openapi/index.js';
|
||||
|
||||
export class FeatureLifecycleService {
|
||||
private eventStore: IEventStore;
|
||||
|
||||
private featureLifecycleStore: IFeatureLifecycleStore;
|
||||
|
||||
private environmentStore: IEnvironmentStore;
|
||||
|
||||
private featureEnvironmentStore: IFeatureEnvironmentStore;
|
||||
|
||||
private flagResolver: IFlagResolver;
|
||||
|
||||
private eventBus: EventEmitter;
|
||||
|
||||
private eventService: EventService;
|
||||
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
{
|
||||
eventStore,
|
||||
featureLifecycleStore,
|
||||
environmentStore,
|
||||
featureEnvironmentStore,
|
||||
}: {
|
||||
eventStore: IEventStore;
|
||||
environmentStore: IEnvironmentStore;
|
||||
featureLifecycleStore: IFeatureLifecycleStore;
|
||||
featureEnvironmentStore: IFeatureEnvironmentStore;
|
||||
},
|
||||
{
|
||||
eventService,
|
||||
}: {
|
||||
eventService: EventService;
|
||||
},
|
||||
{
|
||||
flagResolver,
|
||||
@ -68,159 +32,26 @@ export class FeatureLifecycleService {
|
||||
}: Pick<IUnleashConfig, 'flagResolver' | 'eventBus' | 'getLogger'>,
|
||||
) {
|
||||
this.eventStore = eventStore;
|
||||
this.featureLifecycleStore = featureLifecycleStore;
|
||||
this.environmentStore = environmentStore;
|
||||
this.featureEnvironmentStore = featureEnvironmentStore;
|
||||
this.flagResolver = flagResolver;
|
||||
this.eventBus = eventBus;
|
||||
this.eventService = eventService;
|
||||
this.logger = getLogger(
|
||||
'feature-lifecycle/feature-lifecycle-service.ts',
|
||||
);
|
||||
}
|
||||
|
||||
listen() {
|
||||
this.featureLifecycleStore.backfill();
|
||||
this.eventStore.on(FEATURE_CREATED, async (event) => {
|
||||
await this.featureInitialized(event.featureName);
|
||||
this.eventStore.on(FEATURE_ENVIRONMENT_ENABLED, async (event) => {
|
||||
if (this.flagResolver.isEnabled('paygTrialEvents')) {
|
||||
// report to HS
|
||||
}
|
||||
});
|
||||
this.eventBus.on(
|
||||
CLIENT_METRICS_ADDED,
|
||||
async (events: IClientMetricsEnv[]) => {
|
||||
if (events.length > 0) {
|
||||
const groupedByEnvironment = groupBy(events, 'environment');
|
||||
|
||||
for (const [environment, metrics] of Object.entries(
|
||||
groupedByEnvironment,
|
||||
)) {
|
||||
const features = metrics.map(
|
||||
(metric) => metric.featureName,
|
||||
);
|
||||
await this.featuresReceivedMetrics(
|
||||
features,
|
||||
environment,
|
||||
);
|
||||
}
|
||||
CLIENT_METRICS, // or CLIENT_METRICS_ADDED? 🤷
|
||||
async (event) => {
|
||||
if (this.flagResolver.isEnabled('paygTrialEvents')) {
|
||||
// todo: report to HS
|
||||
}
|
||||
},
|
||||
);
|
||||
this.eventStore.on(FEATURE_ARCHIVED, async (event) => {
|
||||
await this.featureArchived(event.featureName);
|
||||
});
|
||||
this.eventStore.on(FEATURE_REVIVED, async (event) => {
|
||||
await this.featureRevived(event.featureName);
|
||||
});
|
||||
}
|
||||
|
||||
async getFeatureLifecycle(feature: string): Promise<FeatureLifecycleView> {
|
||||
return this.featureLifecycleStore.get(feature);
|
||||
}
|
||||
|
||||
private async featureInitialized(feature: string) {
|
||||
const result = await this.featureLifecycleStore.insert([
|
||||
{ feature, stage: 'initial' },
|
||||
]);
|
||||
this.recordStagesEntered(result);
|
||||
}
|
||||
|
||||
private async stageReceivedMetrics(
|
||||
features: string[],
|
||||
stage: 'live' | 'pre-live',
|
||||
) {
|
||||
const newlyEnteredStages = await this.featureLifecycleStore.insert(
|
||||
features.map((feature) => ({ feature, stage })),
|
||||
);
|
||||
this.recordStagesEntered(newlyEnteredStages);
|
||||
}
|
||||
|
||||
private recordStagesEntered(newlyEnteredStages: NewStage[]) {
|
||||
newlyEnteredStages.forEach(({ stage, feature }) => {
|
||||
this.eventBus.emit(STAGE_ENTERED, { stage, feature });
|
||||
});
|
||||
}
|
||||
|
||||
private async featuresReceivedMetrics(
|
||||
features: string[],
|
||||
environment: string,
|
||||
) {
|
||||
try {
|
||||
const env = await this.environmentStore.get(environment);
|
||||
|
||||
if (!env) {
|
||||
return;
|
||||
}
|
||||
await this.stageReceivedMetrics(features, 'pre-live');
|
||||
if (env.type === 'production') {
|
||||
const featureEnv =
|
||||
await this.featureEnvironmentStore.getAllByFeatures(
|
||||
features,
|
||||
env.name,
|
||||
);
|
||||
const enabledFeatures = featureEnv
|
||||
.filter((feature) => feature.enabled)
|
||||
.map((feature) => feature.featureName);
|
||||
await this.stageReceivedMetrics(enabledFeatures, 'live');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.warn(
|
||||
`Error handling ${features.length} metrics in ${environment}`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async featureCompleted(
|
||||
feature: string,
|
||||
projectId: string,
|
||||
status: FeatureLifecycleCompletedSchema,
|
||||
auditUser: IAuditUser,
|
||||
) {
|
||||
const result = await this.featureLifecycleStore.insert([
|
||||
{
|
||||
feature,
|
||||
stage: 'completed',
|
||||
status: status.status,
|
||||
statusValue: status.statusValue,
|
||||
},
|
||||
]);
|
||||
this.recordStagesEntered(result);
|
||||
await this.eventService.storeEvent(
|
||||
new FeatureCompletedEvent({
|
||||
project: projectId,
|
||||
featureName: feature,
|
||||
data: { ...status, kept: status.status === 'kept' },
|
||||
auditUser,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async featureUncompleted(
|
||||
feature: string,
|
||||
projectId: string,
|
||||
auditUser: IAuditUser,
|
||||
) {
|
||||
await this.featureLifecycleStore.deleteStage({
|
||||
feature,
|
||||
stage: 'completed',
|
||||
});
|
||||
await this.eventService.storeEvent(
|
||||
new FeatureUncompletedEvent({
|
||||
project: projectId,
|
||||
featureName: feature,
|
||||
auditUser,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async featureArchived(feature: string) {
|
||||
const result = await this.featureLifecycleStore.insert([
|
||||
{ feature, stage: 'archived' },
|
||||
]);
|
||||
this.recordStagesEntered(result);
|
||||
}
|
||||
|
||||
private async featureRevived(feature: string) {
|
||||
await this.featureLifecycleStore.delete(feature);
|
||||
await this.featureInitialized(feature);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user