Negation key word to entity filter
Signed-off-by: Jordan Snow <jordans@spotify.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { EntityMatcherFn } from './types';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
const allowedMatchers: Record<string, EntityMatcherFn> = {
|
||||
labels: entity => {
|
||||
@@ -32,6 +33,7 @@ const allowedMatchers: Record<string, EntityMatcherFn> = {
|
||||
export function createHasMatcher(
|
||||
parameters: string[],
|
||||
onParseError: (error: Error) => void,
|
||||
negation?: boolean,
|
||||
): EntityMatcherFn {
|
||||
const matchers = parameters.flatMap(parameter => {
|
||||
const matcher = allowedMatchers[parameter.toLocaleLowerCase('en-US')];
|
||||
@@ -47,6 +49,7 @@ export function createHasMatcher(
|
||||
return [matcher];
|
||||
});
|
||||
|
||||
return entity =>
|
||||
const isMatch = (entity: Entity) =>
|
||||
matchers.length ? matchers.some(matcher => matcher(entity)) : true;
|
||||
return negation ? entity => !isMatch(entity) : isMatch;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const allowedMatchers: Record<string, EntityMatcherFn> = {
|
||||
export function createIsMatcher(
|
||||
parameters: string[],
|
||||
onParseError: (error: Error) => void,
|
||||
negation?: boolean,
|
||||
): EntityMatcherFn {
|
||||
const matchers = parameters.flatMap(parameter => {
|
||||
const matcher = allowedMatchers[parameter.toLocaleLowerCase('en-US')];
|
||||
@@ -43,6 +44,7 @@ export function createIsMatcher(
|
||||
return [matcher];
|
||||
});
|
||||
|
||||
return entity =>
|
||||
const isMatch = (entity: any) =>
|
||||
matchers.length ? matchers.some(matcher => matcher(entity)) : true;
|
||||
return negation ? entity => !isMatch(entity) : isMatch;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ import { EntityMatcherFn } from './types';
|
||||
export function createKindMatcher(
|
||||
parameters: string[],
|
||||
_onParseError: (error: Error) => void,
|
||||
negation?: boolean,
|
||||
): EntityMatcherFn {
|
||||
const items = parameters.map(p => p.toLocaleLowerCase('en-US'));
|
||||
return entity => items.includes(entity.kind.toLocaleLowerCase('en-US'));
|
||||
const isMatch = (entity: any) =>
|
||||
items.includes(entity.kind.toLocaleLowerCase('en-US'));
|
||||
return negation ? entity => !isMatch(entity) : isMatch;
|
||||
}
|
||||
|
||||
@@ -22,13 +22,14 @@ import { EntityMatcherFn } from './types';
|
||||
export function createTypeMatcher(
|
||||
parameters: string[],
|
||||
_onParseError: (error: Error) => void,
|
||||
negation?: boolean,
|
||||
): EntityMatcherFn {
|
||||
const items = parameters.map(p => p.toLocaleLowerCase('en-US'));
|
||||
return entity => {
|
||||
const value = entity.spec?.type;
|
||||
return (
|
||||
const isMatch =
|
||||
typeof value === 'string' &&
|
||||
items.includes(value.toLocaleLowerCase('en-US'))
|
||||
);
|
||||
items.includes(value.toLocaleLowerCase('en-US'));
|
||||
return negation ? !isMatch : isMatch;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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'"`,
|
||||
|
||||
@@ -27,6 +27,7 @@ const rootMatcherFactories: Record<
|
||||
(
|
||||
parameters: string[],
|
||||
onParseError: (error: Error) => void,
|
||||
negation?: boolean,
|
||||
) => EntityMatcherFn
|
||||
> = {
|
||||
kind: createKindMatcher,
|
||||
@@ -60,10 +61,14 @@ export function parseFilterExpression(expression: string): {
|
||||
const parts = splitFilterExpression(expression, e =>
|
||||
expressionParseErrors.push(e),
|
||||
);
|
||||
|
||||
let negation = false;
|
||||
const matchers = parts.flatMap(part => {
|
||||
const factory = rootMatcherFactories[part.key];
|
||||
if (!factory) {
|
||||
if (isNegation(part.key)) {
|
||||
negation = true;
|
||||
return [];
|
||||
}
|
||||
const known = Object.keys(rootMatcherFactories).map(m => `'${m}'`);
|
||||
expressionParseErrors.push(
|
||||
new InputError(
|
||||
@@ -73,9 +78,12 @@ export function parseFilterExpression(expression: string): {
|
||||
return [];
|
||||
}
|
||||
|
||||
const matcher = factory(part.parameters, e =>
|
||||
expressionParseErrors.push(e),
|
||||
const matcher = factory(
|
||||
part.parameters,
|
||||
e => expressionParseErrors.push(e),
|
||||
negation,
|
||||
);
|
||||
negation = false;
|
||||
return [matcher];
|
||||
});
|
||||
|
||||
@@ -106,7 +114,11 @@ export function splitFilterExpression(
|
||||
const result = new Array<{ key: string; parameters: string[] }>();
|
||||
|
||||
for (const word of words) {
|
||||
const match = word.match(/^([^:]+):(.+)$/);
|
||||
let match = word.match(/^([^:]+):(.+)$/);
|
||||
if (match && isNegation(match[1])) {
|
||||
result.push({ key: 'not', parameters: [] });
|
||||
match = match[2].match(/^([^:]+):(.+)$/) || null;
|
||||
}
|
||||
if (!match) {
|
||||
onParseError(
|
||||
new InputError(
|
||||
@@ -124,3 +136,10 @@ export function splitFilterExpression(
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function isNegation(parameter: string): boolean {
|
||||
if (parameter.toLocaleLowerCase('en-US') === 'not') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user