mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: support multiple terms in search, remove tag support in search (#5395)
1. Removing tag support in search 2. Adding multi keyword support for search
This commit is contained in:
parent
5414fa6663
commit
432aed3034
@ -84,7 +84,13 @@ export default class FeatureSearchController extends Controller {
|
|||||||
favoritesFirst,
|
favoritesFirst,
|
||||||
} = req.query;
|
} = req.query;
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
const normalizedTag = tag?.map((tag) => tag.split(':'));
|
const normalizedQuery = query
|
||||||
|
?.split(',')
|
||||||
|
.map((query) => query.trim())
|
||||||
|
.filter((query) => query);
|
||||||
|
const normalizedTag = tag
|
||||||
|
?.map((tag) => tag.split(':'))
|
||||||
|
.filter((tag) => tag.length === 2);
|
||||||
const normalizedStatus = status
|
const normalizedStatus = status
|
||||||
?.map((tag) => tag.split(':'))
|
?.map((tag) => tag.split(':'))
|
||||||
.filter(
|
.filter(
|
||||||
@ -100,7 +106,7 @@ export default class FeatureSearchController extends Controller {
|
|||||||
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
|
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
|
||||||
const normalizedFavoritesFirst = favoritesFirst === 'true';
|
const normalizedFavoritesFirst = favoritesFirst === 'true';
|
||||||
const { features, total } = await this.featureSearchService.search({
|
const { features, total } = await this.featureSearchService.search({
|
||||||
query,
|
queryParams: normalizedQuery,
|
||||||
projectId,
|
projectId,
|
||||||
type,
|
type,
|
||||||
userId,
|
userId,
|
||||||
|
@ -210,50 +210,6 @@ test('should filter features by environment status', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should filter by partial tag', async () => {
|
|
||||||
await app.createFeature('my_feature_a');
|
|
||||||
await app.createFeature('my_feature_b');
|
|
||||||
await app.addTag('my_feature_a', {
|
|
||||||
type: 'simple',
|
|
||||||
value: 'my_tag',
|
|
||||||
});
|
|
||||||
|
|
||||||
const { body } = await filterFeaturesByTag(['simple']);
|
|
||||||
|
|
||||||
expect(body).toMatchObject({
|
|
||||||
features: [{ name: 'my_feature_a' }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should search matching features by tag', async () => {
|
|
||||||
await app.createFeature('my_feature_a');
|
|
||||||
await app.createFeature('my_feature_b');
|
|
||||||
await app.addTag('my_feature_a', {
|
|
||||||
type: 'simple',
|
|
||||||
value: 'my_tag',
|
|
||||||
});
|
|
||||||
|
|
||||||
const { body: fullMatch } = await searchFeatures({
|
|
||||||
query: 'simple:my_tag',
|
|
||||||
});
|
|
||||||
const { body: tagTypeMatch } = await searchFeatures({ query: 'simple' });
|
|
||||||
const { body: tagValueMatch } = await searchFeatures({ query: 'my_tag' });
|
|
||||||
const { body: partialTagMatch } = await searchFeatures({ query: 'e:m' });
|
|
||||||
|
|
||||||
expect(fullMatch).toMatchObject({
|
|
||||||
features: [{ name: 'my_feature_a' }],
|
|
||||||
});
|
|
||||||
expect(tagTypeMatch).toMatchObject({
|
|
||||||
features: [{ name: 'my_feature_a' }],
|
|
||||||
});
|
|
||||||
expect(tagValueMatch).toMatchObject({
|
|
||||||
features: [{ name: 'my_feature_a' }],
|
|
||||||
});
|
|
||||||
expect(partialTagMatch).toMatchObject({
|
|
||||||
features: [{ name: 'my_feature_a' }],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return all feature tags', async () => {
|
test('should return all feature tags', async () => {
|
||||||
await app.createFeature('my_feature_a');
|
await app.createFeature('my_feature_a');
|
||||||
await app.addTag('my_feature_a', {
|
await app.addTag('my_feature_a', {
|
||||||
@ -500,3 +456,31 @@ test('should search features by description', async () => {
|
|||||||
features: [{ name: 'my_feature_b', description }],
|
features: [{ name: 'my_feature_b', description }],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should support multiple search values', async () => {
|
||||||
|
const description = 'secretdescription';
|
||||||
|
await app.createFeature('my_feature_a');
|
||||||
|
await app.createFeature({ name: 'my_feature_b', description });
|
||||||
|
await app.createFeature('my_feature_c');
|
||||||
|
|
||||||
|
const { body } = await searchFeatures({
|
||||||
|
query: 'descr,c',
|
||||||
|
});
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
features: [
|
||||||
|
{ name: 'my_feature_b', description },
|
||||||
|
{ name: 'my_feature_c' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { body: emptyQuery } = await searchFeatures({
|
||||||
|
query: ' , ',
|
||||||
|
});
|
||||||
|
expect(emptyQuery).toMatchObject({
|
||||||
|
features: [
|
||||||
|
{ name: 'my_feature_a' },
|
||||||
|
{ name: 'my_feature_b' },
|
||||||
|
{ name: 'my_feature_c' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -529,7 +529,7 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
async searchFeatures({
|
async searchFeatures({
|
||||||
projectId,
|
projectId,
|
||||||
userId,
|
userId,
|
||||||
query: queryString,
|
queryParams,
|
||||||
type,
|
type,
|
||||||
tag,
|
tag,
|
||||||
status,
|
status,
|
||||||
@ -542,9 +542,6 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
features: IFeatureOverview[];
|
features: IFeatureOverview[];
|
||||||
total: number;
|
total: number;
|
||||||
}> {
|
}> {
|
||||||
const normalizedFullTag = tag?.filter((tag) => tag.length === 2);
|
|
||||||
const normalizedHalfTag = tag?.filter((tag) => tag.length === 1).flat();
|
|
||||||
|
|
||||||
const validatedSortOrder =
|
const validatedSortOrder =
|
||||||
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
|
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';
|
||||||
|
|
||||||
@ -554,54 +551,33 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
|
|||||||
if (projectId) {
|
if (projectId) {
|
||||||
query.where({ project: projectId });
|
query.where({ project: projectId });
|
||||||
}
|
}
|
||||||
const hasQueryString = Boolean(queryString?.trim());
|
const hasQueryString = queryParams?.length;
|
||||||
const hasHalfTag =
|
|
||||||
normalizedHalfTag && normalizedHalfTag.length > 0;
|
|
||||||
if (hasQueryString || hasHalfTag) {
|
|
||||||
const tagQuery = this.db
|
|
||||||
.from('feature_tag')
|
|
||||||
.select('feature_name');
|
|
||||||
// todo: we can run a cheaper query when no colon is detected
|
|
||||||
if (hasQueryString) {
|
if (hasQueryString) {
|
||||||
tagQuery.whereRaw("(?? || ':' || ??) ILIKE ?", [
|
const sqlParameters = queryParams.map(
|
||||||
'tag_type',
|
(item) => `%${item}%`,
|
||||||
'tag_value',
|
|
||||||
`%${queryString}%`,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (hasHalfTag) {
|
|
||||||
const tagParameters = normalizedHalfTag.map(
|
|
||||||
(tag) => `%${tag}%`,
|
|
||||||
);
|
);
|
||||||
const tagQueryParameters = normalizedHalfTag
|
const sqlQueryParameters = sqlParameters
|
||||||
.map(() => '?')
|
.map(() => '?')
|
||||||
.join(',');
|
.join(',');
|
||||||
tagQuery
|
|
||||||
.orWhereRaw(
|
|
||||||
`(??) ILIKE ANY (ARRAY[${tagQueryParameters}])`,
|
|
||||||
['tag_type', ...tagParameters],
|
|
||||||
)
|
|
||||||
.orWhereRaw(
|
|
||||||
`(??) ILIKE ANY (ARRAY[${tagQueryParameters}])`,
|
|
||||||
['tag_value', ...tagParameters],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
query.where((builder) => {
|
query.where((builder) => {
|
||||||
builder
|
builder
|
||||||
.whereILike('features.name', `%${queryString}%`)
|
.orWhereRaw(
|
||||||
.orWhereILike(
|
`(??) ILIKE ANY (ARRAY[${sqlQueryParameters}])`,
|
||||||
'features.description',
|
['features.name', ...sqlParameters],
|
||||||
`%${queryString}%`,
|
|
||||||
)
|
)
|
||||||
.orWhereIn('features.name', tagQuery);
|
.orWhereRaw(
|
||||||
|
`(??) ILIKE ANY (ARRAY[${sqlQueryParameters}])`,
|
||||||
|
['features.description', ...sqlParameters],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (normalizedFullTag && normalizedFullTag.length > 0) {
|
if (tag && tag.length > 0) {
|
||||||
const tagQuery = this.db
|
const tagQuery = this.db
|
||||||
.from('feature_tag')
|
.from('feature_tag')
|
||||||
.select('feature_name')
|
.select('feature_name')
|
||||||
.whereIn(['tag_type', 'tag_value'], normalizedFullTag);
|
.whereIn(['tag_type', 'tag_value'], tag);
|
||||||
query.whereIn('features.name', tagQuery);
|
query.whereIn('features.name', tagQuery);
|
||||||
}
|
}
|
||||||
if (type) {
|
if (type) {
|
||||||
|
@ -23,7 +23,7 @@ export interface FeatureConfigurationClient {
|
|||||||
|
|
||||||
export interface IFeatureSearchParams {
|
export interface IFeatureSearchParams {
|
||||||
userId: number;
|
userId: number;
|
||||||
query?: string;
|
queryParams?: string[];
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
type?: string[];
|
type?: string[];
|
||||||
tag?: string[][];
|
tag?: string[][];
|
||||||
|
Loading…
Reference in New Issue
Block a user