Skip to content

Commit

Permalink
Merge pull request backstage#26366 from SnowBlitzer/jordans/addFilter…
Browse files Browse the repository at this point in the history
…Negation

Negation key word to entity filter
  • Loading branch information
freben authored Oct 4, 2024
2 parents 323e612 + d516cc6 commit 962ab84
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/green-cooks-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': minor
---

Adding negation keyword for entity filtering
32 changes: 27 additions & 5 deletions plugins/catalog/src/alpha/filter/parseFilterExpression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'"`,
Expand Down Expand Up @@ -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 },
]);
});

Expand Down
25 changes: 15 additions & 10 deletions plugins/catalog/src/alpha/filter/parseFilterExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const rootMatcherFactories: Record<
(
parameters: string[],
onParseError: (error: Error) => void,
negation?: boolean,
) => EntityMatcherFn
> = {
kind: createKindMatcher,
Expand Down Expand Up @@ -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(
Expand All @@ -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) =>
Expand All @@ -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(
Expand All @@ -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;
Expand Down

0 comments on commit 962ab84

Please sign in to comment.