1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: created date operators for search (#5513)

1. Added operators for created date
2. Added better descriptions for searchable fields
This commit is contained in:
Jaanus Sellin 2023-11-30 12:00:39 +02:00 committed by GitHub
parent 44d85c0dcd
commit feae69643c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 6 deletions

View File

@ -77,6 +77,7 @@ export default class FeatureSearchController extends Controller {
type,
tag,
segment,
createdAt,
state,
status,
offset,
@ -112,6 +113,7 @@ export default class FeatureSearchController extends Controller {
tag,
segment,
state,
createdAt,
status: normalizedStatus,
offset: normalizedOffset,
limit: normalizedLimit,

View File

@ -43,7 +43,7 @@ export class FeatureSearchService {
parseOperatorValue = (field: string, value: string): IQueryParam | null => {
const pattern =
/^(IS|IS_NOT|IS_ANY_OF|IS_NOT_ANY_OF|INCLUDE|DO_NOT_INCLUDE|INCLUDE_ALL_OF|INCLUDE_ANY_OF|EXCLUDE_IF_ANY_OF|EXCLUDE_ALL):(.+)$/;
/^(IS|IS_NOT|IS_ANY_OF|IS_NOT_ANY_OF|INCLUDE|DO_NOT_INCLUDE|INCLUDE_ALL_OF|INCLUDE_ANY_OF|EXCLUDE_IF_ANY_OF|EXCLUDE_ALL|IS_BEFORE|IS_ON_OR_AFTER):(.+)$/;
const match = value.match(pattern);
if (match) {
@ -70,6 +70,14 @@ export class FeatureSearchService {
}
}
if (params.createdAt) {
const parsed = this.parseOperatorValue(
'features.created_at',
params.createdAt,
);
if (parsed) queryParams.push(parsed);
}
['tag', 'segment', 'project'].forEach((field) => {
if (params[field]) {
const parsed = this.parseOperatorValue(field, params[field]);

View File

@ -126,6 +126,15 @@ const filterFeaturesByState = async (state: string, expectedCode = 200) => {
.expect(expectedCode);
};
const filterFeaturesByCreated = async (
createdAt: string,
expectedCode = 200,
) => {
return app.request
.get(`/api/admin/search/features?createdAt=${createdAt}`)
.expect(expectedCode);
};
const filterFeaturesByEnvironmentStatus = async (
environmentStatuses: string[],
expectedCode = 200,
@ -796,3 +805,28 @@ test('should search features by state with operators', async () => {
features: [],
});
});
test('should search features by created date with operators', async () => {
await app.createFeature({
name: 'my_feature_a',
createdAt: '2023-01-27T15:21:39.975Z',
});
await app.createFeature({
name: 'my_feature_b',
createdAt: '2023-01-29T15:21:39.975Z',
});
const { body } = await filterFeaturesByCreated(
'IS_BEFORE:2023-01-28T15:21:39.975Z',
);
expect(body).toMatchObject({
features: [{ name: 'my_feature_a' }],
});
const { body: afterBody } = await filterFeaturesByCreated(
'IS_ON_OR_AFTER:2023-01-28T15:21:39.975Z',
);
expect(afterBody).toMatchObject({
features: [{ name: 'my_feature_b' }],
});
});

View File

@ -1131,6 +1131,12 @@ const applyGenericQueryParams = (
case 'IS_NOT_ANY_OF':
query.whereNotIn(param.field, param.values);
break;
case 'IS_BEFORE':
query.where(param.field, '<', param.values[0]);
break;
case 'IS_ON_OR_AFTER':
query.where(param.field, '>=', param.values[0]);
break;
}
});
};

View File

@ -26,6 +26,7 @@ export interface IFeatureSearchParams {
searchParams?: string[];
project?: string;
segment?: string;
createdAt?: string;
state?: string;
type?: string[];
tag?: string;
@ -47,7 +48,9 @@ export type IQueryOperator =
| 'INCLUDE_ALL_OF'
| 'INCLUDE_ANY_OF'
| 'EXCLUDE_IF_ANY_OF'
| 'EXCLUDE_ALL';
| 'EXCLUDE_ALL'
| 'IS_BEFORE'
| 'IS_ON_OR_AFTER';
export interface IQueryParam {
field: string;
@ -60,53 +63,71 @@ export interface IFeatureStrategiesStore
createStrategyFeatureEnv(
strategyConfig: Omit<IFeatureStrategy, 'id' | 'createdAt'>,
): Promise<IFeatureStrategy>;
removeAllStrategiesForFeatureEnv(
featureName: string,
environment: string,
): Promise<void>;
getStrategiesForFeatureEnv(
projectId: string,
featureName: string,
environment: string,
): Promise<IFeatureStrategy[]>;
getFeatureToggleWithEnvs(
featureName: string,
userId?: number,
archived?: boolean,
): Promise<FeatureToggleWithEnvironment>;
getFeatureToggleWithVariantEnvs(
featureName: string,
userId?: number,
archived?,
): Promise<FeatureToggleWithEnvironment>;
getFeatureOverview(
params: IFeatureProjectUserParams,
): Promise<IFeatureOverview[]>;
searchFeatures(
params: IFeatureSearchParams,
queryParams: IQueryParam[],
): Promise<{ features: IFeatureOverview[]; total: number }>;
): Promise<{
features: IFeatureOverview[];
total: number;
}>;
getStrategyById(id: string): Promise<IFeatureStrategy>;
updateStrategy(
id: string,
updates: Partial<IFeatureStrategy>,
): Promise<IFeatureStrategy>;
deleteConfigurationsForProjectAndEnvironment(
projectId: String,
environment: String,
): Promise<void>;
setProjectForStrategiesBelongingToFeature(
featureName: string,
newProjectId: string,
): Promise<void>;
getStrategiesBySegment(segmentId: number): Promise<IFeatureStrategy[]>;
getStrategiesByContextField(
contextFieldName: string,
): Promise<IFeatureStrategy[]>;
updateSortOrder(id: string, sortOrder: number): Promise<void>;
getAllByFeatures(
features: string[],
environment?: string,
): Promise<IFeatureStrategy[]>;
getCustomStrategiesInUseCount(): Promise<number>;
}

View File

@ -18,7 +18,8 @@ export const featureSearchQueryParameters = [
pattern:
'^(IS|IS_NOT|IS_ANY_OF|IS_NOT_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$',
},
description: 'Id of the project where search and filter is performed',
description:
'Id of the project where search and filter is performed. The project id can be specified with an operator. The supported operators are IS, IS_NOT, IS_ANY_OF, IS_NOT_ANY_OF.',
in: 'query',
},
{
@ -29,7 +30,8 @@ export const featureSearchQueryParameters = [
pattern:
'^(IS|IS_NOT|IS_ANY_OF|IS_NOT_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$',
},
description: 'The state of the feature active/stale',
description:
'The state of the feature active/stale. The state can be specified with an operator. The supported operators are IS, IS_NOT, IS_ANY_OF, IS_NOT_ANY_OF.',
in: 'query',
},
{
@ -64,7 +66,8 @@ export const featureSearchQueryParameters = [
'^(INCLUDE|DO_NOT_INCLUDE|INCLUDE_ALL_OF|INCLUDE_ANY_OF|EXCLUDE_IF_ANY_OF|EXCLUDE_ALL):(.*?)(,([a-zA-Z0-9_]+))*$',
example: 'INCLUDE:pro-users',
},
description: 'The list of segments with operators to filter by.',
description:
'The list of segments with operators to filter by. The segment valid operators are INCLUDE, DO_NOT_INCLUDE, INCLUDE_ALL_OF, INCLUDE_ANY_OF, EXCLUDE_IF_ANY_OF, EXCLUDE_ALL.',
in: 'query',
},
{
@ -130,6 +133,17 @@ export const featureSearchQueryParameters = [
'The flag to indicate if the favorite features should be returned first. By default it is set to false.',
in: 'query',
},
{
name: 'createdAt',
schema: {
type: 'string',
example: 'IS_ON_OR_AFTER:2023-01-28T15:21:39.975Z',
pattern: '^(IS_BEFORE|IS_ON_OR_AFTER):(.*?)(,([a-zA-Z0-9_]+))*$',
},
description:
'The date the feature was created. The date can be specified with an operator. The supported operators are IS_BEFORE, IS_ON_OR_AFTER.',
in: 'query',
},
] as const;
export type FeatureSearchQueryParameters = Partial<