catalog-backend: move query to fullTextFilterTerm

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2023-02-03 13:59:50 +01:00
parent b02b6c3e05
commit 5468b81811
9 changed files with 96 additions and 44 deletions
+9 -3
View File
@@ -228,7 +228,10 @@ export interface QueryEntitiesInitialRequest {
limit?: number;
filter?: EntityFilter;
orderFields?: EntityOrder[];
query?: string;
fullTextFilter?: {
term: string;
fields?: string[];
};
}
/**
@@ -289,9 +292,12 @@ export type Cursor = {
*/
isPrevious: boolean;
/**
* Used for filtering the data by name.
* Used for performing full text filtering on the given fields.
*/
query?: string;
fullTextFilter?: {
term: string;
fields?: string[];
};
/**
* The value of the fields of the first returned item used for paginating the data.
* The catalog uses this field internally to understand if the beginning
@@ -1057,7 +1057,7 @@ describe('DefaultEntitiesCatalog', () => {
limit: 100,
orderFields: [{ field: 'metadata.name', order: 'asc' }],
query: 'cAt ',
fullTextFilter: { term: 'cAt ' },
};
const response = await catalog.queryEntities(request);
expect(response.items).toEqual([
@@ -368,11 +368,11 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
parseFilter(cursor.filter, dbQuery, db, false, 'search.entity_id');
}
const normalizedQueryByName = cursor.query?.trim();
if (normalizedQueryByName) {
const normalizedFullTextFilterTerm = cursor.fullTextFilter?.term?.trim();
if (normalizedFullTextFilterTerm) {
dbQuery.andWhereRaw(
'value like ?',
`%${normalizedQueryByName.toLocaleLowerCase('en-US')}%`,
`%${normalizedFullTextFilterTerm.toLocaleLowerCase('en-US')}%`,
);
}
@@ -707,9 +707,9 @@ function parseCursorFromRequest(
const {
filter,
orderFields: sortFields = [defaultSortField],
query,
fullTextFilter,
} = request;
return { filter, orderFields: sortFields, query };
return { filter, orderFields: sortFields, fullTextFilter };
}
if (isQueryEntitiesCursorRequest(request)) {
try {
@@ -166,7 +166,7 @@ describe('createRouter readonly disabled', () => {
totalItems: 0,
});
const response = await request(app).get(
'/entities/by-query?filter=a=1,a=2,b=3&filter=c=4&sortField=metadata.name,asc&sortField=metadata.uid,desc',
'/entities/by-query?filter=a=1,a=2,b=3&filter=c=4&orderField=metadata.name,asc&orderField=metadata.uid,desc',
);
expect(response.status).toEqual(200);
@@ -187,6 +187,10 @@ describe('createRouter readonly disabled', () => {
{ field: 'metadata.name', order: 'asc' },
{ field: 'metadata.uid', order: 'desc' },
],
fullTextFilter: {
fields: undefined,
term: '',
},
});
});
@@ -14,31 +14,31 @@
* limitations under the License.
*/
import { parseEntitySortFieldParams } from './parseEntitySortFieldParams';
import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams';
describe('parseEntitySortFieldParams', () => {
it('supports no sort fields', () => {
const result = parseEntitySortFieldParams({});
describe('parseEntityOrderFieldParams', () => {
it('supports no order fields', () => {
const result = parseEntityOrderFieldParams({});
expect(result).toEqual(undefined);
});
it('supports single sortField', () => {
const result = parseEntitySortFieldParams({
sortField: ['metadata.name,desc'],
it('supports single orderField', () => {
const result = parseEntityOrderFieldParams({
orderField: ['metadata.name,desc'],
})!;
expect(result).toEqual([{ field: 'metadata.name', order: 'desc' }]);
});
it('supports single sortField without order', () => {
const result = parseEntitySortFieldParams({
sortField: ['metadata.name'],
it('supports single orderField without order', () => {
const result = parseEntityOrderFieldParams({
orderField: ['metadata.name'],
})!;
expect(result).toEqual([{ field: 'metadata.name' }]);
});
it('supports multiple sort fields', () => {
const result = parseEntitySortFieldParams({
sortField: ['metadata.name,desc', 'metadata.uid,asc'],
it('supports multiple order fields', () => {
const result = parseEntityOrderFieldParams({
orderField: ['metadata.name,desc', 'metadata.uid,asc'],
});
expect(result).toEqual([
@@ -47,11 +47,11 @@ describe('parseEntitySortFieldParams', () => {
]);
});
it('throws if sortField order is not valid', () => {
it('throws if orderField order is not valid', () => {
expect(() =>
parseEntitySortFieldParams({
sortField: ['metadata.name,desc', 'metadata.uid,invalid'],
parseEntityOrderFieldParams({
orderField: ['metadata.name,desc', 'metadata.uid,invalid'],
}),
).toThrow(/Invalid sort field order/);
).toThrow(/Invalid order field order/);
});
});
@@ -18,19 +18,19 @@ import { InputError } from '@backstage/errors';
import { EntityOrder } from '../../catalog/types';
import { parseStringsParam } from './common';
export function parseEntitySortFieldParams(
export function parseEntityOrderFieldParams(
params: Record<string, unknown>,
): EntityOrder[] | undefined {
const sortFieldStrings = parseStringsParam(params.sortField, 'sortField');
if (!sortFieldStrings) {
const orderFieldStrings = parseStringsParam(params.orderField, 'orderField');
if (!orderFieldStrings) {
return undefined;
}
return sortFieldStrings.map(sortFieldString => {
const [field, order] = sortFieldString.split(',');
return orderFieldStrings.map(orderFieldString => {
const [field, order] = orderFieldString.split(',');
if (order !== undefined && !isOrder(order)) {
throw new InputError('Invalid sort field order, must be asc or desc');
throw new InputError('Invalid order field order, must be asc or desc');
}
return { field, order };
});
@@ -0,0 +1,28 @@
import { parseStringParam } from './common';
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function parseFullTextFilterFields(params: Record<string, unknown>) {
const fullTextFilterFields = parseStringParam(
params.fullTextFilterFields,
'fullTextFilterFields',
);
if (!fullTextFilterFields) {
return undefined;
}
return fullTextFilterFields.split(',');
}
@@ -28,8 +28,9 @@ describe('parseQueryEntitiesParams', () => {
fields: ['kind'],
limit: '3',
filter: ['a=1', 'b=2'],
sortField: ['metadata.name,desc'],
query: 'query',
orderField: ['metadata.name,desc'],
fullTextFilterTerm: 'query',
fullTextFilterFields: 'metadata.name,metadata.namespace',
};
const parsedObj = parseQueryEntitiesParams(
validRequest,
@@ -40,7 +41,10 @@ describe('parseQueryEntitiesParams', () => {
{ field: 'metadata.name', order: 'desc' },
]);
expect(parsedObj.filter).toBeDefined();
expect(parsedObj.query).toBe('query');
expect(parsedObj.fullTextFilter).toEqual({
term: 'query',
fields: ['metadata.name', 'metadata.namespace'],
});
expect(parsedObj).not.toHaveProperty('authorizationToken');
expect(parsedObj).not.toHaveProperty('cursor');
});
@@ -52,7 +56,7 @@ describe('parseQueryEntitiesParams', () => {
expect(parsedObj.fields).toBeUndefined();
expect(parsedObj.orderFields).toBeUndefined();
expect(parsedObj.filter).toBeUndefined();
expect(parsedObj.query).toBeUndefined();
expect(parsedObj.fullTextFilter).toEqual({ term: '', fields: undefined });
expect(parsedObj).not.toHaveProperty('authorizationToken');
expect(parsedObj).not.toHaveProperty('cursor');
});
@@ -62,9 +66,10 @@ describe('parseQueryEntitiesParams', () => {
limit: 'asd',
},
{ filter: 3 },
{ sortField: ['metadata.uid,diagonal'] },
{ orderField: ['metadata.uid,diagonal'] },
{ fields: [4] },
{ query: [] },
{ fullTextFilterTerm: [] },
{ fullTextFilterFields: 3 },
])('should throw if some parameter is not valid %p', params => {
expect(() => parseQueryEntitiesParams(params)).toThrow();
});
@@ -21,8 +21,9 @@ import {
} from '../../catalog/types';
import { parseIntegerParam, parseStringParam } from './common';
import { parseEntityFilterParams } from './parseEntityFilterParams';
import { parseEntitySortFieldParams } from './parseEntitySortFieldParams';
import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams';
import { parseEntityTransformParams } from './parseEntityTransformParams';
import { parseFullTextFilterFields } from './parseFullTextFilterFields';
export function parseQueryEntitiesParams(
params: Record<string, unknown>,
@@ -40,15 +41,23 @@ export function parseQueryEntitiesParams(
}
const filter = parseEntityFilterParams(params);
const query = parseStringParam(params.query, 'query');
const sortFields = parseEntitySortFieldParams(params);
const fullTextFilterTerm = parseStringParam(
params.fullTextFilterTerm,
'fullTextFilterTerm',
);
const fullTextFilterFields = parseFullTextFilterFields(params);
const orderFields = parseEntityOrderFieldParams(params);
const response: Omit<QueryEntitiesInitialRequest, 'authorizationToken'> = {
fields,
filter,
limit,
orderFields: sortFields,
query,
orderFields,
fullTextFilter: {
term: fullTextFilterTerm || '',
fields: fullTextFilterFields,
},
};
return response;