1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-08 01:15:49 +02:00

feat: also allow searching partial tags (#5299)

This commit is contained in:
Jaanus Sellin 2023-11-08 16:05:22 +02:00 committed by GitHub
parent f45454fbfd
commit a5288ae0b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 25 additions and 15 deletions

View File

@ -83,9 +83,7 @@ export default class FeatureSearchController extends Controller {
sortBy, sortBy,
} = req.query; } = req.query;
const userId = req.user.id; const userId = req.user.id;
const normalizedTag = tag const normalizedTag = tag?.map((tag) => tag.split(':'));
?.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(
@ -95,7 +93,7 @@ export default class FeatureSearchController extends Controller {
); );
const normalizedLimit = const normalizedLimit =
Number(limit) > 0 && Number(limit) <= 50 ? Number(limit) : 50; Number(limit) > 0 && Number(limit) <= 50 ? Number(limit) : 50;
const normalizedOffset = Number(offset) > 0 ? Number(limit) : 0; const normalizedOffset = Number(offset) > 0 ? Number(offset) : 0;
const normalizedSortBy: string = sortBy ? sortBy : 'createdAt'; const normalizedSortBy: string = sortBy ? sortBy : 'createdAt';
const normalizedSortOrder = const normalizedSortOrder =
sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc'; sortOrder === 'asc' || sortOrder === 'desc' ? sortOrder : 'asc';

View File

@ -190,7 +190,7 @@ test('should filter features by environment status', async () => {
}); });
}); });
test('filter with invalid tag should ignore filter', async () => { test('should filter by partial tag', async () => {
await app.createFeature('my_feature_a'); await app.createFeature('my_feature_a');
await app.createFeature('my_feature_b'); await app.createFeature('my_feature_b');
await app.addTag('my_feature_a', { type: 'simple', value: 'my_tag' }); await app.addTag('my_feature_a', { type: 'simple', value: 'my_tag' });
@ -198,7 +198,7 @@ test('filter with invalid tag should ignore filter', async () => {
const { body } = await filterFeaturesByTag(['simple']); const { body } = await filterFeaturesByTag(['simple']);
expect(body).toMatchObject({ expect(body).toMatchObject({
features: [{ name: 'my_feature_a' }, { name: 'my_feature_b' }], features: [{ name: 'my_feature_a' }],
}); });
}); });

View File

@ -26,7 +26,6 @@ import { IFeatureProjectUserParams } from './feature-toggle-controller';
import { Db } from '../../db/db'; import { Db } from '../../db/db';
import Raw = Knex.Raw; import Raw = Knex.Raw;
import { IFeatureSearchParams } from './types/feature-toggle-strategies-store-type'; import { IFeatureSearchParams } from './types/feature-toggle-strategies-store-type';
import { addMilliseconds, format, formatISO, parseISO } from 'date-fns';
const COLUMNS = [ const COLUMNS = [
'id', 'id',
@ -547,6 +546,9 @@ 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();
let environmentCount = 1; let environmentCount = 1;
if (projectId) { if (projectId) {
const rows = await this.db('project_environments') const rows = await this.db('project_environments')
@ -559,17 +561,27 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
if (projectId) { if (projectId) {
query = query.where({ project: projectId }); query = query.where({ project: projectId });
} }
const hasQueryString = Boolean(queryString?.trim());
if (queryString?.trim()) { 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 // todo: we can run a cheaper query when no colon is detected
const tagQuery = this.db if (hasQueryString) {
.from('feature_tag') tagQuery.whereRaw("(?? || ':' || ??) ILIKE ?", [
.select('feature_name')
.whereRaw("(?? || ':' || ??) LIKE ?", [
'tag_type', 'tag_type',
'tag_value', 'tag_value',
`%${queryString}%`, `%${queryString}%`,
]); ]);
}
if (hasHalfTag) {
const tagParameter = normalizedHalfTag.map((tag) => `%${tag}%`);
tagQuery.orWhereRaw(
`(?? || ':' || ??) ILIKE ANY (ARRAY[${tagParameter
.map(() => '?')
.join(',')}])`,
['tag_type', 'tag_value', ...tagParameter],
);
}
query = query.where((builder) => { query = query.where((builder) => {
builder builder
@ -577,11 +589,11 @@ class FeatureStrategiesStore implements IFeatureStrategiesStore {
.orWhereIn('features.name', tagQuery); .orWhereIn('features.name', tagQuery);
}); });
} }
if (tag && tag.length > 0) { if (normalizedFullTag && normalizedFullTag.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'], tag); .whereIn(['tag_type', 'tag_value'], normalizedFullTag);
query = query.whereIn('features.name', tagQuery); query = query.whereIn('features.name', tagQuery);
} }
if (type) { if (type) {