1
0
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:
Jaanus Sellin 2024-05-09 12:05:19 +03:00 committed by GitHub
parent 97d702afeb
commit 79739a1d7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 75 additions and 20 deletions

View File

@ -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;
}

View File

@ -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;

View File

@ -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';

View File

@ -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,
);

View File

@ -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(

View File

@ -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[];

View File

@ -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> {