catalog-backend: move query to fullTextFilterTerm
Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
@@ -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: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+17
-17
@@ -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/);
|
||||
});
|
||||
});
|
||||
+6
-6
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user