diff --git a/.changeset/soft-roses-cover.md b/.changeset/soft-roses-cover.md new file mode 100644 index 0000000000..50a328729a --- /dev/null +++ b/.changeset/soft-roses-cover.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Added isEntityWith condition helper diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index 1c64fd3f68..124a612f26 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -480,7 +480,7 @@ function isKind(kind: string) { ``` The `@backstage/catalog` plugin provides a couple of built-in conditions, -`isKind`, `isComponentType`, and `isNamespace`. +`isKind`, `isComponentType`, `isResourceType`, `isEntityWith`, and `isNamespace`. In addition to the `EntitySwitch` component, the catalog plugin also exports a new `EntityLayout` component. It is a tweaked version and replacement for the diff --git a/plugins/catalog/src/components/EntitySwitch/conditions.test.ts b/plugins/catalog/src/components/EntitySwitch/conditions.test.ts index 5d8c7c8898..a0485c6a59 100644 --- a/plugins/catalog/src/components/EntitySwitch/conditions.test.ts +++ b/plugins/catalog/src/components/EntitySwitch/conditions.test.ts @@ -20,6 +20,7 @@ import { isKind, isNamespace, isResourceType, + isEntityWith, } from './conditions'; const kubernetesClusterResource: Entity = { @@ -64,6 +65,13 @@ const notComponent: Entity = { spec: { type: 'service' }, }; +const missingSpecType: Entity = { + apiVersion: '', + kind: 'another-type', + metadata: { name: 'anEntity' }, + spec: {}, +}; + const apiKind: Entity = { apiVersion: '', kind: 'api', @@ -125,6 +133,17 @@ describe('isComponentType', () => { }); }); +describe('isEntityWith', () => { + it('allows for a kind-only check (empty type array)', () => { + const checkEntity = isEntityWith({kind: 'api', type: []}); + expect(checkEntity(apiKind)).toBeTruthy(); + }); + it('handles missing spec.type field', () => { + const checkEntity = isEntityWith({kind: 'another-type', type: 'service'}); + expect(checkEntity(missingSpecType)).not.toBeTruthy(); + }); +}); + describe('isKind', () => { it('should check for the intended kind', () => { const checkEntity = isKind('component'); diff --git a/plugins/catalog/src/components/EntitySwitch/conditions.ts b/plugins/catalog/src/components/EntitySwitch/conditions.ts index 222a76b0a9..0ffd9c0b01 100644 --- a/plugins/catalog/src/components/EntitySwitch/conditions.ts +++ b/plugins/catalog/src/components/EntitySwitch/conditions.ts @@ -14,11 +14,12 @@ * limitations under the License. */ -import { - ComponentEntity, - ResourceEntity, - Entity, -} from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; + +export interface EntityPredicates { + kind: string | string[]; + type?: string | string[]; +} function strCmp(a: string | undefined, b: string | undefined): boolean { return Boolean( @@ -37,7 +38,7 @@ function strCmpAll(value: string | undefined, cmpValues: string | string[]) { * @public */ export function isKind(kinds: string | string[]) { - return (entity: Entity) => strCmpAll(entity.kind, kinds); + return isEntityWith({kind: kinds}); } /** @@ -45,13 +46,7 @@ export function isKind(kinds: string | string[]) { * @public */ export function isComponentType(types: string | string[]) { - return (entity: Entity) => { - if (!strCmp(entity.kind, 'component')) { - return false; - } - const componentEntity = entity as ComponentEntity; - return strCmpAll(componentEntity.spec.type, types); - }; + return isEntityWith({ kind: 'component', type: types }); } /** @@ -59,15 +54,33 @@ export function isComponentType(types: string | string[]) { * @public */ export function isResourceType(types: string | string[]) { + return isEntityWith({ kind: 'resource', type: types }); +} + +/** + * + * For use in EntitySwitch.Case. Matches if the entity is the specified kind and type (if present). + * @public + */ +export function isEntityWith(predicate: EntityPredicates) { return (entity: Entity) => { - if (!strCmp(entity.kind, 'resource')) { + if (!strCmpAll(entity.kind, predicate.kind)) { return false; } - const resourceEntity = entity as ResourceEntity; - return strCmpAll(resourceEntity.spec.type, types); + + if (!predicate.type || (Array.isArray(predicate.type) && predicate.type.length === 0)) { + // there's no type check, return true + return true; + } else if (typeof entity.spec?.type !== 'string') { + // we'll reject any entity's with some non-string (including undefined/null) spec.type value + return false; + } + + return strCmpAll(entity.spec.type, predicate.type); }; } + /** * For use in EntitySwitch.Case. Matches if the entity is in a given namespace. * @public