catalog-model: implement matchEntityWithRef for client side filtering
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-model': patch
|
||||
---
|
||||
|
||||
Implement matchEntityWithRef for client side filtering of entities by ref matching
|
||||
@@ -27,6 +27,7 @@ export type {
|
||||
} from './Entity';
|
||||
export * from './policies';
|
||||
export {
|
||||
compareEntityToRef,
|
||||
getEntityName,
|
||||
parseEntityName,
|
||||
parseEntityRef,
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
|
||||
import { ENTITY_DEFAULT_NAMESPACE } from './constants';
|
||||
import { Entity } from './Entity';
|
||||
import { parseEntityName, parseEntityRef, serializeEntityRef } from './ref';
|
||||
import {
|
||||
compareEntityToRef,
|
||||
parseEntityName,
|
||||
parseEntityRef,
|
||||
serializeEntityRef,
|
||||
} from './ref';
|
||||
|
||||
describe('ref', () => {
|
||||
describe('parseEntityName', () => {
|
||||
@@ -381,4 +386,320 @@ describe('ref', () => {
|
||||
).toEqual({ kind: 'a', namespace: 'b', name: 'c/x' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('compareEntityToRef', () => {
|
||||
const entityWithNamespace: Entity = {
|
||||
apiVersion: 'a',
|
||||
kind: 'K',
|
||||
metadata: {
|
||||
name: 'n',
|
||||
namespace: 'ns',
|
||||
},
|
||||
};
|
||||
const entityWithoutNamespace: Entity = {
|
||||
apiVersion: 'a',
|
||||
kind: 'K',
|
||||
metadata: {
|
||||
name: 'n',
|
||||
},
|
||||
};
|
||||
|
||||
it('handles matching string refs', () => {
|
||||
expect(compareEntityToRef(entityWithNamespace, 'K:ns/n')).toBe(true);
|
||||
expect(compareEntityToRef(entityWithNamespace, 'k:nS/N')).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, 'K:n', {
|
||||
defaultNamespace: 'ns',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, 'K:n', {
|
||||
defaultNamespace: 'Ns',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, 'ns/n', { defaultKind: 'K' }),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, 'n', {
|
||||
defaultKind: 'K',
|
||||
defaultNamespace: 'ns',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, 'N', {
|
||||
defaultKind: 'k',
|
||||
defaultNamespace: 'nS',
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(compareEntityToRef(entityWithoutNamespace, 'K:default/n')).toBe(
|
||||
true,
|
||||
);
|
||||
expect(compareEntityToRef(entityWithoutNamespace, 'K:deFault/n')).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, 'K:n', {
|
||||
defaultNamespace: 'default',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, 'K:n', {
|
||||
defaultNamespace: 'deFault',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(compareEntityToRef(entityWithoutNamespace, 'K:default/n')).toBe(
|
||||
true,
|
||||
);
|
||||
expect(compareEntityToRef(entityWithoutNamespace, 'K:n')).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, 'default/n', {
|
||||
defaultKind: 'K',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, 'n', {
|
||||
defaultKind: 'K',
|
||||
defaultNamespace: 'default',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, 'n', {
|
||||
defaultKind: 'K',
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('handles mismatching string refs', () => {
|
||||
expect(compareEntityToRef(entityWithNamespace, 'X:ns/n')).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, 'ns/n', {
|
||||
defaultKind: 'X',
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
expect(compareEntityToRef(entityWithNamespace, 'K:xx/n')).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, 'K:n', {
|
||||
defaultNamespace: 'xx',
|
||||
}),
|
||||
).toBe(false);
|
||||
|
||||
expect(compareEntityToRef(entityWithNamespace, 'K:ns/x')).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, 'x', {
|
||||
defaultKind: 'K',
|
||||
defaultNamespace: 'ns',
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('handles matching compound refs', () => {
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, {
|
||||
kind: 'K',
|
||||
namespace: 'ns',
|
||||
name: 'n',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, {
|
||||
kind: 'k',
|
||||
namespace: 'Ns',
|
||||
name: 'N',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithNamespace,
|
||||
{ kind: 'K', name: 'n' },
|
||||
{
|
||||
defaultNamespace: 'ns',
|
||||
},
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithNamespace,
|
||||
{ namespace: 'ns', name: 'n' },
|
||||
{ defaultKind: 'K' },
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, 'n', {
|
||||
defaultKind: 'K',
|
||||
defaultNamespace: 'ns',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, 'N', {
|
||||
defaultKind: 'k',
|
||||
defaultNamespace: 'nS',
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, {
|
||||
kind: 'K',
|
||||
namespace: 'default',
|
||||
name: 'n',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, {
|
||||
kind: 'k',
|
||||
namespace: 'deFault',
|
||||
name: 'N',
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithoutNamespace,
|
||||
{ kind: 'K', name: 'n' },
|
||||
{
|
||||
defaultNamespace: 'default',
|
||||
},
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, { kind: 'K', name: 'n' }),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithoutNamespace,
|
||||
{ namespace: 'default', name: 'n' },
|
||||
{
|
||||
defaultKind: 'K',
|
||||
},
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithoutNamespace,
|
||||
{ name: 'n' },
|
||||
{
|
||||
defaultKind: 'K',
|
||||
defaultNamespace: 'default',
|
||||
},
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithoutNamespace,
|
||||
{ name: 'N' },
|
||||
{
|
||||
defaultKind: 'k',
|
||||
defaultNamespace: 'defAult',
|
||||
},
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithoutNamespace,
|
||||
{ name: 'n' },
|
||||
{
|
||||
defaultKind: 'K',
|
||||
},
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('handles mismatching compound refs', () => {
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, {
|
||||
kind: 'X',
|
||||
namespace: 'ns',
|
||||
name: 'n',
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithNamespace,
|
||||
{
|
||||
namespace: 'ns',
|
||||
name: 'n',
|
||||
},
|
||||
{ defaultKind: 'X' },
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, {
|
||||
kind: 'X',
|
||||
namespace: 'default',
|
||||
name: 'n',
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithoutNamespace,
|
||||
{
|
||||
namespace: 'default',
|
||||
name: 'n',
|
||||
},
|
||||
{ defaultKind: 'X' },
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, {
|
||||
kind: 'K',
|
||||
namespace: 'xx',
|
||||
name: 'n',
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithNamespace,
|
||||
{
|
||||
kind: 'K',
|
||||
name: 'n',
|
||||
},
|
||||
{ defaultNamespace: 'xx' },
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, {
|
||||
kind: 'K',
|
||||
namespace: 'xx',
|
||||
name: 'n',
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithoutNamespace,
|
||||
{
|
||||
kind: 'K',
|
||||
name: 'n',
|
||||
},
|
||||
{ defaultNamespace: 'xx' },
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
compareEntityToRef(entityWithNamespace, {
|
||||
kind: 'K',
|
||||
namespace: 'ns',
|
||||
name: 'x',
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(entityWithoutNamespace, {
|
||||
kind: 'K',
|
||||
namespace: 'default',
|
||||
name: 'x',
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
compareEntityToRef(
|
||||
entityWithoutNamespace,
|
||||
{
|
||||
kind: 'K',
|
||||
name: 'x',
|
||||
},
|
||||
{ defaultNamespace: 'default' },
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,27 @@ import { EntityName, EntityRef } from '../types';
|
||||
import { ENTITY_DEFAULT_NAMESPACE } from './constants';
|
||||
import { Entity } from './Entity';
|
||||
|
||||
function parseRefString(
|
||||
ref: string,
|
||||
): {
|
||||
kind?: string;
|
||||
namespace?: string;
|
||||
name: string;
|
||||
} {
|
||||
const match = /^([^:/]+:)?([^:/]+\/)?([^:/]+)$/.exec(ref.trim());
|
||||
if (!match) {
|
||||
throw new TypeError(
|
||||
`Entity reference "${ref}" was not on the form [<kind>:][<namespace>/]<name>`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
kind: match[1]?.slice(0, -1),
|
||||
namespace: match[2]?.slice(0, -1),
|
||||
name: match[3],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the kind, namespace and name that form the name triplet of the
|
||||
* given entity.
|
||||
@@ -121,17 +142,11 @@ export function parseEntityRef(
|
||||
}
|
||||
|
||||
if (typeof ref === 'string') {
|
||||
const match = /^([^:/]+:)?([^:/]+\/)?([^:/]+)$/.exec(ref.trim());
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`Entity reference "${ref}" was not on the form [<kind>:][<namespace>/]<name>`,
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = parseRefString(ref);
|
||||
return {
|
||||
kind: match[1]?.slice(0, -1) ?? context.defaultKind,
|
||||
namespace: match[2]?.slice(0, -1) ?? context.defaultNamespace,
|
||||
name: match[3],
|
||||
kind: parsed.kind ?? context.defaultKind,
|
||||
namespace: parsed.namespace ?? context.defaultNamespace,
|
||||
name: parsed.name,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -196,3 +211,53 @@ export function serializeEntityRef(
|
||||
|
||||
return `${kind ? `${kind}:` : ''}${namespace ? `${namespace}/` : ''}${name}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares an entity to either a string reference or a compound reference.
|
||||
*
|
||||
* The comparison is case insensitive, and all of kind, namespace, and name
|
||||
* must match (after applying the optional context to the ref).
|
||||
*
|
||||
* @param entity The entity to match
|
||||
* @param ref A string or compound entity ref
|
||||
* @param context An optional context of default kind and namespace, that apply
|
||||
* to the ref if given
|
||||
* @returns True if matching, false otherwise
|
||||
*/
|
||||
export function compareEntityToRef(
|
||||
entity: Entity,
|
||||
ref: EntityRef | EntityName,
|
||||
context?: EntityRefContext,
|
||||
): boolean {
|
||||
const entityKind = entity.kind;
|
||||
const entityNamespace = entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE;
|
||||
const entityName = entity.metadata.name;
|
||||
|
||||
let refKind: string | undefined;
|
||||
let refNamespace: string | undefined;
|
||||
let refName: string;
|
||||
if (typeof ref === 'string') {
|
||||
const parsed = parseRefString(ref);
|
||||
refKind = parsed.kind || context?.defaultKind;
|
||||
refNamespace =
|
||||
parsed.namespace || context?.defaultNamespace || ENTITY_DEFAULT_NAMESPACE;
|
||||
refName = parsed.name;
|
||||
} else {
|
||||
refKind = ref.kind || context?.defaultKind;
|
||||
refNamespace =
|
||||
ref.namespace || context?.defaultNamespace || ENTITY_DEFAULT_NAMESPACE;
|
||||
refName = ref.name;
|
||||
}
|
||||
|
||||
if (!refKind || !refNamespace) {
|
||||
throw new Error(
|
||||
`Entity reference or context did not contain kind and namespace`,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
entityKind.toLowerCase() === refKind.toLowerCase() &&
|
||||
entityNamespace.toLowerCase() === refNamespace.toLowerCase() &&
|
||||
entityName.toLowerCase() === refName.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user