Merge pull request #26366 from SnowBlitzer/jordans/addFilterNegation

Negation key word to entity filter
This commit is contained in:
Fredrik Adelöw
2024-10-04 09:58:59 +02:00
committed by GitHub
3 changed files with 47 additions and 15 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': minor
---
Adding negation keyword for entity filtering
@@ -93,6 +93,20 @@ describe('parseFilterExpression', () => {
);
});
it('recognizes negation key', () => {
const component = { kind: 'Component' } as unknown as Entity;
expect(run('not:kind:user')(component)).toBe(true);
});
it('supports negation and affirmative expressions', () => {
const component = {
kind: 'Component',
spec: { type: 'service' },
} as unknown as Entity;
expect(run('not:kind:user type:service')(component)).toBe(true);
expect(run('type:service not:kind:user')(component)).toBe(true);
});
it('rejects unknown keys', () => {
expect(() => run('unknown:foo')).toThrowErrorMatchingInlineSnapshot(
`"'unknown' is not a valid filter expression key, expected one of 'kind','type','is','has'"`,
@@ -123,17 +137,25 @@ describe('splitFilterExpression', () => {
expect(run('')).toEqual([]);
expect(run(' ')).toEqual([]);
expect(run('kind:component')).toEqual([
{ key: 'kind', parameters: ['component'] },
{ key: 'kind', parameters: ['component'], negation: false },
]);
expect(run('kind:component,user')).toEqual([
{ key: 'kind', parameters: ['component', 'user'] },
{ key: 'kind', parameters: ['component', 'user'], negation: false },
]);
expect(run('kind:component,user not:type:foo')).toEqual([
{ key: 'kind', parameters: ['component', 'user'], negation: false },
{ key: 'type', parameters: ['foo'], negation: true },
]);
expect(run('not:type:foo kind:component,user')).toEqual([
{ key: 'type', parameters: ['foo'], negation: true },
{ key: 'kind', parameters: ['component', 'user'], negation: false },
]);
expect(run('kind:component,user type:foo')).toEqual([
{ key: 'kind', parameters: ['component', 'user'] },
{ key: 'type', parameters: ['foo'] },
{ key: 'kind', parameters: ['component', 'user'], negation: false },
{ key: 'type', parameters: ['foo'], negation: false },
]);
expect(run('with:multiple:colons')).toEqual([
{ key: 'with', parameters: ['multiple:colons'] },
{ key: 'with', parameters: ['multiple:colons'], negation: false },
]);
});
@@ -27,6 +27,7 @@ const rootMatcherFactories: Record<
(
parameters: string[],
onParseError: (error: Error) => void,
negation?: boolean,
) => EntityMatcherFn
> = {
kind: createKindMatcher,
@@ -60,9 +61,9 @@ export function parseFilterExpression(expression: string): {
const parts = splitFilterExpression(expression, e =>
expressionParseErrors.push(e),
);
const matchers = parts.flatMap(part => {
const factory = rootMatcherFactories[part.key];
const negation = part.negation;
if (!factory) {
const known = Object.keys(rootMatcherFactories).map(m => `'${m}'`);
expressionParseErrors.push(
@@ -76,7 +77,8 @@ export function parseFilterExpression(expression: string): {
const matcher = factory(part.parameters, e =>
expressionParseErrors.push(e),
);
return [matcher];
return [negation ? (entity: Entity) => !matcher(entity) : matcher];
});
const filterFn = (entity: Entity) =>
@@ -97,16 +99,20 @@ export function parseFilterExpression(expression: string): {
export function splitFilterExpression(
expression: string,
onParseError: (error: Error) => void,
): Array<{ key: string; parameters: string[] }> {
): Array<{ key: string; parameters: string[]; negation: boolean }> {
const words = expression
.split(' ')
.map(w => w.trim())
.filter(Boolean);
const result = new Array<{ key: string; parameters: string[] }>();
const result = new Array<{
key: string;
parameters: string[];
negation: boolean;
}>();
for (const word of words) {
const match = word.match(/^([^:]+):(.+)$/);
const match = word.match(/^(not:)?([^:]+):(.+)$/);
if (!match) {
onParseError(
new InputError(
@@ -115,11 +121,10 @@ export function splitFilterExpression(
);
continue;
}
const key = match[1];
const parameters = match[2].split(',').filter(Boolean); // silently ignore double commas
result.push({ key, parameters });
const key = match[2];
const parameters = match[3].split(',').filter(Boolean); // silently ignore double commas
const negation = Boolean(match[1]);
result.push({ key, parameters, negation });
}
return result;