mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: flag overview change requests (#9702)
This commit is contained in:
parent
bd11ece873
commit
4130e06d17
@ -71,6 +71,7 @@ class FeatureSearchStore implements IFeatureSearchStore {
|
|||||||
hasEnabledStrategies: r.has_enabled_strategies,
|
hasEnabledStrategies: r.has_enabled_strategies,
|
||||||
yes: Number(r.yes) || 0,
|
yes: Number(r.yes) || 0,
|
||||||
no: Number(r.no) || 0,
|
no: Number(r.no) || 0,
|
||||||
|
changeRequestIds: r.change_request_ids ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,6 +327,43 @@ class FeatureSearchStore implements IFeatureSearchStore {
|
|||||||
'ranked_features.feature_name',
|
'ranked_features.feature_name',
|
||||||
'lifecycle.stage_feature',
|
'lifecycle.stage_feature',
|
||||||
);
|
);
|
||||||
|
if (this.flagResolver.isEnabled('flagsOverviewSearch')) {
|
||||||
|
finalQuery
|
||||||
|
.leftJoin(
|
||||||
|
this.db('change_request_events AS cre')
|
||||||
|
.join(
|
||||||
|
'change_requests AS cr',
|
||||||
|
'cre.change_request_id',
|
||||||
|
'cr.id',
|
||||||
|
)
|
||||||
|
.select('cre.feature')
|
||||||
|
.select(
|
||||||
|
this.db.raw(
|
||||||
|
'array_agg(distinct cre.change_request_id) AS change_request_ids',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.select('cr.environment')
|
||||||
|
.groupBy('cre.feature', 'cr.environment')
|
||||||
|
.whereNotIn('cr.state', [
|
||||||
|
'Applied',
|
||||||
|
'Cancelled',
|
||||||
|
'Rejected',
|
||||||
|
])
|
||||||
|
.as('feature_cr'),
|
||||||
|
function () {
|
||||||
|
this.on(
|
||||||
|
'feature_cr.feature',
|
||||||
|
'=',
|
||||||
|
'ranked_features.feature_name',
|
||||||
|
).andOn(
|
||||||
|
'feature_cr.environment',
|
||||||
|
'=',
|
||||||
|
'ranked_features.environment',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.select('feature_cr.change_request_ids');
|
||||||
|
}
|
||||||
this.queryExtraData(finalQuery);
|
this.queryExtraData(finalQuery);
|
||||||
const rows = await finalQuery;
|
const rows = await finalQuery;
|
||||||
stopTimer();
|
stopTimer();
|
||||||
|
@ -29,6 +29,7 @@ beforeAll(async () => {
|
|||||||
flags: {
|
flags: {
|
||||||
strictSchemaValidation: true,
|
strictSchemaValidation: true,
|
||||||
anonymiseEventLog: true,
|
anonymiseEventLog: true,
|
||||||
|
flagsOverviewSearch: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1286,3 +1287,98 @@ test('should return tags with color information from tag type', async () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const createChangeRequest = async ({
|
||||||
|
id,
|
||||||
|
feature,
|
||||||
|
environment,
|
||||||
|
state,
|
||||||
|
}: { id: number; feature: string; environment: string; state: string }) => {
|
||||||
|
await db
|
||||||
|
.rawDatabase('change_requests')
|
||||||
|
.insert({ id, environment, state, project: 'default', created_by: 1 });
|
||||||
|
await db.rawDatabase('change_request_events').insert({
|
||||||
|
id,
|
||||||
|
feature,
|
||||||
|
action: 'updateEnabled',
|
||||||
|
created_by: 1,
|
||||||
|
change_request_id: id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should return change request ids per environment', async () => {
|
||||||
|
await app.createFeature('my_feature_a');
|
||||||
|
await app.createFeature('my_feature_b');
|
||||||
|
|
||||||
|
await createChangeRequest({
|
||||||
|
id: 1,
|
||||||
|
feature: 'my_feature_a',
|
||||||
|
environment: 'production',
|
||||||
|
state: 'In review',
|
||||||
|
});
|
||||||
|
await createChangeRequest({
|
||||||
|
id: 2,
|
||||||
|
feature: 'my_feature_a',
|
||||||
|
environment: 'production',
|
||||||
|
state: 'Applied',
|
||||||
|
});
|
||||||
|
await createChangeRequest({
|
||||||
|
id: 3,
|
||||||
|
feature: 'my_feature_a',
|
||||||
|
environment: 'production',
|
||||||
|
state: 'Cancelled',
|
||||||
|
});
|
||||||
|
await createChangeRequest({
|
||||||
|
id: 4,
|
||||||
|
feature: 'my_feature_a',
|
||||||
|
environment: 'production',
|
||||||
|
state: 'Rejected',
|
||||||
|
});
|
||||||
|
await createChangeRequest({
|
||||||
|
id: 5,
|
||||||
|
feature: 'my_feature_a',
|
||||||
|
environment: 'development',
|
||||||
|
state: 'Draft',
|
||||||
|
});
|
||||||
|
await createChangeRequest({
|
||||||
|
id: 6,
|
||||||
|
feature: 'my_feature_a',
|
||||||
|
environment: 'development',
|
||||||
|
state: 'Scheduled',
|
||||||
|
});
|
||||||
|
await createChangeRequest({
|
||||||
|
id: 7,
|
||||||
|
feature: 'my_feature_a',
|
||||||
|
environment: 'development',
|
||||||
|
state: 'Approved',
|
||||||
|
});
|
||||||
|
await createChangeRequest({
|
||||||
|
id: 8,
|
||||||
|
feature: 'my_feature_b',
|
||||||
|
environment: 'development',
|
||||||
|
state: 'Approved',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { body } = await searchFeatures({});
|
||||||
|
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
name: 'my_feature_a',
|
||||||
|
environments: [
|
||||||
|
{ name: 'default', changeRequestIds: [] },
|
||||||
|
{ name: 'development', changeRequestIds: [5, 6, 7] },
|
||||||
|
{ name: 'production', changeRequestIds: [1] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'my_feature_b',
|
||||||
|
environments: [
|
||||||
|
{ name: 'default', changeRequestIds: [] },
|
||||||
|
{ name: 'development', changeRequestIds: [8] },
|
||||||
|
{ name: 'production', changeRequestIds: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -63,6 +63,14 @@ export const featureEnvironmentSchema = {
|
|||||||
},
|
},
|
||||||
description: 'A list of variants for the feature environment',
|
description: 'A list of variants for the feature environment',
|
||||||
},
|
},
|
||||||
|
changeRequestIds: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
'Experimental. A list of change request identifiers for actionable change requests that are not Cancelled, Rejected or Approved.',
|
||||||
|
},
|
||||||
lastSeenAt: {
|
lastSeenAt: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
format: 'date-time',
|
format: 'date-time',
|
||||||
|
@ -66,7 +66,8 @@ export type IFlagKey =
|
|||||||
| 'tagTypeColor'
|
| 'tagTypeColor'
|
||||||
| 'globalChangeRequestConfig'
|
| 'globalChangeRequestConfig'
|
||||||
| 'addEditStrategy'
|
| 'addEditStrategy'
|
||||||
| 'newStrategyDropdown';
|
| 'newStrategyDropdown'
|
||||||
|
| 'flagsOverviewSearch';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
@ -319,6 +320,10 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_NEW_STRATEGY_DROPDOWN,
|
process.env.UNLEASH_EXPERIMENTAL_NEW_STRATEGY_DROPDOWN,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
flagsOverviewSearch: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_EXPERIMENTAL_FLAGS_OVERVIEW_SEARCH,
|
||||||
|
false,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||||
|
Loading…
Reference in New Issue
Block a user