Merge pull request #15063 from rikez/feat/enhance-owner-picker

feat(scaffolder): add EntityFilterQuery support on Entity and Owner Picker components
This commit is contained in:
Johan Haals
2022-12-22 16:44:32 +01:00
committed by GitHub
15 changed files with 320 additions and 35 deletions
@@ -73,16 +73,16 @@ spec:
description: Owner of the component
ui:field: OwnerPicker
ui:options:
allowedKinds:
- Group
catalogFilter:
kind: Group
system:
title: System
type: string
description: System of the component
ui:field: EntityPicker
ui:options:
allowedKinds:
- System
catalogFilter:
kind: System
defaultKind: System
- title: Choose a location
@@ -74,16 +74,16 @@ spec:
description: Owner of the component
ui:field: OwnerPicker
ui:options:
allowedKinds:
- Group
catalogFilter:
kind: Group
system:
title: System
type: string
description: System of the component
ui:field: EntityPicker
ui:options:
allowedKinds:
- System
catalogFilter:
kind: System
defaultKind: System
- title: Choose a location
@@ -73,16 +73,16 @@ spec:
description: Owner of the component
ui:field: OwnerPicker
ui:options:
allowedKinds:
- Group
catalogFilter:
kind: Group
system:
title: System
type: string
description: System of the component
ui:field: EntityPicker
ui:options:
allowedKinds:
- System
catalogFilter:
kind: System
defaultKind: System
- title: Choose a location
@@ -49,8 +49,8 @@ spec:
description: Owner of the component
ui:field: OwnerPicker
ui:options:
allowedKinds:
- Group
catalogFilter:
kind: Group
steps:
- id: fetch-base
+16
View File
@@ -88,6 +88,10 @@ export const EntityPickerFieldExtension: FieldExtensionComponent<
defaultNamespace?: string | false | undefined;
allowedKinds?: string[] | undefined;
allowArbitraryValues?: boolean | undefined;
catalogFilter?:
| Record<string, string | string[]>
| Record<string, string | string[]>[]
| undefined;
}
>;
@@ -99,6 +103,10 @@ export const EntityPickerFieldSchema: FieldSchema<
defaultNamespace?: string | false | undefined;
allowedKinds?: string[] | undefined;
allowArbitraryValues?: boolean | undefined;
catalogFilter?:
| Record<string, string | string[]>
| Record<string, string | string[]>[]
| undefined;
}
>;
@@ -316,6 +324,10 @@ export const OwnerPickerFieldExtension: FieldExtensionComponent<
defaultNamespace?: string | false | undefined;
allowedKinds?: string[] | undefined;
allowArbitraryValues?: boolean | undefined;
catalogFilter?:
| Record<string, string | string[]>
| Record<string, string | string[]>[]
| undefined;
}
>;
@@ -326,6 +338,10 @@ export const OwnerPickerFieldSchema: FieldSchema<
defaultNamespace?: string | false | undefined;
allowedKinds?: string[] | undefined;
allowArbitraryValues?: boolean | undefined;
catalogFilter?:
| Record<string, string | string[]>
| Record<string, string | string[]>[]
| undefined;
}
>;
@@ -53,8 +53,8 @@ parameters:
description: Owner of the component
ui:field: OwnerPicker
ui:options:
allowedKinds:
- Group
catalogFilter:
kind: Group
- title: Choose a location
required:
- repoUrl
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { EntityFilterQuery } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
@@ -34,7 +35,13 @@ describe('<EntityPicker />', () => {
const schema = {};
const required = false;
let uiSchema: {
'ui:options': { allowedKinds?: string[]; defaultKind?: string };
'ui:options': {
allowedKinds?: string[];
defaultKind?: string;
allowArbitraryValues?: boolean;
defaultNamespace?: string | false;
catalogFilter?: EntityFilterQuery;
};
};
const rawErrors: string[] = [];
const formData = undefined;
@@ -66,7 +73,7 @@ describe('<EntityPicker />', () => {
afterEach(() => jest.resetAllMocks());
describe('without allowedKinds', () => {
describe('without allowedKinds and catalogFilter', () => {
beforeEach(() => {
uiSchema = { 'ui:options': {} };
props = {
@@ -136,4 +143,97 @@ describe('<EntityPicker />', () => {
});
});
});
describe('with catalogFilter', () => {
beforeEach(() => {
uiSchema = {
'ui:options': {
catalogFilter: [
{
kind: ['Group'],
'metadata.name': 'test-entity',
},
{
kind: ['User'],
'metadata.name': 'test-entity',
},
],
},
};
props = {
onChange,
schema,
required,
uiSchema,
rawErrors,
formData,
} as unknown as FieldProps<any>;
catalogApi.getEntities.mockResolvedValue({ items: entities });
});
it('searches for a specific group entity', async () => {
await renderInTestApp(
<Wrapper>
<EntityPicker {...props} />
</Wrapper>,
);
expect(catalogApi.getEntities).toHaveBeenCalledWith({
filter: [
{
kind: ['Group'],
'metadata.name': 'test-entity',
},
{
kind: ['User'],
'metadata.name': 'test-entity',
},
],
});
});
});
describe('catalogFilter should take precedence over allowedKinds', () => {
beforeEach(() => {
uiSchema = {
'ui:options': {
catalogFilter: [
{
kind: ['Group'],
'metadata.name': 'test-group',
},
],
allowedKinds: ['User'],
},
};
props = {
onChange,
schema,
required,
uiSchema,
rawErrors,
formData,
} as unknown as FieldProps<any>;
catalogApi.getEntities.mockResolvedValue({ items: entities });
});
it('searches for a Group entity', async () => {
await renderInTestApp(
<Wrapper>
<EntityPicker {...props} />
</Wrapper>,
);
expect(catalogApi.getEntities).toHaveBeenCalledWith({
filter: [
{
kind: ['Group'],
'metadata.name': 'test-group',
},
],
});
});
});
});
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type EntityFilterQuery } from '@backstage/catalog-client';
import { useApi } from '@backstage/core-plugin-api';
import {
catalogApiRef,
@@ -44,6 +45,11 @@ export const EntityPicker = (props: EntityPickerProps) => {
idSchema,
} = props;
const allowedKinds = uiSchema['ui:options']?.allowedKinds;
const catalogFilter: EntityFilterQuery | undefined =
uiSchema['ui:options']?.catalogFilter ||
(allowedKinds && { kind: allowedKinds });
const defaultKind = uiSchema['ui:options']?.defaultKind;
const defaultNamespace = uiSchema['ui:options']?.defaultNamespace;
@@ -51,7 +57,7 @@ export const EntityPicker = (props: EntityPickerProps) => {
const { value: entities, loading } = useAsync(() =>
catalogApi.getEntities(
allowedKinds ? { filter: { kind: allowedKinds } } : undefined,
catalogFilter ? { filter: catalogFilter } : undefined,
),
);
@@ -16,16 +16,28 @@
import { z } from 'zod';
import { makeFieldSchemaFromZod } from '../utils';
/**
* @public
*/
export const entityQueryFilterExpressionSchema = z.record(
z.string().or(z.array(z.string())),
);
/**
* @public
*/
export const EntityPickerFieldSchema = makeFieldSchemaFromZod(
z.string(),
z.object({
/**
* @deprecated Use `catalogFilter` instead.
*/
allowedKinds: z
.array(z.string())
.optional()
.describe('List of kinds of entities to derive options from'),
.describe(
'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from',
),
defaultKind: z
.string()
.optional()
@@ -42,6 +54,11 @@ export const EntityPickerFieldSchema = makeFieldSchemaFromZod(
.describe(
'The default namespace. Options with this namespace will not be prefixed.',
),
catalogFilter: z
.array(entityQueryFilterExpressionSchema)
.or(entityQueryFilterExpressionSchema)
.optional()
.describe('List of key-value filter expression for entities'),
}),
);
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { type EntityFilterQuery } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
@@ -32,7 +33,15 @@ describe('<OwnerPicker />', () => {
const onChange = jest.fn();
const schema = {};
const required = false;
let uiSchema: { 'ui:options': { allowedKinds?: string[] } };
let uiSchema: {
'ui:options': {
allowedKinds?: string[];
defaultKind?: string;
allowArbitraryValues?: boolean;
defaultNamespace?: string | false;
catalogFilter?: EntityFilterQuery;
};
};
const rawErrors: string[] = [];
const formData = undefined;
@@ -63,7 +72,7 @@ describe('<OwnerPicker />', () => {
afterEach(() => jest.resetAllMocks());
describe('without allowedKinds', () => {
describe('without catalogFilter and allowedKinds', () => {
beforeEach(() => {
uiSchema = { 'ui:options': {} };
props = {
@@ -108,7 +117,7 @@ describe('<OwnerPicker />', () => {
catalogApi.getEntities.mockResolvedValue({ items: entities });
});
it('searches for users and groups', async () => {
it('searches for users', async () => {
await renderInTestApp(
<Wrapper>
<OwnerPicker {...props} />
@@ -122,4 +131,93 @@ describe('<OwnerPicker />', () => {
});
});
});
describe('with catalogFilter', () => {
beforeEach(() => {
uiSchema = {
'ui:options': {
catalogFilter: [
{
kind: ['Group'],
'spec.type': 'team',
},
],
},
};
props = {
onChange,
schema,
required,
uiSchema,
rawErrors,
formData,
} as unknown as FieldProps<any>;
catalogApi.getEntities.mockResolvedValue({ items: entities });
});
it('searches for group entities of type team', async () => {
await renderInTestApp(
<Wrapper>
<OwnerPicker {...props} />
</Wrapper>,
);
expect(catalogApi.getEntities).toHaveBeenCalledWith({
filter: [
{
kind: ['Group'],
'spec.type': 'team',
},
],
});
});
});
describe('catalogFilter should take precedence over allowedKinds', () => {
beforeEach(() => {
uiSchema = {
'ui:options': {
allowedKinds: ['User'],
catalogFilter: [
{
kind: ['Group', 'User'],
},
{
'spec.type': ['team', 'business-unit'],
},
],
},
};
props = {
onChange,
schema,
required,
uiSchema,
rawErrors,
formData,
} as unknown as FieldProps<any>;
catalogApi.getEntities.mockResolvedValue({ items: entities });
});
it('searches for users and groups or teams and business units', async () => {
await renderInTestApp(
<Wrapper>
<OwnerPicker {...props} />
</Wrapper>,
);
expect(catalogApi.getEntities).toHaveBeenCalledWith({
filter: [
{
kind: ['Group', 'User'],
},
{
'spec.type': ['team', 'business-unit'],
},
],
});
});
});
});
@@ -33,14 +33,16 @@ export const OwnerPicker = (props: OwnerPickerProps) => {
} = props;
const defaultNamespace = uiSchema['ui:options']?.defaultNamespace;
const allowedKinds = uiSchema['ui:options']?.allowedKinds;
const catalogFilter = uiSchema['ui:options']?.catalogFilter || {
kind: allowedKinds || ['Group', 'User'],
};
const ownerUiSchema = {
...uiSchema,
'ui:options': {
allowedKinds: (uiSchema['ui:options']?.allowedKinds || [
'Group',
'User',
]) as string[],
catalogFilter,
defaultKind: 'Group',
allowArbitraryValues:
uiSchema['ui:options']?.allowArbitraryValues ?? true,
@@ -15,6 +15,7 @@
*/
import { z } from 'zod';
import { makeFieldSchemaFromZod } from '../utils';
import { entityQueryFilterExpressionSchema } from '../EntityPicker/schema';
/**
* @public
@@ -22,12 +23,15 @@ import { makeFieldSchemaFromZod } from '../utils';
export const OwnerPickerFieldSchema = makeFieldSchemaFromZod(
z.string(),
z.object({
/**
* @deprecated Use `catalogFilter` instead.
*/
allowedKinds: z
.array(z.string())
.default(['Group', 'User'])
.optional()
.describe(
'List of kinds of entities to derive options from. Defaults to Group and User',
'DEPRECATED: Use `catalogFilter` instead. List of kinds of entities to derive options from. Defaults to Group and User',
),
allowArbitraryValues: z
.boolean()
@@ -39,6 +43,11 @@ export const OwnerPickerFieldSchema = makeFieldSchemaFromZod(
.describe(
'The default namespace. Options with this namespace will not be prefixed.',
),
catalogFilter: z
.array(entityQueryFilterExpressionSchema)
.or(entityQueryFilterExpressionSchema)
.optional()
.describe('List of key-value filter expression for entities'),
}),
);