From c0d22517bfc55c33dec7f0a63abe2fe0352c4f9d Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 5 Aug 2021 11:45:52 +0200 Subject: [PATCH 01/10] scaffolder: add EntityNamePicker field Co-authored-by: Mike Lewis Signed-off-by: Himanshu Mishra --- .../EntityNamePicker/EntityNamePicker.tsx | 43 +++++++++++++++++++ .../fields/EntityNamePicker/index.ts | 17 ++++++++ .../fields/EntityNamePicker/validation.ts | 29 +++++++++++++ .../scaffolder/src/components/fields/index.ts | 1 + plugins/scaffolder/src/extensions/default.ts | 9 ++++ plugins/scaffolder/src/index.ts | 1 + plugins/scaffolder/src/plugin.ts | 12 ++++++ 7 files changed, 112 insertions(+) create mode 100644 plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx create mode 100644 plugins/scaffolder/src/components/fields/EntityNamePicker/index.ts create mode 100644 plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx new file mode 100644 index 0000000000..6562ddb270 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { FieldProps } from '@rjsf/core'; +import InputLabel from '@material-ui/core/InputLabel'; +import Input from '@material-ui/core/Input'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; + +export const EntityNamePicker = ({ + onChange, + required, + schema: { title = 'Name', description = 'Unique name of the component' }, + rawErrors, + formData, +}: FieldProps) => ( + 0 && !formData} + > + {title} + onChange(value)} + value={formData ?? ''} + /> + {description} + +); diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/index.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/index.ts new file mode 100644 index 0000000000..0ba88d1277 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { EntityNamePicker } from './EntityNamePicker'; +export { entityNamePickerValidation } from './validation'; diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts new file mode 100644 index 0000000000..7a40a460ba --- /dev/null +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FieldValidation } from '@rjsf/core'; +import { KubernetesValidatorFunctions } from '@backstage/catalog-model'; + +export const entityNamePickerValidation = ( + value: string, + validation: FieldValidation, +) => { + if (!KubernetesValidatorFunctions.isValidObjectName(value)) { + validation.addError( + 'must start and end with an alphanumeric character, and contain only alphanumeric characters, hyphens, underscores, and periods. Maximum length is 63 characters.', + ); + } +}; diff --git a/plugins/scaffolder/src/components/fields/index.ts b/plugins/scaffolder/src/components/fields/index.ts index 3d7875a742..805cc6bb2b 100644 --- a/plugins/scaffolder/src/components/fields/index.ts +++ b/plugins/scaffolder/src/components/fields/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +export * from './EntityNamePicker'; export * from './EntityPicker'; export * from './OwnerPicker'; export * from './RepoUrlPicker'; diff --git a/plugins/scaffolder/src/extensions/default.ts b/plugins/scaffolder/src/extensions/default.ts index b7af2f4a6b..be934546b3 100644 --- a/plugins/scaffolder/src/extensions/default.ts +++ b/plugins/scaffolder/src/extensions/default.ts @@ -14,6 +14,10 @@ * limitations under the License. */ import { EntityPicker } from '../components/fields/EntityPicker'; +import { + EntityNamePicker, + entityNamePickerValidation, +} from '../components/fields/EntityNamePicker'; import { OwnerPicker } from '../components/fields/OwnerPicker'; import { repoPickerValidation, @@ -26,6 +30,11 @@ export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS: FieldExtensionOptions[] = [ component: EntityPicker, name: 'EntityPicker', }, + { + component: EntityNamePicker, + name: 'EntityNamePicker', + validation: entityNamePickerValidation, + }, { component: RepoUrlPicker, name: 'RepoUrlPicker', diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 74091a1a85..c78674cded 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -23,6 +23,7 @@ export { export type { CustomFieldValidator, FieldExtensionOptions } from './extensions'; export { EntityPickerFieldExtension, + EntityNamePickerFieldExtension, OwnerPickerFieldExtension, RepoUrlPickerFieldExtension, ScaffolderPage, diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index e12305639e..8b543b52eb 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -17,6 +17,10 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { scaffolderApiRef, ScaffolderClient } from './api'; import { EntityPicker } from './components/fields/EntityPicker'; +import { + entityNamePickerValidation, + EntityNamePicker, +} from './components/fields/EntityNamePicker'; import { OwnerPicker } from './components/fields/OwnerPicker'; import { repoPickerValidation, @@ -61,6 +65,14 @@ export const EntityPickerFieldExtension = scaffolderPlugin.provide( }), ); +export const EntityNamePickerFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + component: EntityNamePicker, + name: 'EntityNamePicker', + validation: entityNamePickerValidation, + }), +); + export const RepoUrlPickerFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ component: RepoUrlPicker, From 9425607173c5bf17e1d712badb9a1807bcfafa5d Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 5 Aug 2021 11:51:02 +0200 Subject: [PATCH 02/10] scaffolder: export Picker components from the plugin This is needed for creating a custom field extension which re-uses an existing picker component. Co-authored-by: Mike Lewis Signed-off-by: Himanshu Mishra --- plugins/scaffolder/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index c78674cded..4821f681bc 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -30,3 +30,9 @@ export { scaffolderPlugin as plugin, scaffolderPlugin, } from './plugin'; +export { + EntityNamePicker, + EntityPicker, + OwnerPicker, + RepoUrlPicker, +} from './components/fields'; From 27a2b642d0fbfce49b3e7f61b98ad78b978d132b Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 5 Aug 2021 12:18:55 +0200 Subject: [PATCH 03/10] add a todo to create a basic textvaluepicker so that it can be reused Co-authored-by: Mike Lewis Signed-off-by: Himanshu Mishra --- .../src/components/fields/EntityNamePicker/EntityNamePicker.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 6562ddb270..15ded2f9dc 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -20,6 +20,7 @@ import Input from '@material-ui/core/Input'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; +// TODO(Mike/Himanshu): Create a simple TextValuePicker so that it could be reused for creating custom field extensions with its own validators. export const EntityNamePicker = ({ onChange, required, From 5b182e38235a2707d7e5c52ed5b84da6fb27fe13 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Thu, 5 Aug 2021 11:57:26 +0100 Subject: [PATCH 04/10] scaffolder: extract reusable TextValuePicker from EntityNamePicker Signed-off-by: Mike Lewis --- .../EntityNamePicker/EntityNamePicker.tsx | 25 ++----------- .../TextValuePicker/TextValuePicker.tsx | 36 +++++++++++++++++++ .../fields/TextValuePicker/index.ts | 16 +++++++++ .../scaffolder/src/components/fields/index.ts | 1 + plugins/scaffolder/src/index.ts | 1 + 5 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx create mode 100644 plugins/scaffolder/src/components/fields/TextValuePicker/index.ts diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 15ded2f9dc..8e7c0b0bea 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -15,30 +15,11 @@ */ import React from 'react'; import { FieldProps } from '@rjsf/core'; -import InputLabel from '@material-ui/core/InputLabel'; -import Input from '@material-ui/core/Input'; -import FormControl from '@material-ui/core/FormControl'; -import FormHelperText from '@material-ui/core/FormHelperText'; +import { TextValuePicker } from '../TextValuePicker'; -// TODO(Mike/Himanshu): Create a simple TextValuePicker so that it could be reused for creating custom field extensions with its own validators. export const EntityNamePicker = ({ - onChange, - required, schema: { title = 'Name', description = 'Unique name of the component' }, - rawErrors, - formData, + ...props }: FieldProps) => ( - 0 && !formData} - > - {title} - onChange(value)} - value={formData ?? ''} - /> - {description} - + ); diff --git a/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx b/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx new file mode 100644 index 0000000000..20e19d8526 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { FieldProps } from '@rjsf/core'; +import { TextField } from '@material-ui/core'; + +export const TextValuePicker = ({ + onChange, + required, + schema: { title, description }, + rawErrors, + formData, +}: FieldProps) => ( + onChange(value)} + margin="normal" + error={rawErrors?.length > 0 && !formData} + /> +); diff --git a/plugins/scaffolder/src/components/fields/TextValuePicker/index.ts b/plugins/scaffolder/src/components/fields/TextValuePicker/index.ts new file mode 100644 index 0000000000..8ab810b6ed --- /dev/null +++ b/plugins/scaffolder/src/components/fields/TextValuePicker/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { TextValuePicker } from './TextValuePicker'; diff --git a/plugins/scaffolder/src/components/fields/index.ts b/plugins/scaffolder/src/components/fields/index.ts index 805cc6bb2b..5adc3d3141 100644 --- a/plugins/scaffolder/src/components/fields/index.ts +++ b/plugins/scaffolder/src/components/fields/index.ts @@ -17,3 +17,4 @@ export * from './EntityNamePicker'; export * from './EntityPicker'; export * from './OwnerPicker'; export * from './RepoUrlPicker'; +export * from './TextValuePicker'; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 4821f681bc..989b3b53ce 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -35,4 +35,5 @@ export { EntityPicker, OwnerPicker, RepoUrlPicker, + TextValuePicker, } from './components/fields'; From b6d5c2a35b6bf455d49b3311753c41d655e32303 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Thu, 5 Aug 2021 11:59:07 +0100 Subject: [PATCH 05/10] Include EntityNamePickerFieldExtension in example app Signed-off-by: Mike Lewis --- packages/app/src/App.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index de62971f96..a93cc9279a 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -41,6 +41,15 @@ import { GcpProjectsPage } from '@backstage/plugin-gcp-projects'; import { GraphiQLPage } from '@backstage/plugin-graphiql'; import { LighthousePage } from '@backstage/plugin-lighthouse'; import { NewRelicPage } from '@backstage/plugin-newrelic'; +import { + ScaffolderPage, + scaffolderPlugin, + ScaffolderFieldExtensions, + RepoUrlPickerFieldExtension, + OwnerPickerFieldExtension, + EntityPickerFieldExtension, + EntityNamePickerFieldExtension, +} from '@backstage/plugin-scaffolder'; import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; @@ -108,7 +117,15 @@ const routes = ( } /> } /> - } /> + }> + + + + + + + + } /> Date: Thu, 5 Aug 2021 12:09:22 +0100 Subject: [PATCH 06/10] scaffolder: add test suite for EntityNamePicker validation Signed-off-by: Mike Lewis --- .../EntityNamePicker/validation.test.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts new file mode 100644 index 0000000000..2006a87520 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/validation.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FieldValidation } from '@rjsf/core'; +import { KubernetesValidatorFunctions } from '@backstage/catalog-model'; +import { entityNamePickerValidation } from './validation'; + +jest.mock('@backstage/catalog-model', () => ({ + KubernetesValidatorFunctions: { + isValidObjectName: jest.fn(), + }, +})); + +const mockIsValidObjectName = KubernetesValidatorFunctions.isValidObjectName as jest.MockedFunction< + typeof KubernetesValidatorFunctions.isValidObjectName +>; + +describe('EntityNamePicker Validation', () => { + let mockFieldValidation: FieldValidation; + + beforeEach(() => { + mockFieldValidation = ({ + addError: jest.fn(), + } as unknown) as FieldValidation; + }); + + it('calls isValidObjectName to validate value', () => { + entityNamePickerValidation('test value', mockFieldValidation); + + expect(mockIsValidObjectName).toHaveBeenCalled(); + }); + + it('does not add an error when isValidObjectName returns true', () => { + mockIsValidObjectName.mockReturnValue(true); + + entityNamePickerValidation('test value', mockFieldValidation); + + expect(mockFieldValidation.addError).not.toHaveBeenCalled(); + }); + + it('adds an error when isValidObjectName returns false', () => { + mockIsValidObjectName.mockReturnValue(false); + + entityNamePickerValidation('test value', mockFieldValidation); + + expect(mockFieldValidation.addError).toHaveBeenCalledWith( + expect.stringMatching( + /contain only alphanumeric characters, hyphens, underscores, and periods/, + ), + ); + }); +}); From f9694b02e6af4e9cbcb5c9f9b5ac28077baa68f9 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Thu, 5 Aug 2021 12:35:08 +0100 Subject: [PATCH 07/10] scaffolder: add support for ui:autofocus to TextValuePicker Signed-off-by: Mike Lewis --- .../src/components/fields/TextValuePicker/TextValuePicker.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx b/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx index 20e19d8526..f391843f73 100644 --- a/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx +++ b/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx @@ -23,6 +23,7 @@ export const TextValuePicker = ({ schema: { title, description }, rawErrors, formData, + uiSchema: { 'ui:autofocus': autoFocus }, }: FieldProps) => ( onChange(value)} margin="normal" error={rawErrors?.length > 0 && !formData} + inputProps={{ autoFocus }} /> ); From fbbc9aafc2c36448ad75634b02c17b3244138fd3 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 5 Aug 2021 12:17:39 +0200 Subject: [PATCH 08/10] example-app: add an example of adding a custom Scaffolder field extension This is useful to visualize what it takes to add a new custom Scaffolder field extension. Co-authored-by: Mike Lewis Signed-off-by: Himanshu Mishra --- packages/app/package.json | 1 + packages/app/src/App.tsx | 3 +- .../scaffolder/customScaffolderExtensions.tsx | 34 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 packages/app/src/components/scaffolder/customScaffolderExtensions.tsx diff --git a/packages/app/package.json b/packages/app/package.json index 5fa6fe2198..10066f9ba0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -61,6 +61,7 @@ }, "devDependencies": { "@backstage/test-utils": "^0.1.16", + "@rjsf/core": "^3.0.0", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index a93cc9279a..12d7c0c231 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -50,7 +50,6 @@ import { EntityPickerFieldExtension, EntityNamePickerFieldExtension, } from '@backstage/plugin-scaffolder'; -import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; import { TechdocsPage } from '@backstage/plugin-techdocs'; @@ -63,6 +62,7 @@ import { apis } from './apis'; import { Root } from './components/Root'; import { entityPage } from './components/catalog/EntityPage'; import { searchPage } from './components/search/SearchPage'; +import { LowerCaseValuePickerFieldExtension } from './components/scaffolder/customScaffolderExtensions'; import { providers } from './identityProviders'; import * as plugins from './plugins'; @@ -133,7 +133,6 @@ const routes = ( /> } /> } /> - } /> } /> } /> diff --git a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx new file mode 100644 index 0000000000..5ef4f617a7 --- /dev/null +++ b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { FieldValidation } from '@rjsf/core'; +import { + createScaffolderFieldExtension, + TextValuePicker, + scaffolderPlugin, +} from '@backstage/plugin-scaffolder'; + +export const LowerCaseValuePickerFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: 'LowerCaseValuePicker', + component: TextValuePicker, + validation: (value: string, validation: FieldValidation) => { + if (value.toLowerCase() !== value) { + validation.addError('Only lowercase values are allowed.'); + } + }, + }), +); From fa84fe44ecfef275f376b3c1664e3ec4b0ac1721 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Thu, 5 Aug 2021 12:51:06 +0100 Subject: [PATCH 09/10] Add changeset Signed-off-by: Mike Lewis --- .changeset/happy-lies-wink.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .changeset/happy-lies-wink.md diff --git a/.changeset/happy-lies-wink.md b/.changeset/happy-lies-wink.md new file mode 100644 index 0000000000..709cd3862e --- /dev/null +++ b/.changeset/happy-lies-wink.md @@ -0,0 +1,22 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +- Adds a new field `EntityNamePicker` that can be used in scaffolder templates to accept and validate an entity name. This field is registered by default, and can be used in templates by setting the `ui:field` property to `EntityNamePicker`. If you've customized your scaffolder field extensions, you can include this one by adding it when registering the scaffolder route: + +```diff +import { + ScaffolderFieldExtensions, ++ EntityNamePickerFieldExtension, +} from '@backstage/plugin-scaffolder'; + + }> + + {/* ...custom field extensions... */} + ++ + + ; +``` + +- Adds a new generic field `TextValuePicker` to be used when writing custom field extensions that use a standard UI with custom validation. An example of doing this can be found in `packages/app/src/components/scaffolder/customScaffolderExtensions.tsx`. From c9952bce306c9e653e6dcee2f57d4c5acd8444c0 Mon Sep 17 00:00:00 2001 From: Mike Lewis Date: Thu, 5 Aug 2021 13:33:04 +0100 Subject: [PATCH 10/10] scaffolder: api-report Signed-off-by: Mike Lewis --- plugins/scaffolder/api-report.md | 56 ++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 1be7e8fec8..250eed0d2c 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -43,6 +43,31 @@ export type CustomFieldValidator = }, ) => void); +// Warning: (ae-missing-release-tag) "EntityNamePicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityNamePicker: ({ + schema: { title, description }, + ...props +}: FieldProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "EntityNamePickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityNamePickerFieldExtension: () => null; + +// Warning: (ae-missing-release-tag) "EntityPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityPicker: ({ + onChange, + schema: { title, description }, + required, + uiSchema, + rawErrors, + formData, +}: FieldProps) => JSX.Element; + // Warning: (ae-missing-release-tag) "EntityPickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -57,11 +82,30 @@ export type FieldExtensionOptions = { validation?: CustomFieldValidator; }; +// Warning: (ae-missing-release-tag) "OwnerPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const OwnerPicker: ({ + schema: { title, description }, + uiSchema, + ...props +}: FieldProps) => JSX.Element; + // Warning: (ae-missing-release-tag) "OwnerPickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const OwnerPickerFieldExtension: () => null; +// Warning: (ae-missing-release-tag) "RepoUrlPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const RepoUrlPicker: ({ + onChange, + uiSchema, + rawErrors, + formData, +}: FieldProps) => JSX.Element; + // Warning: (ae-missing-release-tag) "RepoUrlPickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -179,5 +223,17 @@ const scaffolderPlugin: BackstagePlugin< export { scaffolderPlugin as plugin }; export { scaffolderPlugin }; +// Warning: (ae-missing-release-tag) "TextValuePicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const TextValuePicker: ({ + onChange, + required, + schema: { title, description }, + rawErrors, + formData, + uiSchema: { 'ui:autofocus': autoFocus }, +}: FieldProps) => JSX.Element; + // (No @packageDocumentation comment for this package) ```