mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-05 17:53:12 +02:00
fix: lifecycle metrics will now be posted across all environments (#10586)
This commit is contained in:
parent
547f7ac14e
commit
2442e5c973
@ -8,8 +8,10 @@ import {
|
||||
FeatureCompletedEvent,
|
||||
FeatureUncompletedEvent,
|
||||
type IAuditUser,
|
||||
type IEnvironment,
|
||||
type IEnvironmentStore,
|
||||
type IEventStore,
|
||||
type IFeatureEnvironment,
|
||||
type IFeatureEnvironmentStore,
|
||||
type IFlagResolver,
|
||||
type IUnleashConfig,
|
||||
@ -88,7 +90,13 @@ export class FeatureLifecycleService {
|
||||
CLIENT_METRICS_ADDED,
|
||||
async (events: IClientMetricsEnv[]) => {
|
||||
if (events.length > 0) {
|
||||
const groupedByEnvironment = groupBy(events, 'environment');
|
||||
if (this.flagResolver.isEnabled('optimizeLifecycle')) {
|
||||
await this.handleBulkMetrics(events);
|
||||
} else {
|
||||
const groupedByEnvironment = groupBy(
|
||||
events,
|
||||
'environment',
|
||||
);
|
||||
|
||||
for (const [environment, metrics] of Object.entries(
|
||||
groupedByEnvironment,
|
||||
@ -102,6 +110,7 @@ export class FeatureLifecycleService {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
this.eventStore.on(FEATURE_ARCHIVED, async (event) => {
|
||||
@ -169,6 +178,140 @@ export class FeatureLifecycleService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized bulk processing: reduces DB calls from O(4 * environments) to O(3) by batching all data fetches and processing in-memory
|
||||
*/
|
||||
private async handleBulkMetrics(events: IClientMetricsEnv[]) {
|
||||
try {
|
||||
const { environments, allFeatures } =
|
||||
this.extractUniqueEnvironmentsAndFeatures(events);
|
||||
const envMap = await this.buildEnvironmentMap();
|
||||
const featureEnvMap =
|
||||
await this.buildFeatureEnvironmentMap(allFeatures);
|
||||
const allStagesToInsert = this.determineLifecycleStages(
|
||||
events,
|
||||
environments,
|
||||
envMap,
|
||||
featureEnvMap,
|
||||
);
|
||||
|
||||
if (allStagesToInsert.length > 0) {
|
||||
const newlyEnteredStages =
|
||||
await this.featureLifecycleStore.insert(allStagesToInsert);
|
||||
this.recordStagesEntered(newlyEnteredStages);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.warn(
|
||||
`Error handling bulk metrics for ${events.length} events`,
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private extractUniqueEnvironmentsAndFeatures(events: IClientMetricsEnv[]) {
|
||||
const environments = [...new Set(events.map((e) => e.environment))];
|
||||
const allFeatures = [...new Set(events.map((e) => e.featureName))];
|
||||
return { environments, allFeatures };
|
||||
}
|
||||
|
||||
private async buildEnvironmentMap(): Promise<Map<string, IEnvironment>> {
|
||||
const allEnvs = await this.environmentStore.getAll();
|
||||
return new Map(allEnvs.map((env) => [env.name, env]));
|
||||
}
|
||||
|
||||
private async buildFeatureEnvironmentMap(allFeatures: string[]) {
|
||||
const allFeatureEnvs =
|
||||
await this.featureEnvironmentStore.getAllByFeatures(allFeatures);
|
||||
const featureEnvMap = new Map<
|
||||
string,
|
||||
Map<string, IFeatureEnvironment>
|
||||
>();
|
||||
|
||||
allFeatureEnvs.forEach((fe) => {
|
||||
if (!featureEnvMap.has(fe.environment)) {
|
||||
featureEnvMap.set(fe.environment, new Map());
|
||||
}
|
||||
const envMap = featureEnvMap.get(fe.environment);
|
||||
if (envMap) {
|
||||
envMap.set(fe.featureName, fe);
|
||||
}
|
||||
});
|
||||
|
||||
return featureEnvMap;
|
||||
}
|
||||
|
||||
private determineLifecycleStages(
|
||||
events: IClientMetricsEnv[],
|
||||
environments: string[],
|
||||
envMap: Map<string, IEnvironment>,
|
||||
featureEnvMap: Map<string, Map<string, IFeatureEnvironment>>,
|
||||
): Array<{ feature: string; stage: 'pre-live' | 'live' }> {
|
||||
const allStagesToInsert: Array<{
|
||||
feature: string;
|
||||
stage: 'pre-live' | 'live';
|
||||
}> = [];
|
||||
|
||||
for (const environment of environments) {
|
||||
const env = envMap.get(environment);
|
||||
if (!env) continue;
|
||||
|
||||
const envFeatures = this.getFeaturesForEnvironment(
|
||||
events,
|
||||
environment,
|
||||
);
|
||||
allStagesToInsert.push(...this.createPreLiveStages(envFeatures));
|
||||
|
||||
if (env.type === 'production') {
|
||||
const enabledFeatures = this.getEnabledFeaturesForEnvironment(
|
||||
envFeatures,
|
||||
environment,
|
||||
featureEnvMap,
|
||||
);
|
||||
allStagesToInsert.push(
|
||||
...this.createLiveStages(enabledFeatures),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return allStagesToInsert;
|
||||
}
|
||||
|
||||
private getFeaturesForEnvironment(
|
||||
events: IClientMetricsEnv[],
|
||||
environment: string,
|
||||
): string[] {
|
||||
return events
|
||||
.filter((e) => e.environment === environment)
|
||||
.map((e) => e.featureName);
|
||||
}
|
||||
|
||||
private createPreLiveStages(
|
||||
features: string[],
|
||||
): Array<{ feature: string; stage: 'pre-live' }> {
|
||||
return features.map((feature) => ({
|
||||
feature,
|
||||
stage: 'pre-live' as const,
|
||||
}));
|
||||
}
|
||||
|
||||
private createLiveStages(
|
||||
features: string[],
|
||||
): Array<{ feature: string; stage: 'live' }> {
|
||||
return features.map((feature) => ({ feature, stage: 'live' as const }));
|
||||
}
|
||||
|
||||
private getEnabledFeaturesForEnvironment(
|
||||
features: string[],
|
||||
environment: string,
|
||||
featureEnvMap: Map<string, Map<string, IFeatureEnvironment>>,
|
||||
): string[] {
|
||||
const envFeatureEnvs = featureEnvMap.get(environment) ?? new Map();
|
||||
return features.filter((feature) => {
|
||||
const fe = envFeatureEnvs.get(feature);
|
||||
return fe?.enabled;
|
||||
});
|
||||
}
|
||||
|
||||
public async featureCompleted(
|
||||
feature: string,
|
||||
projectId: string,
|
||||
|
@ -58,7 +58,8 @@ export type IFlagKey =
|
||||
| 'lifecycleGraphs'
|
||||
| 'addConfiguration'
|
||||
| 'etagByEnv'
|
||||
| 'fetchMode';
|
||||
| 'fetchMode'
|
||||
| 'optimizeLifecycle';
|
||||
|
||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user