mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: filter by environment status (#5165)
This commit is contained in:
parent
46d7cb236d
commit
1c8fab63e2
@ -72,17 +72,25 @@ export default class FeatureSearchController extends Controller {
|
||||
res: Response,
|
||||
): Promise<void> {
|
||||
if (this.config.flagResolver.isEnabled('featureSearchAPI')) {
|
||||
const { query, projectId, type, tag } = req.query;
|
||||
const { query, projectId, type, tag, status } = req.query;
|
||||
const userId = req.user.id;
|
||||
const normalizedTag = tag
|
||||
?.map((tag) => tag.split(':'))
|
||||
.filter((tag) => tag.length === 2);
|
||||
const normalizedStatus = status
|
||||
?.map((tag) => tag.split(':'))
|
||||
.filter(
|
||||
(tag) =>
|
||||
tag.length === 2 &&
|
||||
['enabled', 'disabled'].includes(tag[1]),
|
||||
);
|
||||
const features = await this.featureSearchService.search({
|
||||
query,
|
||||
projectId,
|
||||
type,
|
||||
userId,
|
||||
tag: normalizedTag,
|
||||
status: normalizedStatus,
|
||||
});
|
||||
res.json({ features });
|
||||
} else {
|
||||
|
@ -20,13 +20,9 @@ export class FeatureSearchService {
|
||||
}
|
||||
|
||||
async search(params: IFeatureSearchParams) {
|
||||
const features = await this.featureStrategiesStore.searchFeatures({
|
||||
projectId: params.projectId,
|
||||
query: params.query,
|
||||
userId: params.userId,
|
||||
type: params.type,
|
||||
tag: params.tag,
|
||||
});
|
||||
const features = await this.featureStrategiesStore.searchFeatures(
|
||||
params,
|
||||
);
|
||||
|
||||
return features;
|
||||
}
|
||||
|
@ -57,6 +57,18 @@ const filterFeaturesByTag = async (tags: string[], expectedCode = 200) => {
|
||||
.expect(expectedCode);
|
||||
};
|
||||
|
||||
const filterFeaturesByEnvironmentStatus = async (
|
||||
environmentStatuses: string[],
|
||||
expectedCode = 200,
|
||||
) => {
|
||||
const statuses = environmentStatuses
|
||||
.map((status) => `status[]=${status}`)
|
||||
.join('&');
|
||||
return app.request
|
||||
.get(`/api/admin/search/features?${statuses}`)
|
||||
.expect(expectedCode);
|
||||
};
|
||||
|
||||
const searchFeaturesWithoutQueryParams = async (expectedCode = 200) => {
|
||||
return app.request.get(`/api/admin/search/features`).expect(expectedCode);
|
||||
};
|
||||
@ -99,6 +111,22 @@ test('should filter features by tag', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should filter features by environment status', async () => {
|
||||
await app.createFeature('my_feature_a');
|
||||
await app.createFeature('my_feature_b');
|
||||
await app.enableFeature('my_feature_a', 'default');
|
||||
|
||||
const { body } = await filterFeaturesByEnvironmentStatus([
|
||||
'default:enabled',
|
||||
'nonexistentEnv:disabled',
|
||||
'default:wrongStatus',
|
||||
]);
|
||||
|
||||
expect(body).toMatchObject({
|
||||
features: [{ name: 'my_feature_a' }],
|
||||
});
|
||||
});
|
||||
|
||||
test('filter with invalid tag should ignore filter', async () => {
|
||||
await app.createFeature('my_feature_a');
|
||||
await app.createFeature('my_feature_b');
|
||||
|
@ -522,6 +522,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
query: queryString,
|
||||
type,
|
||||
tag,
|
||||
status,
|
||||
}: IFeatureSearchParams): Promise<IFeatureOverview[]> {
|
||||
let query = this.db('features');
|
||||
if (projectId) {
|
||||
@ -553,6 +554,23 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
||||
if (type) {
|
||||
query = query.whereIn('features.type', type);
|
||||
}
|
||||
|
||||
if (status && status.length > 0) {
|
||||
query = query.where((builder) => {
|
||||
for (const [envName, envStatus] of status) {
|
||||
builder.orWhere(function () {
|
||||
this.where(
|
||||
'feature_environments.environment',
|
||||
envName,
|
||||
).andWhere(
|
||||
'feature_environments.enabled',
|
||||
envStatus === 'enabled' ? true : false,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
query = query
|
||||
.modify(FeatureToggleStore.filterByArchived, false)
|
||||
.leftJoin(
|
||||
|
@ -27,6 +27,7 @@ export interface IFeatureSearchParams {
|
||||
projectId?: string;
|
||||
type?: string[];
|
||||
tag?: string[][];
|
||||
status?: string[][];
|
||||
}
|
||||
|
||||
export interface IFeatureStrategiesStore
|
||||
|
@ -41,7 +41,20 @@ export const featureSearchQueryParameters = [
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The list of feature tags to filter by. Feature tag has to specify type and value joined with a colon.',
|
||||
'The list of feature tags to filter by. Feature tag has to specify a type and a value joined with a colon.',
|
||||
in: 'query',
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
example: 'production:enabled',
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The list of feature environment status to filter by. Feature environment has to specify a name and a status joined with a colon.',
|
||||
in: 'query',
|
||||
},
|
||||
] as const;
|
||||
|
@ -47,6 +47,13 @@ export interface IUnleashHttpAPI {
|
||||
expectedResponseCode?: number,
|
||||
): supertest.Test;
|
||||
|
||||
enableFeature(
|
||||
feature: string,
|
||||
environment: string,
|
||||
project?: string,
|
||||
expectedResponseCode?: number,
|
||||
): supertest.Test;
|
||||
|
||||
getFeatures(name?: string, expectedResponseCode?: number): supertest.Test;
|
||||
|
||||
getProjectFeatures(
|
||||
@ -219,6 +226,19 @@ function httpApis(
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(expectedResponseCode);
|
||||
},
|
||||
|
||||
enableFeature(
|
||||
feature: string,
|
||||
environment,
|
||||
project = 'default',
|
||||
expectedResponseCode = 200,
|
||||
): supertest.Test {
|
||||
return request
|
||||
.post(
|
||||
`/api/admin/projects/${project}/features/${feature}/environments/${environment}/on`,
|
||||
)
|
||||
.expect(expectedResponseCode);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user