mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: add completed status backend (#7022)
1. Implemented controller, service, store for status saving
This commit is contained in:
parent
97d702afeb
commit
79739a1d7f
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
import type { FeatureLifecycleCompletedSchemaStatus } from './featureLifecycleCompletedSchemaStatus';
|
||||
|
||||
/**
|
||||
* A feature that has been marked as completed
|
||||
*/
|
||||
export interface FeatureLifecycleCompletedSchema {
|
||||
/** The status of the feature after it has been marked as completed */
|
||||
status: FeatureLifecycleCompletedSchemaStatus;
|
||||
/** The metadata value passed in together with status */
|
||||
statusValue?: string;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Generated by Orval
|
||||
* Do not edit manually.
|
||||
* See `gen:api` script in package.json
|
||||
*/
|
||||
|
||||
/**
|
||||
* The status of the feature after it has been marked as completed
|
||||
*/
|
||||
export type FeatureLifecycleCompletedSchemaStatus =
|
||||
(typeof FeatureLifecycleCompletedSchemaStatus)[keyof typeof FeatureLifecycleCompletedSchemaStatus];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const FeatureLifecycleCompletedSchemaStatus = {
|
||||
kept: 'kept',
|
||||
discarded: 'discarded',
|
||||
} as const;
|
@ -540,6 +540,8 @@ export * from './featureEnvironmentMetricsSchemaVariants';
|
||||
export * from './featureEnvironmentSchema';
|
||||
export * from './featureEventsSchema';
|
||||
export * from './featureEventsSchemaVersion';
|
||||
export * from './featureLifecycleCompletedSchema';
|
||||
export * from './featureLifecycleCompletedSchemaStatus';
|
||||
export * from './featureLifecycleSchema';
|
||||
export * from './featureLifecycleSchemaItem';
|
||||
export * from './featureLifecycleSchemaItemStage';
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
createRequestSchema,
|
||||
createResponseSchema,
|
||||
emptyResponse,
|
||||
type FeatureLifecycleCompletedSchema,
|
||||
featureLifecycleSchema,
|
||||
type FeatureLifecycleSchema,
|
||||
getStandardResponses,
|
||||
@ -132,7 +133,11 @@ export default class FeatureLifecycleController extends Controller {
|
||||
}
|
||||
|
||||
async complete(
|
||||
req: IAuthRequest<FeatureLifecycleParams>,
|
||||
req: IAuthRequest<
|
||||
FeatureLifecycleParams,
|
||||
any,
|
||||
FeatureLifecycleCompletedSchema
|
||||
>,
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
if (!this.flagResolver.isEnabled('featureLifecycle')) {
|
||||
@ -140,8 +145,11 @@ export default class FeatureLifecycleController extends Controller {
|
||||
}
|
||||
const { featureName } = req.params;
|
||||
|
||||
const status = req.body;
|
||||
|
||||
await this.featureLifecycleService.featureCompleted(
|
||||
featureName,
|
||||
status,
|
||||
req.audit,
|
||||
);
|
||||
|
||||
|
@ -23,6 +23,7 @@ import type { Logger } from '../../logger';
|
||||
import type EventService from '../events/event-service';
|
||||
import type { ValidatedClientMetrics } from '../metrics/shared/schema';
|
||||
import { differenceInMinutes } from 'date-fns';
|
||||
import type { FeatureLifecycleCompletedSchema } from '../../openapi';
|
||||
|
||||
export const STAGE_ENTERED = 'STAGE_ENTERED';
|
||||
|
||||
@ -167,11 +168,17 @@ export class FeatureLifecycleService extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public async featureCompleted(feature: string, auditUser: IAuditUser) {
|
||||
public async featureCompleted(
|
||||
feature: string,
|
||||
status: FeatureLifecycleCompletedSchema,
|
||||
auditUser: IAuditUser,
|
||||
) {
|
||||
await this.featureLifecycleStore.insert([
|
||||
{
|
||||
feature,
|
||||
stage: 'completed',
|
||||
status: status.status,
|
||||
statusValue: status.statusValue,
|
||||
},
|
||||
]);
|
||||
await this.eventService.storeEvent(
|
||||
|
@ -3,6 +3,8 @@ import type { IFeatureLifecycleStage, StageName } from '../../types';
|
||||
export type FeatureLifecycleStage = {
|
||||
feature: string;
|
||||
stage: StageName;
|
||||
status?: string;
|
||||
statusValue?: string;
|
||||
};
|
||||
|
||||
export type FeatureLifecycleView = IFeatureLifecycleStage[];
|
||||
|
@ -29,28 +29,31 @@ export class FeatureLifecycleStore implements IFeatureLifecycleStore {
|
||||
async insert(
|
||||
featureLifecycleStages: FeatureLifecycleStage[],
|
||||
): Promise<void> {
|
||||
const joinedLifecycleStages = featureLifecycleStages
|
||||
.map((stage) => `('${stage.feature}', '${stage.stage}')`)
|
||||
.join(', ');
|
||||
const existingFeatures = await this.db('features')
|
||||
.select('name')
|
||||
.whereIn(
|
||||
'name',
|
||||
featureLifecycleStages.map((stage) => stage.feature),
|
||||
);
|
||||
const existingFeaturesSet = new Set(
|
||||
existingFeatures.map((item) => item.name),
|
||||
);
|
||||
const validStages = featureLifecycleStages.filter((stage) =>
|
||||
existingFeaturesSet.has(stage.feature),
|
||||
);
|
||||
|
||||
const query = this.db
|
||||
.with(
|
||||
'new_stages',
|
||||
this.db.raw(`
|
||||
SELECT v.feature, v.stage
|
||||
FROM (VALUES ${joinedLifecycleStages}) AS v(feature, stage)
|
||||
JOIN features ON features.name = v.feature
|
||||
LEFT JOIN feature_lifecycles ON feature_lifecycles.feature = v.feature AND feature_lifecycles.stage = v.stage
|
||||
WHERE feature_lifecycles.feature IS NULL AND feature_lifecycles.stage IS NULL
|
||||
`),
|
||||
await this.db('feature_lifecycles')
|
||||
.insert(
|
||||
validStages.map((stage) => ({
|
||||
feature: stage.feature,
|
||||
stage: stage.stage,
|
||||
status: stage.status,
|
||||
status_value: stage.statusValue,
|
||||
})),
|
||||
)
|
||||
.insert((query) => {
|
||||
query.select('feature', 'stage').from('new_stages');
|
||||
})
|
||||
.into('feature_lifecycles')
|
||||
.returning('*')
|
||||
.onConflict(['feature', 'stage'])
|
||||
.ignore();
|
||||
await query;
|
||||
}
|
||||
|
||||
async get(feature: string): Promise<FeatureLifecycleView> {
|
||||
|
Loading…
Reference in New Issue
Block a user