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:
parent
44d85c0dcd
commit
feae69643c
@ -77,6 +77,7 @@ export default class FeatureSearchController extends Controller {
|
|||||||
type,
|
type,
|
||||||
tag,
|
tag,
|
||||||
segment,
|
segment,
|
||||||
|
createdAt,
|
||||||
state,
|
state,
|
||||||
status,
|
status,
|
||||||
offset,
|
offset,
|
||||||
@ -112,6 +113,7 @@ export default class FeatureSearchController extends Controller {
|
|||||||
tag,
|
tag,
|
||||||
segment,
|
segment,
|
||||||
state,
|
state,
|
||||||
|
createdAt,
|
||||||
status: normalizedStatus,
|
status: normalizedStatus,
|
||||||
offset: normalizedOffset,
|
offset: normalizedOffset,
|
||||||
limit: normalizedLimit,
|
limit: normalizedLimit,
|
||||||
|
@ -43,7 +43,7 @@ export class FeatureSearchService {
|
|||||||
|
|
||||||
parseOperatorValue = (field: string, value: string): IQueryParam | null => {
|
parseOperatorValue = (field: string, value: string): IQueryParam | null => {
|
||||||
const pattern =
|
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);
|
const match = value.match(pattern);
|
||||||
|
|
||||||
if (match) {
|
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) => {
|
['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]);
|
||||||
|
@ -126,6 +126,15 @@ const filterFeaturesByState = async (state: string, expectedCode = 200) => {
|
|||||||
.expect(expectedCode);
|
.expect(expectedCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterFeaturesByCreated = async (
|
||||||
|
createdAt: string,
|
||||||
|
expectedCode = 200,
|
||||||
|
) => {
|
||||||
|
return app.request
|
||||||
|
.get(`/api/admin/search/features?createdAt=${createdAt}`)
|
||||||
|
.expect(expectedCode);
|
||||||
|
};
|
||||||
|
|
||||||
const filterFeaturesByEnvironmentStatus = async (
|
const filterFeaturesByEnvironmentStatus = async (
|
||||||
environmentStatuses: string[],
|
environmentStatuses: string[],
|
||||||
expectedCode = 200,
|
expectedCode = 200,
|
||||||
@ -796,3 +805,28 @@ test('should search features by state with operators', async () => {
|
|||||||
features: [],
|
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' }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -1131,6 +1131,12 @@ const applyGenericQueryParams = (
|
|||||||
case 'IS_NOT_ANY_OF':
|
case 'IS_NOT_ANY_OF':
|
||||||
query.whereNotIn(param.field, param.values);
|
query.whereNotIn(param.field, param.values);
|
||||||
break;
|
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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -26,6 +26,7 @@ export interface IFeatureSearchParams {
|
|||||||
searchParams?: string[];
|
searchParams?: string[];
|
||||||
project?: string;
|
project?: string;
|
||||||
segment?: string;
|
segment?: string;
|
||||||
|
createdAt?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
type?: string[];
|
type?: string[];
|
||||||
tag?: string;
|
tag?: string;
|
||||||
@ -47,7 +48,9 @@ export type IQueryOperator =
|
|||||||
| 'INCLUDE_ALL_OF'
|
| 'INCLUDE_ALL_OF'
|
||||||
| 'INCLUDE_ANY_OF'
|
| 'INCLUDE_ANY_OF'
|
||||||
| 'EXCLUDE_IF_ANY_OF'
|
| 'EXCLUDE_IF_ANY_OF'
|
||||||
| 'EXCLUDE_ALL';
|
| 'EXCLUDE_ALL'
|
||||||
|
| 'IS_BEFORE'
|
||||||
|
| 'IS_ON_OR_AFTER';
|
||||||
|
|
||||||
export interface IQueryParam {
|
export interface IQueryParam {
|
||||||
field: string;
|
field: string;
|
||||||
@ -60,53 +63,71 @@ export interface IFeatureStrategiesStore
|
|||||||
createStrategyFeatureEnv(
|
createStrategyFeatureEnv(
|
||||||
strategyConfig: Omit<IFeatureStrategy, 'id' | 'createdAt'>,
|
strategyConfig: Omit<IFeatureStrategy, 'id' | 'createdAt'>,
|
||||||
): Promise<IFeatureStrategy>;
|
): Promise<IFeatureStrategy>;
|
||||||
|
|
||||||
removeAllStrategiesForFeatureEnv(
|
removeAllStrategiesForFeatureEnv(
|
||||||
featureName: string,
|
featureName: string,
|
||||||
environment: string,
|
environment: string,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
getStrategiesForFeatureEnv(
|
getStrategiesForFeatureEnv(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
featureName: string,
|
featureName: string,
|
||||||
environment: string,
|
environment: string,
|
||||||
): Promise<IFeatureStrategy[]>;
|
): Promise<IFeatureStrategy[]>;
|
||||||
|
|
||||||
getFeatureToggleWithEnvs(
|
getFeatureToggleWithEnvs(
|
||||||
featureName: string,
|
featureName: string,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
archived?: boolean,
|
archived?: boolean,
|
||||||
): Promise<FeatureToggleWithEnvironment>;
|
): Promise<FeatureToggleWithEnvironment>;
|
||||||
|
|
||||||
getFeatureToggleWithVariantEnvs(
|
getFeatureToggleWithVariantEnvs(
|
||||||
featureName: string,
|
featureName: string,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
archived?,
|
archived?,
|
||||||
): Promise<FeatureToggleWithEnvironment>;
|
): Promise<FeatureToggleWithEnvironment>;
|
||||||
|
|
||||||
getFeatureOverview(
|
getFeatureOverview(
|
||||||
params: IFeatureProjectUserParams,
|
params: IFeatureProjectUserParams,
|
||||||
): Promise<IFeatureOverview[]>;
|
): Promise<IFeatureOverview[]>;
|
||||||
|
|
||||||
searchFeatures(
|
searchFeatures(
|
||||||
params: IFeatureSearchParams,
|
params: IFeatureSearchParams,
|
||||||
queryParams: IQueryParam[],
|
queryParams: IQueryParam[],
|
||||||
): Promise<{ features: IFeatureOverview[]; total: number }>;
|
): Promise<{
|
||||||
|
features: IFeatureOverview[];
|
||||||
|
total: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
getStrategyById(id: string): Promise<IFeatureStrategy>;
|
getStrategyById(id: string): Promise<IFeatureStrategy>;
|
||||||
|
|
||||||
updateStrategy(
|
updateStrategy(
|
||||||
id: string,
|
id: string,
|
||||||
updates: Partial<IFeatureStrategy>,
|
updates: Partial<IFeatureStrategy>,
|
||||||
): Promise<IFeatureStrategy>;
|
): Promise<IFeatureStrategy>;
|
||||||
|
|
||||||
deleteConfigurationsForProjectAndEnvironment(
|
deleteConfigurationsForProjectAndEnvironment(
|
||||||
projectId: String,
|
projectId: String,
|
||||||
environment: String,
|
environment: String,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
setProjectForStrategiesBelongingToFeature(
|
setProjectForStrategiesBelongingToFeature(
|
||||||
featureName: string,
|
featureName: string,
|
||||||
newProjectId: string,
|
newProjectId: string,
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
getStrategiesBySegment(segmentId: number): Promise<IFeatureStrategy[]>;
|
getStrategiesBySegment(segmentId: number): Promise<IFeatureStrategy[]>;
|
||||||
|
|
||||||
getStrategiesByContextField(
|
getStrategiesByContextField(
|
||||||
contextFieldName: string,
|
contextFieldName: string,
|
||||||
): Promise<IFeatureStrategy[]>;
|
): Promise<IFeatureStrategy[]>;
|
||||||
|
|
||||||
updateSortOrder(id: string, sortOrder: number): Promise<void>;
|
updateSortOrder(id: string, sortOrder: number): Promise<void>;
|
||||||
|
|
||||||
getAllByFeatures(
|
getAllByFeatures(
|
||||||
features: string[],
|
features: string[],
|
||||||
environment?: string,
|
environment?: string,
|
||||||
): Promise<IFeatureStrategy[]>;
|
): Promise<IFeatureStrategy[]>;
|
||||||
|
|
||||||
getCustomStrategiesInUseCount(): Promise<number>;
|
getCustomStrategiesInUseCount(): Promise<number>;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@ export const featureSearchQueryParameters = [
|
|||||||
pattern:
|
pattern:
|
||||||
'^(IS|IS_NOT|IS_ANY_OF|IS_NOT_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$',
|
'^(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',
|
in: 'query',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -29,7 +30,8 @@ export const featureSearchQueryParameters = [
|
|||||||
pattern:
|
pattern:
|
||||||
'^(IS|IS_NOT|IS_ANY_OF|IS_NOT_ANY_OF):(.*?)(,([a-zA-Z0-9_]+))*$',
|
'^(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',
|
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_]+))*$',
|
'^(INCLUDE|DO_NOT_INCLUDE|INCLUDE_ALL_OF|INCLUDE_ANY_OF|EXCLUDE_IF_ANY_OF|EXCLUDE_ALL):(.*?)(,([a-zA-Z0-9_]+))*$',
|
||||||
example: 'INCLUDE:pro-users',
|
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',
|
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.',
|
'The flag to indicate if the favorite features should be returned first. By default it is set to false.',
|
||||||
in: 'query',
|
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;
|
] as const;
|
||||||
|
|
||||||
export type FeatureSearchQueryParameters = Partial<
|
export type FeatureSearchQueryParameters = Partial<
|
||||||
|
Loading…
Reference in New Issue
Block a user