mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-27 13:49:10 +02:00
feat: add operators for state filtering (#5497)
No changes in store needed, already utilizing reusable logic.
This commit is contained in:
parent
1aad9819cc
commit
a743ca0df6
@ -77,6 +77,7 @@ export default class FeatureSearchController extends Controller {
|
|||||||
type,
|
type,
|
||||||
tag,
|
tag,
|
||||||
segment,
|
segment,
|
||||||
|
state,
|
||||||
status,
|
status,
|
||||||
offset,
|
offset,
|
||||||
limit = '50',
|
limit = '50',
|
||||||
@ -110,6 +111,7 @@ export default class FeatureSearchController extends Controller {
|
|||||||
userId,
|
userId,
|
||||||
tag,
|
tag,
|
||||||
segment,
|
segment,
|
||||||
|
state,
|
||||||
status: normalizedStatus,
|
status: normalizedStatus,
|
||||||
offset: normalizedOffset,
|
offset: normalizedOffset,
|
||||||
limit: normalizedLimit,
|
limit: normalizedLimit,
|
||||||
|
@ -60,6 +60,16 @@ export class FeatureSearchService {
|
|||||||
convertToQueryParams = (params: IFeatureSearchParams): IQueryParam[] => {
|
convertToQueryParams = (params: IFeatureSearchParams): IQueryParam[] => {
|
||||||
const queryParams: IQueryParam[] = [];
|
const queryParams: IQueryParam[] = [];
|
||||||
|
|
||||||
|
if (params.state) {
|
||||||
|
const parsedState = this.parseOperatorValue('stale', params.state);
|
||||||
|
if (parsedState) {
|
||||||
|
parsedState.values = parsedState.values.map((value) =>
|
||||||
|
value === 'active' ? 'false' : 'true',
|
||||||
|
);
|
||||||
|
queryParams.push(parsedState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
['tag', 'segment', 'project'].forEach((field) => {
|
['tag', 'segment', 'project'].forEach((field) => {
|
||||||
if (params[field]) {
|
if (params[field]) {
|
||||||
const parsed = this.parseOperatorValue(field, params[field]);
|
const parsed = this.parseOperatorValue(field, params[field]);
|
||||||
|
@ -120,6 +120,12 @@ const filterFeaturesBySegment = async (segment: string, expectedCode = 200) => {
|
|||||||
.expect(expectedCode);
|
.expect(expectedCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterFeaturesByState = async (state: string, expectedCode = 200) => {
|
||||||
|
return app.request
|
||||||
|
.get(`/api/admin/search/features?state=${state}`)
|
||||||
|
.expect(expectedCode);
|
||||||
|
};
|
||||||
|
|
||||||
const filterFeaturesByEnvironmentStatus = async (
|
const filterFeaturesByEnvironmentStatus = async (
|
||||||
environmentStatuses: string[],
|
environmentStatuses: string[],
|
||||||
expectedCode = 200,
|
expectedCode = 200,
|
||||||
@ -756,3 +762,37 @@ test('should filter features by segment', async () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should search features by state with operators', async () => {
|
||||||
|
await app.createFeature({ name: 'my_feature_a', stale: false });
|
||||||
|
await app.createFeature({ name: 'my_feature_b', stale: true });
|
||||||
|
await app.createFeature({ name: 'my_feature_c', stale: true });
|
||||||
|
|
||||||
|
const { body } = await filterFeaturesByState('IS:active');
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
features: [{ name: 'my_feature_a' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { body: isNotBody } = await filterFeaturesByState('IS_NOT:active');
|
||||||
|
expect(isNotBody).toMatchObject({
|
||||||
|
features: [{ name: 'my_feature_b' }, { name: 'my_feature_c' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { body: isAnyOfBody } = await filterFeaturesByState(
|
||||||
|
'IS_ANY_OF:active, stale',
|
||||||
|
);
|
||||||
|
expect(isAnyOfBody).toMatchObject({
|
||||||
|
features: [
|
||||||
|
{ name: 'my_feature_a' },
|
||||||
|
{ name: 'my_feature_b' },
|
||||||
|
{ name: 'my_feature_c' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { body: isNotAnyBody } = await filterFeaturesByState(
|
||||||
|
'IS_NOT_ANY_OF:active, stale',
|
||||||
|
);
|
||||||
|
expect(isNotAnyBody).toMatchObject({
|
||||||
|
features: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -748,7 +748,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
)
|
)
|
||||||
.joinRaw('CROSS JOIN total_features')
|
.joinRaw('CROSS JOIN total_features')
|
||||||
.whereBetween('final_rank', [offset + 1, offset + limit]);
|
.whereBetween('final_rank', [offset + 1, offset + limit]);
|
||||||
console.log(finalQuery.toQuery());
|
|
||||||
const rows = await finalQuery;
|
const rows = await finalQuery;
|
||||||
|
|
||||||
if (rows.length > 0) {
|
if (rows.length > 0) {
|
||||||
|
@ -26,6 +26,7 @@ export interface IFeatureSearchParams {
|
|||||||
searchParams?: string[];
|
searchParams?: string[];
|
||||||
project?: string;
|
project?: string;
|
||||||
segment?: string;
|
segment?: string;
|
||||||
|
state?: string;
|
||||||
type?: string[];
|
type?: string[];
|
||||||
tag?: string;
|
tag?: string;
|
||||||
status?: string[][];
|
status?: string[][];
|
||||||
|
@ -21,6 +21,17 @@ export const featureSearchQueryParameters = [
|
|||||||
description: 'Id of the project where search and filter is performed',
|
description: 'Id of the project where search and filter is performed',
|
||||||
in: 'query',
|
in: 'query',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'state',
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
example: 'IS:active',
|
||||||
|
pattern:
|
||||||
|
'^(IS|IS_NOT|IS_ANY_OF|IS_NOT_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$',
|
||||||
|
},
|
||||||
|
description: 'The state of the feature active/stale',
|
||||||
|
in: 'query',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'type',
|
name: 'type',
|
||||||
schema: {
|
schema: {
|
||||||
|
@ -6,7 +6,11 @@ import { createTestConfig } from '../../config/test-config';
|
|||||||
import { IAuthType, IUnleashConfig } from '../../../lib/types/option';
|
import { IAuthType, IUnleashConfig } from '../../../lib/types/option';
|
||||||
import { createServices } from '../../../lib/services';
|
import { createServices } from '../../../lib/services';
|
||||||
import sessionDb from '../../../lib/middleware/session-db';
|
import sessionDb from '../../../lib/middleware/session-db';
|
||||||
import { DEFAULT_PROJECT, IUnleashStores } from '../../../lib/types';
|
import {
|
||||||
|
DEFAULT_PROJECT,
|
||||||
|
FeatureToggleDTO,
|
||||||
|
IUnleashStores,
|
||||||
|
} from '../../../lib/types';
|
||||||
import { IUnleashServices } from '../../../lib/types/services';
|
import { IUnleashServices } from '../../../lib/types/services';
|
||||||
import { Db } from '../../../lib/db/db';
|
import { Db } from '../../../lib/db/db';
|
||||||
import { IContextFieldDto } from 'lib/types/stores/context-field-store';
|
import { IContextFieldDto } from 'lib/types/stores/context-field-store';
|
||||||
@ -121,7 +125,7 @@ function httpApis(
|
|||||||
return request.post(url).send(postData).expect(expectStatusCode);
|
return request.post(url).send(postData).expect(expectStatusCode);
|
||||||
},
|
},
|
||||||
createFeature: (
|
createFeature: (
|
||||||
feature: string | CreateFeatureSchema,
|
feature: string | FeatureToggleDTO,
|
||||||
project: string = DEFAULT_PROJECT,
|
project: string = DEFAULT_PROJECT,
|
||||||
expectedResponseCode: number = 201,
|
expectedResponseCode: number = 201,
|
||||||
) => {
|
) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user