Allow singleton and flexible EntityFilters.

Signed-off-by: Joon Park <joonp@spotify.com>
This commit is contained in:
Joon Park
2021-11-08 12:31:28 +00:00
parent 86bef79ad1
commit c74834a62d
6 changed files with 77 additions and 46 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
'@backstage/plugin-catalog-backend': patch
---
Allow nested EntityFilters
Allow singleton and flexibly nested EntityFilters
+8 -5
View File
@@ -870,11 +870,14 @@ export type EntityAncestryResponse = {
// Warning: (ae-missing-release-tag) "EntityFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export type EntityFilter = {
anyOf: {
allOf: (EntitiesSearchFilter | EntityFilter)[];
}[];
};
export type EntityFilter =
| {
allOf: EntityFilter[];
}
| {
anyOf: EntityFilter[];
}
| EntitiesSearchFilter;
// Warning: (ae-missing-release-tag) "EntityPagination" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
+4 -3
View File
@@ -22,9 +22,10 @@ import { Entity, EntityRelationSpec } from '@backstage/catalog-model';
* Any (at least one) of the outer sets must match, within which all of the
* individual filters must match.
*/
export type EntityFilter = {
anyOf: { allOf: (EntitiesSearchFilter | EntityFilter)[] }[];
};
export type EntityFilter =
| { allOf: EntityFilter[] }
| { anyOf: EntityFilter[] }
| EntitiesSearchFilter;
/**
* A pagination rule for entities.
@@ -46,11 +46,11 @@ import {
DbPageInfo,
Transaction,
} from './types';
import {
EntityPagination,
EntityFilter,
EntitiesSearchFilter,
} from '../../catalog/types';
import { EntityPagination, EntitiesSearchFilter } from '../../catalog/types';
type LegacyEntityFilter = {
anyOf: { allOf: EntitiesSearchFilter[] }[];
};
// The number of items that are sent per batch to the database layer, when
// doing .batchInsert calls to knex. This needs to be low enough to not cause
@@ -221,10 +221,23 @@ export class CommonDatabase implements Database {
let entitiesQuery = tx<DbEntitiesRow>('entities');
for (const singleFilter of request?.filter?.anyOf ?? []) {
if (
request?.filter &&
(request.filter.hasOwnProperty('key') ||
request.filter.hasOwnProperty('allOf'))
) {
throw new Error(
'Filters for the legacy CommonDatabase must obey the { anyOf: [{ allOf: [] }] } format.',
);
}
for (const singleFilter of (request?.filter as LegacyEntityFilter)?.anyOf ??
[]) {
entitiesQuery = entitiesQuery.orWhere(function singleFilterFn() {
for (const filter of singleFilter.allOf) {
if (isEntityFilter(filter)) {
if (
filter.hasOwnProperty('anyOf') ||
filter.hasOwnProperty('allOf')
) {
throw new Error(
'Nested filters are not supported in the legacy CommonDatabase',
);
@@ -612,9 +625,3 @@ function deduplicateRelations(
r => `${r.source_full_name}:${r.target_full_name}:${r.type}`,
);
}
function isEntityFilter(
filter: EntitiesSearchFilter | EntityFilter,
): filter is EntityFilter {
return filter.hasOwnProperty('anyOf');
}
@@ -284,9 +284,7 @@ describe('NextEntitiesCatalog', () => {
key: 'spec.test',
matchValueExists: true,
};
const request = {
filter: { anyOf: [{ allOf: [testFilter] }] },
};
const request = { filter: testFilter };
const { entities } = await catalog.entities(request);
expect(entities.length).toBe(1);
@@ -344,14 +342,10 @@ describe('NextEntitiesCatalog', () => {
};
const request = {
filter: {
anyOf: [
allOf: [
testFilter1,
{
allOf: [
testFilter1,
{
anyOf: [{ allOf: [testFilter2] }, { allOf: [testFilter3] }],
},
],
anyOf: [testFilter2, testFilter3],
},
],
},
@@ -107,30 +107,56 @@ function addCondition(
);
}
function isEntityFilter(
function isEntitiesSearchFilter(
filter: EntitiesSearchFilter | EntityFilter,
): filter is EntityFilter {
): filter is EntitiesSearchFilter {
return filter.hasOwnProperty('key');
}
function isAndEntityFilter(
filter: { allOf: EntityFilter[] } | EntityFilter,
): filter is { allOf: EntityFilter[] } {
return filter.hasOwnProperty('allOf');
}
function isOrEntityFilter(
filter: { anyOf: EntityFilter[] } | EntityFilter,
): filter is { anyOf: EntityFilter[] } {
return filter.hasOwnProperty('anyOf');
}
function parseFilter(
filters: EntityFilter,
filter: EntityFilter,
query: Knex.QueryBuilder,
db: Knex,
): Knex.QueryBuilder {
let cumulativeQuery = query;
for (const singleFilter of filters?.anyOf ?? []) {
cumulativeQuery = cumulativeQuery.orWhere(function singleFilterFn() {
for (const filter of singleFilter.allOf) {
if (isEntityFilter(filter)) {
this.andWhere(subQuery => parseFilter(filter, subQuery, db));
} else {
addCondition(this, db, filter);
}
}
if (isEntitiesSearchFilter(filter)) {
return query.where(function filterFunction() {
addCondition(this, db, filter);
});
}
return cumulativeQuery;
if (isOrEntityFilter(filter)) {
let cumulativeQuery = query;
for (const subFilter of filter.anyOf ?? []) {
cumulativeQuery = cumulativeQuery.orWhere(subQuery =>
parseFilter(subFilter, subQuery, db),
);
}
return cumulativeQuery;
}
if (isAndEntityFilter(filter)) {
let cumulativeQuery = query;
for (const subFilter of filter.allOf ?? []) {
cumulativeQuery = cumulativeQuery.andWhere(subQuery =>
parseFilter(subFilter, subQuery, db),
);
}
return cumulativeQuery;
}
return query;
}
export class NextEntitiesCatalog implements EntitiesCatalog {