Skip to content

Commit

Permalink
feat: created date operators for search (#5513)
Browse files Browse the repository at this point in the history
1. Added operators for created date
2. Added better descriptions for searchable fields
  • Loading branch information
sjaanus authored Nov 30, 2023
1 parent 44d85c0 commit feae696
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/lib/features/feature-search/feature-search-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export default class FeatureSearchController extends Controller {
type,
tag,
segment,
createdAt,
state,
status,
offset,
Expand Down Expand Up @@ -112,6 +113,7 @@ export default class FeatureSearchController extends Controller {
tag,
segment,
state,
createdAt,
status: normalizedStatus,
offset: normalizedOffset,
limit: normalizedLimit,
Expand Down
10 changes: 9 additions & 1 deletion src/lib/features/feature-search/feature-search-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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]);
Expand Down
34 changes: 34 additions & 0 deletions src/lib/features/feature-search/feature.search.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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' }],
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface IFeatureSearchParams {
searchParams?: string[];
project?: string;
segment?: string;
createdAt?: string;
state?: string;
type?: string[];
tag?: string;
Expand All @@ -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;
Expand All @@ -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>;
}
20 changes: 17 additions & 3 deletions src/lib/openapi/spec/feature-search-query-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
{
Expand All @@ -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',
},
{
Expand Down Expand Up @@ -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',
},
{
Expand Down Expand Up @@ -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<
Expand Down

0 comments on commit feae696

Please sign in to comment.