Merge pull request #16880 from brianphillips/new-entity-switch-condition

Add isEntityWith helper method to EntitySwitch conditions
This commit is contained in:
Johan Haals
2023-03-21 10:13:10 +01:00
committed by GitHub
6 changed files with 79 additions and 21 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': minor
---
Added `isEntityWith` condition helper for `EntitySwitch` case statements.
+1 -1
View File
@@ -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
+13
View File
@@ -355,6 +355,14 @@ export const EntityListContainer: (props: {
// @public
export function EntityOrphanWarning(): JSX.Element;
// @public (undocumented)
export interface EntityPredicates {
// (undocumented)
kind?: string | string[];
// (undocumented)
type?: string | string[];
}
// @public
export function EntityProcessingErrorsPanel(): JSX.Element | null;
@@ -433,6 +441,11 @@ export function isComponentType(
types: string | string[],
): (entity: Entity) => boolean;
// @public
export function isEntityWith(
predicate: EntityPredicates,
): (entity: Entity) => boolean;
// @public
export function isKind(kinds: string | string[]): (entity: Entity) => boolean;
@@ -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,25 @@ 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();
});
it('allows a check against no criteria', () => {
const checkEntity = isEntityWith({});
expect(checkEntity(apiKind)).toBeTruthy();
});
it('allows a check against empty criteria', () => {
const checkEntity = isEntityWith({ kind: [], type: [] });
expect(checkEntity(apiKind)).toBeTruthy();
});
});
describe('isKind', () => {
it('should check for the intended kind', () => {
const checkEntity = isKind('component');
@@ -14,22 +14,26 @@
* limitations under the License.
*/
import {
ComponentEntity,
ResourceEntity,
Entity,
} from '@backstage/catalog-model';
import { Entity } from '@backstage/catalog-model';
function strCmp(a: string | undefined, b: string | undefined): boolean {
/** @public */
export interface EntityPredicates {
kind?: string | string[];
type?: string | string[];
}
function strCmp(a: unknown, b: string | undefined): boolean {
return Boolean(
a && a?.toLocaleLowerCase('en-US') === b?.toLocaleLowerCase('en-US'),
a &&
typeof a === 'string' &&
a?.toLocaleLowerCase('en-US') === b?.toLocaleLowerCase('en-US'),
);
}
function strCmpAll(value: string | undefined, cmpValues: string | string[]) {
function strCmpAll(value: unknown, cmpValues: string | string[]) {
return typeof cmpValues === 'string'
? strCmp(value, cmpValues)
: cmpValues.some(cmpVal => strCmp(value, cmpVal));
: cmpValues.length === 0 || cmpValues.some(cmpVal => strCmp(value, cmpVal));
}
/**
@@ -37,7 +41,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 +49,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,12 +57,25 @@ 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 (predicate.kind && !strCmpAll(entity.kind, predicate.kind)) {
return false;
}
const resourceEntity = entity as ResourceEntity;
return strCmpAll(resourceEntity.spec.type, types);
if (predicate.type && !strCmpAll(entity.spec?.type, predicate.type)) {
return false;
}
// there's no type check, return true
return true;
};
}
@@ -16,9 +16,11 @@
export { EntitySwitch } from './EntitySwitch';
export type { EntitySwitchProps, EntitySwitchCaseProps } from './EntitySwitch';
export type { EntityPredicates } from './conditions';
export {
isKind,
isNamespace,
isComponentType,
isResourceType,
isEntityWith,
} from './conditions';