@backstage/plugin-scaffolder 1.36.3-next.0 → 1.37.0-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/dist/alpha/api/FormDecoratorsApi.esm.js.map +1 -1
  3. package/dist/alpha/api/ref.esm.js.map +1 -1
  4. package/dist/alpha/components/TemplateEditorPage/CustomFieldExplorer.esm.js +4 -4
  5. package/dist/alpha/components/TemplateEditorPage/CustomFieldExplorer.esm.js.map +1 -1
  6. package/dist/alpha/components/TemplateEditorPage/CustomFieldPlayground.esm.js +5 -5
  7. package/dist/alpha/components/TemplateEditorPage/CustomFieldPlayground.esm.js.map +1 -1
  8. package/dist/alpha/components/TemplateListPage/TemplateListPage.esm.js +14 -14
  9. package/dist/alpha/components/TemplateListPage/TemplateListPage.esm.js.map +1 -1
  10. package/dist/alpha/components/TemplatesSubPage.esm.js +20 -10
  11. package/dist/alpha/components/TemplatesSubPage.esm.js.map +1 -1
  12. package/dist/alpha/extensions.esm.js +28 -2
  13. package/dist/alpha/extensions.esm.js.map +1 -1
  14. package/dist/alpha/hooks/useFormDecorators.esm.js +34 -3
  15. package/dist/alpha/hooks/useFormDecorators.esm.js.map +1 -1
  16. package/dist/alpha/lib/createGroupsWithOther.esm.js +13 -0
  17. package/dist/alpha/lib/createGroupsWithOther.esm.js.map +1 -0
  18. package/dist/alpha.d.ts +14 -4
  19. package/dist/components/TemplateTypePicker/TemplateTypePicker.esm.js +4 -4
  20. package/dist/components/TemplateTypePicker/TemplateTypePicker.esm.js.map +1 -1
  21. package/dist/components/TemplatingExtensionsPage/TemplatingExtensionsPage.esm.js +4 -4
  22. package/dist/components/TemplatingExtensionsPage/TemplatingExtensionsPage.esm.js.map +1 -1
  23. package/dist/components/fields/Autocomplete/Autocomplete.esm.js +23 -0
  24. package/dist/components/fields/Autocomplete/Autocomplete.esm.js.map +1 -0
  25. package/dist/components/fields/EntityNamePicker/EntityNamePicker.esm.js +23 -2
  26. package/dist/components/fields/EntityNamePicker/EntityNamePicker.esm.js.map +1 -1
  27. package/dist/components/fields/EntityPicker/EntityPicker.esm.js +125 -9
  28. package/dist/components/fields/EntityPicker/EntityPicker.esm.js.map +1 -1
  29. package/dist/components/fields/EntityTagsPicker/EntityTagsPicker.esm.js +106 -7
  30. package/dist/components/fields/EntityTagsPicker/EntityTagsPicker.esm.js.map +1 -1
  31. package/dist/components/fields/MultiEntityPicker/MultiEntityPicker.esm.js +128 -10
  32. package/dist/components/fields/MultiEntityPicker/MultiEntityPicker.esm.js.map +1 -1
  33. package/dist/components/fields/MyGroupsPicker/MyGroupsPicker.esm.js +66 -6
  34. package/dist/components/fields/MyGroupsPicker/MyGroupsPicker.esm.js.map +1 -1
  35. package/dist/components/fields/OwnedEntityPicker/OwnedEntityPicker.esm.js +29 -6
  36. package/dist/components/fields/OwnedEntityPicker/OwnedEntityPicker.esm.js.map +1 -1
  37. package/dist/components/fields/OwnedEntityPicker/schema.esm.js +1 -0
  38. package/dist/components/fields/OwnedEntityPicker/schema.esm.js.map +1 -1
  39. package/dist/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.esm.js +29 -5
  40. package/dist/components/fields/RepoBranchPicker/BitbucketRepoBranchPicker.esm.js.map +1 -1
  41. package/dist/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.esm.js +20 -3
  42. package/dist/components/fields/RepoBranchPicker/DefaultRepoBranchPicker.esm.js.map +1 -1
  43. package/dist/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.esm.js +29 -5
  44. package/dist/components/fields/RepoBranchPicker/GitHubRepoBranchPicker.esm.js.map +1 -1
  45. package/dist/components/fields/RepoBranchPicker/RepoBranchPicker.esm.js +18 -6
  46. package/dist/components/fields/RepoBranchPicker/RepoBranchPicker.esm.js.map +1 -1
  47. package/dist/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.esm.js +20 -3
  48. package/dist/components/fields/RepoOwnerPicker/DefaultRepoOwnerPicker.esm.js.map +1 -1
  49. package/dist/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.esm.js +29 -5
  50. package/dist/components/fields/RepoOwnerPicker/GitHubRepoOwnerPicker.esm.js.map +1 -1
  51. package/dist/components/fields/RepoOwnerPicker/RepoOwnerPicker.esm.js +28 -31
  52. package/dist/components/fields/RepoOwnerPicker/RepoOwnerPicker.esm.js.map +1 -1
  53. package/dist/components/fields/RepoUrlPicker/AzureRepoPicker.esm.js +86 -4
  54. package/dist/components/fields/RepoUrlPicker/AzureRepoPicker.esm.js.map +1 -1
  55. package/dist/components/fields/RepoUrlPicker/BitbucketRepoPicker.esm.js +107 -11
  56. package/dist/components/fields/RepoUrlPicker/BitbucketRepoPicker.esm.js.map +1 -1
  57. package/dist/components/fields/RepoUrlPicker/GerritRepoPicker.esm.js +33 -3
  58. package/dist/components/fields/RepoUrlPicker/GerritRepoPicker.esm.js.map +1 -1
  59. package/dist/components/fields/RepoUrlPicker/GiteaRepoPicker.esm.js +42 -5
  60. package/dist/components/fields/RepoUrlPicker/GiteaRepoPicker.esm.js.map +1 -1
  61. package/dist/components/fields/RepoUrlPicker/GithubRepoPicker.esm.js +52 -7
  62. package/dist/components/fields/RepoUrlPicker/GithubRepoPicker.esm.js.map +1 -1
  63. package/dist/components/fields/RepoUrlPicker/GitlabRepoPicker.esm.js +60 -16
  64. package/dist/components/fields/RepoUrlPicker/GitlabRepoPicker.esm.js.map +1 -1
  65. package/dist/components/fields/RepoUrlPicker/RepoUrlPicker.esm.js +23 -9
  66. package/dist/components/fields/RepoUrlPicker/RepoUrlPicker.esm.js.map +1 -1
  67. package/dist/components/fields/RepoUrlPicker/RepoUrlPickerHost.esm.js +27 -3
  68. package/dist/components/fields/RepoUrlPicker/RepoUrlPickerHost.esm.js.map +1 -1
  69. package/dist/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.esm.js +60 -6
  70. package/dist/components/fields/RepoUrlPicker/RepoUrlPickerRepoName.esm.js.map +1 -1
  71. package/dist/components/fields/SecretInput/SecretInput.esm.js +57 -2
  72. package/dist/components/fields/SecretInput/SecretInput.esm.js.map +1 -1
  73. package/dist/components/fields/buiChipStyles.esm.js +25 -0
  74. package/dist/components/fields/buiChipStyles.esm.js.map +1 -0
  75. package/dist/components/fields/scaffolderFieldOverrides.module.css.esm.js +8 -0
  76. package/dist/components/fields/scaffolderFieldOverrides.module.css.esm.js.map +1 -0
  77. package/dist/index.esm.js +2 -0
  78. package/dist/index.esm.js.map +1 -1
  79. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
  80. package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
  81. package/dist/plugins/scaffolder/package.json.esm.js +4 -2
  82. package/dist/plugins/scaffolder/package.json.esm.js.map +1 -1
  83. package/package.json +18 -16
@@ -3,17 +3,19 @@ import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
3
3
  import { stringifyEntityRef, parseEntityRef } from '@backstage/catalog-model';
4
4
  import { useApi } from '@backstage/core-plugin-api';
5
5
  import { catalogApiRef, entityPresentationApiRef, EntityDisplayName } from '@backstage/plugin-catalog-react';
6
- import TextField from '@material-ui/core/TextField';
7
- import Autocomplete, { createFilterOptions } from '@material-ui/lab/Autocomplete';
8
- import { useCallback, useEffect } from 'react';
6
+ import MuiTextField from '@material-ui/core/TextField';
7
+ import MuiAutocomplete, { createFilterOptions } from '@material-ui/lab/Autocomplete';
8
+ import { useCallback, useMemo, useState, useEffect, useRef } from 'react';
9
9
  import useAsync from 'react-use/esm/useAsync';
10
10
  import { VirtualizedListbox } from '../VirtualizedListbox.esm.js';
11
11
  import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
12
12
  import { scaffolderTranslationRef } from '../../../translation.esm.js';
13
- import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';
13
+ import { useScaffolderTheme, ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';
14
+ import { Autocomplete } from '../Autocomplete/Autocomplete.esm.js';
14
15
  export { EntityPickerSchema } from './schema.esm.js';
15
16
 
16
17
  const EntityPicker = (props) => {
18
+ const theme = useScaffolderTheme();
17
19
  const { t } = useTranslationRef(scaffolderTranslationRef);
18
20
  const {
19
21
  onChange,
@@ -98,11 +100,125 @@ const EntityPicker = (props) => {
98
100
  [onChange, formData, defaultKind, defaultNamespace, allowArbitraryValues]
99
101
  );
100
102
  const selectedEntity = entities?.catalogEntities.find((e) => stringifyEntityRef(e) === formData) ?? (allowArbitraryValues && formData ? getLabel(formData) : "");
103
+ const buiOptions = useMemo(
104
+ () => (entities?.catalogEntities || []).map((entity) => {
105
+ const entityRef = stringifyEntityRef(entity);
106
+ const presentation = entities?.entityRefToPresentation.get(entityRef);
107
+ return {
108
+ value: entityRef,
109
+ label: presentation?.primaryTitle || entityRef
110
+ };
111
+ }),
112
+ [entities]
113
+ );
114
+ const [inputValue, setInputValue] = useState(formData || "");
115
+ useEffect(() => {
116
+ if (formData) {
117
+ const opt = buiOptions.find((o) => o.value === formData);
118
+ setInputValue(opt?.label || formData);
119
+ } else {
120
+ setInputValue("");
121
+ }
122
+ }, [formData, buiOptions]);
123
+ const selectedKey = formData && buiOptions.some((o) => o.value === formData) ? formData : null;
124
+ const lastCommittedRef = useRef(formData);
125
+ useEffect(() => {
126
+ lastCommittedRef.current = formData;
127
+ }, [formData]);
128
+ const handleSelectionChange = useCallback(
129
+ (key) => {
130
+ if (key !== null) {
131
+ const value = String(key);
132
+ lastCommittedRef.current = value;
133
+ onChange(value);
134
+ } else if (allowArbitraryValues && inputValue) {
135
+ let entityRef = inputValue;
136
+ try {
137
+ entityRef = stringifyEntityRef(
138
+ parseEntityRef(inputValue, { defaultKind, defaultNamespace })
139
+ );
140
+ } catch {
141
+ }
142
+ lastCommittedRef.current = entityRef;
143
+ onChange(entityRef);
144
+ } else {
145
+ lastCommittedRef.current = void 0;
146
+ onChange(void 0);
147
+ }
148
+ },
149
+ [onChange, allowArbitraryValues, inputValue, defaultKind, defaultNamespace]
150
+ );
151
+ const handleBlur = useCallback(() => {
152
+ if (allowArbitraryValues && inputValue) {
153
+ let entityRef = inputValue;
154
+ try {
155
+ entityRef = stringifyEntityRef(
156
+ parseEntityRef(inputValue, { defaultKind, defaultNamespace })
157
+ );
158
+ } catch {
159
+ }
160
+ if (lastCommittedRef.current !== entityRef) {
161
+ lastCommittedRef.current = entityRef;
162
+ onChange(entityRef);
163
+ }
164
+ }
165
+ }, [
166
+ allowArbitraryValues,
167
+ inputValue,
168
+ defaultKind,
169
+ defaultNamespace,
170
+ onChange
171
+ ]);
101
172
  useEffect(() => {
102
- if (required && !allowArbitraryValues && entities?.catalogEntities.length === 1 && selectedEntity === "") {
103
- onChange(stringifyEntityRef(entities.catalogEntities[0]));
173
+ if (theme === "bui") {
174
+ if (required && !allowArbitraryValues && entities?.catalogEntities.length === 1 && !formData) {
175
+ onChange(stringifyEntityRef(entities.catalogEntities[0]));
176
+ }
177
+ } else {
178
+ if (required && !allowArbitraryValues && entities?.catalogEntities.length === 1 && selectedEntity === "") {
179
+ onChange(stringifyEntityRef(entities.catalogEntities[0]));
180
+ }
104
181
  }
105
- }, [entities, onChange, selectedEntity, required, allowArbitraryValues]);
182
+ }, [
183
+ entities,
184
+ onChange,
185
+ selectedEntity,
186
+ formData,
187
+ required,
188
+ allowArbitraryValues,
189
+ theme
190
+ ]);
191
+ if (theme === "bui") {
192
+ const isAutoSelected = required && !allowArbitraryValues && entities?.catalogEntities.length === 1;
193
+ return /* @__PURE__ */ jsx(
194
+ ScaffolderField,
195
+ {
196
+ rawErrors,
197
+ rawDescription: uiSchema["ui:description"] ?? description,
198
+ required,
199
+ disabled: isDisabled,
200
+ errors,
201
+ children: /* @__PURE__ */ jsx(
202
+ Autocomplete,
203
+ {
204
+ id: idSchema?.$id,
205
+ label: title,
206
+ isRequired: required,
207
+ isDisabled: isDisabled || isAutoSelected,
208
+ selectedKey,
209
+ inputValue,
210
+ onInputChange: setInputValue,
211
+ onSelectionChange: handleSelectionChange,
212
+ onBlur: handleBlur,
213
+ isLoading: loading,
214
+ options: buiOptions,
215
+ allowsCustomValue: allowArbitraryValues,
216
+ isInvalid: rawErrors && rawErrors.length > 0
217
+ }
218
+ )
219
+ }
220
+ );
221
+ }
106
222
  return /* @__PURE__ */ jsx(
107
223
  ScaffolderField,
108
224
  {
@@ -112,7 +228,7 @@ const EntityPicker = (props) => {
112
228
  disabled: isDisabled,
113
229
  errors,
114
230
  children: /* @__PURE__ */ jsx(
115
- Autocomplete,
231
+ MuiAutocomplete,
116
232
  {
117
233
  disabled: isDisabled || required && !allowArbitraryValues && entities?.catalogEntities.length === 1,
118
234
  id: idSchema?.$id,
@@ -127,7 +243,7 @@ const EntityPicker = (props) => {
127
243
  autoSelect,
128
244
  freeSolo: allowArbitraryValues,
129
245
  renderInput: (params) => /* @__PURE__ */ jsx(
130
- TextField,
246
+ MuiTextField,
131
247
  {
132
248
  ...params,
133
249
  label: title,
@@ -1 +1 @@
1
- {"version":3,"file":"EntityPicker.esm.js","sources":["../../../../src/components/fields/EntityPicker/EntityPicker.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n type EntityFilterQuery,\n CATALOG_FILTER_EXISTS,\n} from '@backstage/catalog-client';\nimport {\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n EntityDisplayName,\n EntityRefPresentationSnapshot,\n catalogApiRef,\n entityPresentationApiRef,\n} from '@backstage/plugin-catalog-react';\nimport TextField from '@material-ui/core/TextField';\nimport Autocomplete, {\n AutocompleteChangeReason,\n createFilterOptions,\n} from '@material-ui/lab/Autocomplete';\nimport { useCallback, useEffect } from 'react';\nimport useAsync from 'react-use/esm/useAsync';\nimport {\n EntityPickerFilterQueryValue,\n EntityPickerProps,\n EntityPickerUiOptions,\n EntityPickerFilterQuery,\n} from './schema';\nimport { VirtualizedListbox } from '../VirtualizedListbox';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { scaffolderTranslationRef } from '../../../translation';\nimport { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';\n\nexport { EntityPickerSchema } from './schema';\n\n/**\n * The underlying component that is rendered in the form for the `EntityPicker`\n * field extension.\n *\n * @public\n */\nexport const EntityPicker = (props: EntityPickerProps) => {\n const { t } = useTranslationRef(scaffolderTranslationRef);\n const {\n onChange,\n schema: {\n title = t('fields.entityPicker.title'),\n description = t('fields.entityPicker.description'),\n },\n required,\n uiSchema,\n rawErrors,\n formData,\n idSchema,\n errors,\n } = props;\n const catalogFilter = buildCatalogFilter(uiSchema);\n const defaultKind = uiSchema['ui:options']?.defaultKind;\n const defaultNamespace =\n uiSchema['ui:options']?.defaultNamespace || undefined;\n const autoSelect = uiSchema?.['ui:options']?.autoSelect ?? true;\n const isDisabled = uiSchema?.['ui:disabled'] ?? false;\n\n const catalogApi = useApi(catalogApiRef);\n const entityPresentationApi = useApi(entityPresentationApiRef);\n\n const { value: entities, loading } = useAsync(async () => {\n const fields = [\n 'kind',\n 'metadata.name',\n 'metadata.namespace',\n 'metadata.title',\n 'metadata.description',\n 'spec.profile.displayName',\n 'spec.type',\n ];\n const { items } = await catalogApi.getEntities(\n catalogFilter\n ? { filter: catalogFilter, fields }\n : { filter: undefined, fields },\n );\n\n const entityRefToPresentation = new Map<\n string,\n EntityRefPresentationSnapshot\n >(\n await Promise.all(\n items.map(async item => {\n const presentation = await entityPresentationApi.forEntity(item)\n .promise;\n return [stringifyEntityRef(item), presentation] as [\n string,\n EntityRefPresentationSnapshot,\n ];\n }),\n ),\n );\n\n return { catalogEntities: items, entityRefToPresentation };\n });\n\n const allowArbitraryValues =\n uiSchema['ui:options']?.allowArbitraryValues ?? true;\n\n const getLabel = useCallback(\n (freeSoloValue: string) => {\n try {\n // Will throw if defaultKind or defaultNamespace are not set\n const parsedRef = parseEntityRef(freeSoloValue, {\n defaultKind,\n defaultNamespace,\n });\n\n return stringifyEntityRef(parsedRef);\n } catch (err) {\n return freeSoloValue;\n }\n },\n [defaultKind, defaultNamespace],\n );\n\n const onSelect = useCallback(\n (_: any, ref: string | Entity | null, reason: AutocompleteChangeReason) => {\n // ref can either be a string from free solo entry or\n if (typeof ref !== 'string') {\n // if ref does not exist: pass 'undefined' to trigger validation for required value\n onChange(ref ? stringifyEntityRef(ref as Entity) : undefined);\n } else {\n if (reason === 'blur' || reason === 'create-option') {\n // Add in default namespace, etc.\n let entityRef = ref;\n try {\n // Attempt to parse the entity ref into it's full form.\n entityRef = stringifyEntityRef(\n parseEntityRef(ref as string, {\n defaultKind,\n defaultNamespace,\n }),\n );\n } catch (err) {\n // If the passed in value isn't an entity ref, do nothing.\n }\n // We need to check against formData here as that's the previous value for this field.\n if (formData !== ref || allowArbitraryValues) {\n onChange(entityRef);\n }\n }\n }\n },\n [onChange, formData, defaultKind, defaultNamespace, allowArbitraryValues],\n );\n\n // Since free solo can be enabled, attempt to parse as a full entity ref first, then fall\n // back to the given value.\n const selectedEntity =\n entities?.catalogEntities.find(e => stringifyEntityRef(e) === formData) ??\n (allowArbitraryValues && formData ? getLabel(formData) : '');\n\n useEffect(() => {\n if (\n required &&\n !allowArbitraryValues &&\n entities?.catalogEntities.length === 1 &&\n selectedEntity === ''\n ) {\n onChange(stringifyEntityRef(entities.catalogEntities[0]));\n }\n }, [entities, onChange, selectedEntity, required, allowArbitraryValues]);\n\n return (\n <ScaffolderField\n rawErrors={rawErrors}\n rawDescription={uiSchema['ui:description'] ?? description}\n required={required}\n disabled={isDisabled}\n errors={errors}\n >\n <Autocomplete\n disabled={\n isDisabled ||\n (required &&\n !allowArbitraryValues &&\n entities?.catalogEntities.length === 1)\n }\n id={idSchema?.$id}\n value={selectedEntity}\n loading={loading}\n onChange={onSelect}\n options={entities?.catalogEntities || []}\n getOptionLabel={option =>\n // option can be a string due to freeSolo.\n typeof option === 'string'\n ? option\n : entities?.entityRefToPresentation.get(stringifyEntityRef(option))\n ?.entityRef!\n }\n autoSelect={autoSelect}\n freeSolo={allowArbitraryValues}\n renderInput={params => (\n <TextField\n {...params}\n label={title}\n margin=\"dense\"\n variant=\"outlined\"\n required={required}\n disabled={isDisabled}\n InputProps={params.InputProps}\n />\n )}\n renderOption={option => <EntityDisplayName entityRef={option} />}\n filterOptions={createFilterOptions<Entity>({\n stringify: option =>\n entities?.entityRefToPresentation.get(stringifyEntityRef(option))\n ?.primaryTitle!,\n })}\n ListboxComponent={VirtualizedListbox}\n />\n </ScaffolderField>\n );\n};\n\n/**\n * Converts a especial `{exists: true}` value to the `CATALOG_FILTER_EXISTS` symbol.\n *\n * @param value - The value to convert.\n * @returns The converted value.\n */\nfunction convertOpsValues(\n value: Exclude<EntityPickerFilterQueryValue, Array<any>>,\n): string | symbol {\n if (typeof value === 'object' && value.exists) {\n return CATALOG_FILTER_EXISTS;\n }\n return value?.toString();\n}\n\n/**\n * Converts schema filters to entity filter query, replacing `{exists:true}` values\n * with the constant `CATALOG_FILTER_EXISTS`.\n *\n * @param schemaFilters - An object containing schema filters with keys as filter names\n * and values as filter values.\n * @returns An object with the same keys as the input object, but with `{exists:true}` values\n * transformed to `CATALOG_FILTER_EXISTS` symbol.\n */\nfunction convertSchemaFiltersToQuery(\n schemaFilters: EntityPickerFilterQuery,\n): Exclude<EntityFilterQuery, Array<any>> {\n const query: EntityFilterQuery = {};\n\n for (const [key, value] of Object.entries(schemaFilters)) {\n if (Array.isArray(value)) {\n query[key] = value;\n } else {\n query[key] = convertOpsValues(value);\n }\n }\n\n return query;\n}\n\n/**\n * Builds an `EntityFilterQuery` based on the `uiSchema` passed in.\n * If `catalogFilter` is specified in the `uiSchema`, it is converted to a `EntityFilterQuery`.\n * If `allowedKinds` is specified in the `uiSchema` will support the legacy `allowedKinds` option.\n *\n * @param uiSchema The `uiSchema` of an `EntityPicker` component.\n * @returns An `EntityFilterQuery` based on the `uiSchema`, or `undefined` if `catalogFilter` is not specified in the `uiSchema`.\n */\nfunction buildCatalogFilter(\n uiSchema: EntityPickerProps['uiSchema'],\n): EntityFilterQuery | undefined {\n const allowedKinds = uiSchema['ui:options']?.allowedKinds;\n\n const catalogFilter: EntityPickerUiOptions['catalogFilter'] | undefined =\n uiSchema['ui:options']?.catalogFilter ||\n (allowedKinds && { kind: allowedKinds });\n\n if (!catalogFilter) {\n return undefined;\n }\n\n if (Array.isArray(catalogFilter)) {\n return catalogFilter.map(convertSchemaFiltersToQuery);\n }\n\n return convertSchemaFiltersToQuery(catalogFilter);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityPicker.esm.js","sources":["../../../../src/components/fields/EntityPicker/EntityPicker.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n type EntityFilterQuery,\n CATALOG_FILTER_EXISTS,\n} from '@backstage/catalog-client';\nimport {\n Entity,\n parseEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n EntityDisplayName,\n EntityRefPresentationSnapshot,\n catalogApiRef,\n entityPresentationApiRef,\n} from '@backstage/plugin-catalog-react';\nimport TextField from '@material-ui/core/TextField';\nimport Autocomplete, {\n AutocompleteChangeReason,\n createFilterOptions,\n} from '@material-ui/lab/Autocomplete';\nimport {\n type Key,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport useAsync from 'react-use/esm/useAsync';\nimport {\n EntityPickerFilterQueryValue,\n EntityPickerProps,\n EntityPickerUiOptions,\n EntityPickerFilterQuery,\n} from './schema';\nimport { VirtualizedListbox } from '../VirtualizedListbox';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { scaffolderTranslationRef } from '../../../translation';\nimport {\n ScaffolderField,\n useScaffolderTheme,\n} from '@backstage/plugin-scaffolder-react/alpha';\nimport { Autocomplete as BuiAutocomplete } from '../Autocomplete';\n\nexport { EntityPickerSchema } from './schema';\n\n/**\n * The underlying component that is rendered in the form for the `EntityPicker`\n * field extension.\n *\n * @public\n */\nexport const EntityPicker = (props: EntityPickerProps) => {\n const theme = useScaffolderTheme();\n const { t } = useTranslationRef(scaffolderTranslationRef);\n const {\n onChange,\n schema: {\n title = t('fields.entityPicker.title'),\n description = t('fields.entityPicker.description'),\n },\n required,\n uiSchema,\n rawErrors,\n formData,\n idSchema,\n errors,\n } = props;\n const catalogFilter = buildCatalogFilter(uiSchema);\n const defaultKind = uiSchema['ui:options']?.defaultKind;\n const defaultNamespace =\n uiSchema['ui:options']?.defaultNamespace || undefined;\n const autoSelect = uiSchema?.['ui:options']?.autoSelect ?? true;\n const isDisabled = uiSchema?.['ui:disabled'] ?? false;\n\n const catalogApi = useApi(catalogApiRef);\n const entityPresentationApi = useApi(entityPresentationApiRef);\n\n const { value: entities, loading } = useAsync(async () => {\n const fields = [\n 'kind',\n 'metadata.name',\n 'metadata.namespace',\n 'metadata.title',\n 'metadata.description',\n 'spec.profile.displayName',\n 'spec.type',\n ];\n const { items } = await catalogApi.getEntities(\n catalogFilter\n ? { filter: catalogFilter, fields }\n : { filter: undefined, fields },\n );\n\n const entityRefToPresentation = new Map<\n string,\n EntityRefPresentationSnapshot\n >(\n await Promise.all(\n items.map(async item => {\n const presentation = await entityPresentationApi.forEntity(item)\n .promise;\n return [stringifyEntityRef(item), presentation] as [\n string,\n EntityRefPresentationSnapshot,\n ];\n }),\n ),\n );\n\n return { catalogEntities: items, entityRefToPresentation };\n });\n\n const allowArbitraryValues =\n uiSchema['ui:options']?.allowArbitraryValues ?? true;\n\n const getLabel = useCallback(\n (freeSoloValue: string) => {\n try {\n // Will throw if defaultKind or defaultNamespace are not set\n const parsedRef = parseEntityRef(freeSoloValue, {\n defaultKind,\n defaultNamespace,\n });\n\n return stringifyEntityRef(parsedRef);\n } catch (err) {\n return freeSoloValue;\n }\n },\n [defaultKind, defaultNamespace],\n );\n\n const onSelect = useCallback(\n (_: any, ref: string | Entity | null, reason: AutocompleteChangeReason) => {\n // ref can either be a string from free solo entry or\n if (typeof ref !== 'string') {\n // if ref does not exist: pass 'undefined' to trigger validation for required value\n onChange(ref ? stringifyEntityRef(ref as Entity) : undefined);\n } else {\n if (reason === 'blur' || reason === 'create-option') {\n // Add in default namespace, etc.\n let entityRef = ref;\n try {\n // Attempt to parse the entity ref into it's full form.\n entityRef = stringifyEntityRef(\n parseEntityRef(ref as string, {\n defaultKind,\n defaultNamespace,\n }),\n );\n } catch (err) {\n // If the passed in value isn't an entity ref, do nothing.\n }\n // We need to check against formData here as that's the previous value for this field.\n if (formData !== ref || allowArbitraryValues) {\n onChange(entityRef);\n }\n }\n }\n },\n [onChange, formData, defaultKind, defaultNamespace, allowArbitraryValues],\n );\n\n // Since free solo can be enabled, attempt to parse as a full entity ref first, then\n // fall back to the given value.\n const selectedEntity =\n entities?.catalogEntities.find(e => stringifyEntityRef(e) === formData) ??\n (allowArbitraryValues && formData ? getLabel(formData) : '');\n\n // BUI: options for autocomplete\n const buiOptions = useMemo(\n () =>\n (entities?.catalogEntities || []).map(entity => {\n const entityRef = stringifyEntityRef(entity);\n const presentation = entities?.entityRefToPresentation.get(entityRef);\n return {\n value: entityRef,\n label: presentation?.primaryTitle || entityRef,\n };\n }),\n [entities],\n );\n\n // BUI: controlled input value\n const [inputValue, setInputValue] = useState(formData || '');\n\n useEffect(() => {\n if (formData) {\n const opt = buiOptions.find(o => o.value === formData);\n setInputValue(opt?.label || formData);\n } else {\n setInputValue('');\n }\n }, [formData, buiOptions]);\n\n const selectedKey =\n formData && buiOptions.some(o => o.value === formData) ? formData : null;\n\n const lastCommittedRef = useRef(formData);\n\n useEffect(() => {\n lastCommittedRef.current = formData;\n }, [formData]);\n\n const handleSelectionChange = useCallback(\n (key: Key | null) => {\n if (key !== null) {\n const value = String(key);\n lastCommittedRef.current = value;\n onChange(value);\n } else if (allowArbitraryValues && inputValue) {\n let entityRef = inputValue;\n try {\n entityRef = stringifyEntityRef(\n parseEntityRef(inputValue, { defaultKind, defaultNamespace }),\n );\n } catch {\n // If the input isn't a valid entity ref, use it as-is\n }\n lastCommittedRef.current = entityRef;\n onChange(entityRef);\n } else {\n lastCommittedRef.current = undefined;\n onChange(undefined);\n }\n },\n [onChange, allowArbitraryValues, inputValue, defaultKind, defaultNamespace],\n );\n\n const handleBlur = useCallback(() => {\n if (allowArbitraryValues && inputValue) {\n let entityRef = inputValue;\n try {\n entityRef = stringifyEntityRef(\n parseEntityRef(inputValue, { defaultKind, defaultNamespace }),\n );\n } catch {\n // If the input isn't a valid entity ref, use it as-is\n }\n if (lastCommittedRef.current !== entityRef) {\n lastCommittedRef.current = entityRef;\n onChange(entityRef);\n }\n }\n }, [\n allowArbitraryValues,\n inputValue,\n defaultKind,\n defaultNamespace,\n onChange,\n ]);\n\n // Auto-select when only one entity and required\n useEffect(() => {\n if (theme === 'bui') {\n if (\n required &&\n !allowArbitraryValues &&\n entities?.catalogEntities.length === 1 &&\n !formData\n ) {\n onChange(stringifyEntityRef(entities.catalogEntities[0]));\n }\n } else {\n if (\n required &&\n !allowArbitraryValues &&\n entities?.catalogEntities.length === 1 &&\n selectedEntity === ''\n ) {\n onChange(stringifyEntityRef(entities.catalogEntities[0]));\n }\n }\n }, [\n entities,\n onChange,\n selectedEntity,\n formData,\n required,\n allowArbitraryValues,\n theme,\n ]);\n\n if (theme === 'bui') {\n const isAutoSelected =\n required &&\n !allowArbitraryValues &&\n entities?.catalogEntities.length === 1;\n\n return (\n <ScaffolderField\n rawErrors={rawErrors}\n rawDescription={uiSchema['ui:description'] ?? description}\n required={required}\n disabled={isDisabled}\n errors={errors}\n >\n <BuiAutocomplete\n id={idSchema?.$id}\n label={title}\n isRequired={required}\n isDisabled={isDisabled || isAutoSelected}\n selectedKey={selectedKey}\n inputValue={inputValue}\n onInputChange={setInputValue}\n onSelectionChange={handleSelectionChange}\n onBlur={handleBlur}\n isLoading={loading}\n options={buiOptions}\n allowsCustomValue={allowArbitraryValues}\n isInvalid={rawErrors && rawErrors.length > 0}\n />\n </ScaffolderField>\n );\n }\n\n return (\n <ScaffolderField\n rawErrors={rawErrors}\n rawDescription={uiSchema['ui:description'] ?? description}\n required={required}\n disabled={isDisabled}\n errors={errors}\n >\n <Autocomplete\n disabled={\n isDisabled ||\n (required &&\n !allowArbitraryValues &&\n entities?.catalogEntities.length === 1)\n }\n id={idSchema?.$id}\n value={selectedEntity}\n loading={loading}\n onChange={onSelect}\n options={entities?.catalogEntities || []}\n getOptionLabel={option =>\n // option can be a string due to freeSolo.\n typeof option === 'string'\n ? option\n : entities?.entityRefToPresentation.get(stringifyEntityRef(option))\n ?.entityRef!\n }\n autoSelect={autoSelect}\n freeSolo={allowArbitraryValues}\n renderInput={params => (\n <TextField\n {...params}\n label={title}\n margin=\"dense\"\n variant=\"outlined\"\n required={required}\n disabled={isDisabled}\n InputProps={params.InputProps}\n />\n )}\n renderOption={option => <EntityDisplayName entityRef={option} />}\n filterOptions={createFilterOptions<Entity>({\n stringify: option =>\n entities?.entityRefToPresentation.get(stringifyEntityRef(option))\n ?.primaryTitle!,\n })}\n ListboxComponent={VirtualizedListbox}\n />\n </ScaffolderField>\n );\n};\n\n/**\n * Converts a especial `{exists: true}` value to the `CATALOG_FILTER_EXISTS` symbol.\n *\n * @param value - The value to convert.\n * @returns The converted value.\n */\nfunction convertOpsValues(\n value: Exclude<EntityPickerFilterQueryValue, Array<any>>,\n): string | symbol {\n if (typeof value === 'object' && value.exists) {\n return CATALOG_FILTER_EXISTS;\n }\n return value?.toString();\n}\n\n/**\n * Converts schema filters to entity filter query, replacing `{exists:true}` values\n * with the constant `CATALOG_FILTER_EXISTS`.\n *\n * @param schemaFilters - An object containing schema filters with keys as filter names\n * and values as filter values.\n * @returns An object with the same keys as the input object, but with `{exists:true}` values\n * transformed to `CATALOG_FILTER_EXISTS` symbol.\n */\nfunction convertSchemaFiltersToQuery(\n schemaFilters: EntityPickerFilterQuery,\n): Exclude<EntityFilterQuery, Array<any>> {\n const query: EntityFilterQuery = {};\n\n for (const [key, value] of Object.entries(schemaFilters)) {\n if (Array.isArray(value)) {\n query[key] = value;\n } else {\n query[key] = convertOpsValues(value);\n }\n }\n\n return query;\n}\n\n/**\n * Builds an `EntityFilterQuery` based on the `uiSchema` passed in.\n * If `catalogFilter` is specified in the `uiSchema`, it is converted to a `EntityFilterQuery`.\n * If `allowedKinds` is specified in the `uiSchema` will support the legacy `allowedKinds` option.\n *\n * @param uiSchema The `uiSchema` of an `EntityPicker` component.\n * @returns An `EntityFilterQuery` based on the `uiSchema`, or `undefined` if `catalogFilter` is not specified in the `uiSchema`.\n */\nfunction buildCatalogFilter(\n uiSchema: EntityPickerProps['uiSchema'],\n): EntityFilterQuery | undefined {\n const allowedKinds = uiSchema['ui:options']?.allowedKinds;\n\n const catalogFilter: EntityPickerUiOptions['catalogFilter'] | undefined =\n uiSchema['ui:options']?.catalogFilter ||\n (allowedKinds && { kind: allowedKinds });\n\n if (!catalogFilter) {\n return undefined;\n }\n\n if (Array.isArray(catalogFilter)) {\n return catalogFilter.map(convertSchemaFiltersToQuery);\n }\n\n return convertSchemaFiltersToQuery(catalogFilter);\n}\n"],"names":["BuiAutocomplete","Autocomplete","TextField"],"mappings":"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"}
@@ -1,18 +1,21 @@
1
- import { jsx } from 'react/jsx-runtime';
2
- import { useState } from 'react';
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { useState, useMemo, useCallback } from 'react';
3
3
  import useAsync from 'react-use/esm/useAsync';
4
4
  import useEffectOnce from 'react-use/esm/useEffectOnce';
5
5
  import { makeValidator } from '@backstage/catalog-model';
6
6
  import { useApi } from '@backstage/core-plugin-api';
7
7
  import { catalogApiRef } from '@backstage/plugin-catalog-react';
8
- import TextField from '@material-ui/core/TextField';
9
- import Autocomplete from '@material-ui/lab/Autocomplete';
8
+ import MuiTextField from '@material-ui/core/TextField';
9
+ import MuiAutocomplete from '@material-ui/lab/Autocomplete';
10
10
  import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
11
11
  import { scaffolderTranslationRef } from '../../../translation.esm.js';
12
- import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';
12
+ import { useScaffolderTheme, ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';
13
+ import { Autocomplete } from '../Autocomplete/Autocomplete.esm.js';
14
+ import { chipStyle, chipRemoveStyle } from '../buiChipStyles.esm.js';
13
15
  export { EntityTagsPickerSchema } from './schema.esm.js';
14
16
 
15
17
  const EntityTagsPicker = (props) => {
18
+ const theme = useScaffolderTheme();
16
19
  const { t } = useTranslationRef(scaffolderTranslationRef);
17
20
  const {
18
21
  formData,
@@ -67,7 +70,103 @@ const EntityTagsPicker = (props) => {
67
70
  onChange(values || []);
68
71
  }
69
72
  };
73
+ const buiTagOptions = useMemo(() => {
74
+ if (!existingTags) return [];
75
+ return Object.keys(existingTags).sort(
76
+ (a, b) => showCounts ? existingTags[b] - existingTags[a] : a.localeCompare(b)
77
+ );
78
+ }, [existingTags, showCounts]);
79
+ const selectedTags = useMemo(() => formData || [], [formData]);
80
+ const availableOptions = useMemo(
81
+ () => buiTagOptions.filter((tag) => !selectedTags.includes(tag)).map((tag) => ({
82
+ value: tag,
83
+ label: showCounts && existingTags ? `${tag} (${existingTags[tag]})` : tag
84
+ })),
85
+ [buiTagOptions, selectedTags, showCounts, existingTags]
86
+ );
87
+ const handleSelectionChange = useCallback(
88
+ (key) => {
89
+ if (key !== null) {
90
+ const tag = String(key);
91
+ if (!selectedTags.includes(tag)) {
92
+ onChange([...selectedTags, tag]);
93
+ }
94
+ setInputValue("");
95
+ setInputError(false);
96
+ } else if (inputValue) {
97
+ const newTag = inputValue.toLocaleLowerCase("en-US").trim();
98
+ const isValid = tagValidator(newTag);
99
+ const isDuplicate = selectedTags.includes(newTag);
100
+ setInputError(!isValid);
101
+ if (isValid && !isDuplicate) {
102
+ onChange([...selectedTags, newTag]);
103
+ setInputValue("");
104
+ }
105
+ }
106
+ },
107
+ [selectedTags, onChange, inputValue, tagValidator]
108
+ );
109
+ const handleRemove = useCallback(
110
+ (tag) => {
111
+ onChange(selectedTags.filter((item) => item !== tag));
112
+ },
113
+ [selectedTags, onChange]
114
+ );
70
115
  useEffectOnce(() => onChange(formData || []));
116
+ if (theme === "bui") {
117
+ return /* @__PURE__ */ jsx(
118
+ ScaffolderField,
119
+ {
120
+ rawErrors,
121
+ rawDescription: helperText ?? uiSchema["ui:description"] ?? description,
122
+ required,
123
+ disabled: isDisabled,
124
+ errors,
125
+ children: /* @__PURE__ */ jsxs("div", { children: [
126
+ selectedTags.length > 0 && /* @__PURE__ */ jsx(
127
+ "div",
128
+ {
129
+ style: {
130
+ display: "flex",
131
+ flexWrap: "wrap",
132
+ gap: "var(--bui-space-1)",
133
+ marginBottom: "var(--bui-space-2)"
134
+ },
135
+ children: selectedTags.map((tag) => /* @__PURE__ */ jsxs("span", { style: chipStyle, children: [
136
+ tag,
137
+ !isDisabled && /* @__PURE__ */ jsx(
138
+ "button",
139
+ {
140
+ type: "button",
141
+ onClick: () => handleRemove(tag),
142
+ style: chipRemoveStyle,
143
+ "aria-label": `Remove ${tag}`,
144
+ children: "\xD7"
145
+ }
146
+ )
147
+ ] }, tag))
148
+ }
149
+ ),
150
+ /* @__PURE__ */ jsx(
151
+ Autocomplete,
152
+ {
153
+ label: title,
154
+ isRequired: required,
155
+ isDisabled,
156
+ selectedKey: null,
157
+ inputValue,
158
+ onInputChange: setInputValue,
159
+ onSelectionChange: handleSelectionChange,
160
+ isLoading: loading,
161
+ options: availableOptions,
162
+ allowsCustomValue: true,
163
+ isInvalid: inputError || rawErrors && rawErrors.length > 0
164
+ }
165
+ )
166
+ ] })
167
+ }
168
+ );
169
+ }
71
170
  return /* @__PURE__ */ jsx(
72
171
  ScaffolderField,
73
172
  {
@@ -77,7 +176,7 @@ const EntityTagsPicker = (props) => {
77
176
  disabled: isDisabled,
78
177
  errors,
79
178
  children: /* @__PURE__ */ jsx(
80
- Autocomplete,
179
+ MuiAutocomplete,
81
180
  {
82
181
  multiple: true,
83
182
  freeSolo: true,
@@ -91,7 +190,7 @@ const EntityTagsPicker = (props) => {
91
190
  ChipProps: { size: "small" },
92
191
  renderOption: (option) => showCounts ? `${option} (${existingTags?.[option]})` : option,
93
192
  renderInput: (params) => /* @__PURE__ */ jsx(
94
- TextField,
193
+ MuiTextField,
95
194
  {
96
195
  ...params,
97
196
  label: title,
@@ -1 +1 @@
1
- {"version":3,"file":"EntityTagsPicker.esm.js","sources":["../../../../src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { ChangeEvent, useState } from 'react';\nimport useAsync from 'react-use/esm/useAsync';\nimport useEffectOnce from 'react-use/esm/useEffectOnce';\nimport { GetEntityFacetsRequest } from '@backstage/catalog-client';\nimport { makeValidator } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport TextField from '@material-ui/core/TextField';\nimport Autocomplete from '@material-ui/lab/Autocomplete';\nimport { EntityTagsPickerProps } from './schema';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { scaffolderTranslationRef } from '../../../translation';\nimport { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';\n\nexport { EntityTagsPickerSchema } from './schema';\n\n/**\n * The underlying component that is rendered in the form for the `EntityTagsPicker`\n * field extension.\n *\n * @public\n */\nexport const EntityTagsPicker = (props: EntityTagsPickerProps) => {\n const { t } = useTranslationRef(scaffolderTranslationRef);\n const {\n formData,\n onChange,\n schema: {\n title = t('fields.entityTagsPicker.title'),\n description = t('fields.entityTagsPicker.description'),\n },\n uiSchema,\n rawErrors,\n required,\n errors,\n } = props;\n const catalogApi = useApi(catalogApiRef);\n const [tagOptions, setTagOptions] = useState<string[]>([]);\n const [inputValue, setInputValue] = useState('');\n const [inputError, setInputError] = useState(false);\n const tagValidator = makeValidator().isValidTag;\n const kinds = uiSchema['ui:options']?.kinds;\n const showCounts = uiSchema['ui:options']?.showCounts;\n const helperText = uiSchema['ui:options']?.helperText;\n const isDisabled = uiSchema?.['ui:disabled'] ?? false;\n\n const { loading, value: existingTags } = useAsync(async () => {\n const facet = 'metadata.tags';\n const tagsRequest: GetEntityFacetsRequest = { facets: [facet] };\n if (kinds) {\n tagsRequest.filter = { kind: kinds };\n }\n\n const { facets } = await catalogApi.getEntityFacets(tagsRequest);\n\n const tagFacets = Object.fromEntries(\n facets[facet].map(({ value, count }) => [value, count]),\n );\n\n setTagOptions(\n Object.keys(tagFacets).sort((a, b) =>\n showCounts ? tagFacets[b] - tagFacets[a] : a.localeCompare(b),\n ),\n );\n\n return tagFacets;\n });\n\n const setTags = (_: ChangeEvent<{}>, values: string[] | null) => {\n // Reset error state in case all tags were removed\n let hasError = false;\n let addDuplicate = false;\n const currentTags = formData || [];\n\n // If adding a new tag\n if (values?.length && currentTags.length < values.length) {\n const newTag = (values[values.length - 1] = values[values.length - 1]\n .toLocaleLowerCase('en-US')\n .trim());\n hasError = !tagValidator(newTag);\n addDuplicate = currentTags.indexOf(newTag) !== -1;\n }\n\n setInputError(hasError);\n setInputValue(!hasError ? '' : inputValue);\n if (!hasError && !addDuplicate) {\n onChange(values || []);\n }\n };\n\n // Initialize field to always return an array\n useEffectOnce(() => onChange(formData || []));\n\n return (\n <ScaffolderField\n rawErrors={rawErrors}\n rawDescription={helperText ?? uiSchema['ui:description'] ?? description}\n required={required}\n disabled={isDisabled}\n errors={errors}\n >\n <Autocomplete\n multiple\n freeSolo\n filterSelectedOptions\n onChange={setTags}\n disabled={isDisabled}\n value={formData || []}\n inputValue={inputValue}\n loading={loading}\n options={tagOptions}\n ChipProps={{ size: 'small' }}\n renderOption={option =>\n showCounts ? `${option} (${existingTags?.[option]})` : option\n }\n renderInput={params => (\n <TextField\n {...params}\n label={title}\n disabled={isDisabled}\n onChange={e => setInputValue(e.target.value)}\n error={inputError}\n FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }}\n />\n )}\n />\n </ScaffolderField>\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityTagsPicker.esm.js","sources":["../../../../src/components/fields/EntityTagsPicker/EntityTagsPicker.tsx"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { type Key, ChangeEvent, useCallback, useMemo, useState } from 'react';\nimport useAsync from 'react-use/esm/useAsync';\nimport useEffectOnce from 'react-use/esm/useEffectOnce';\nimport { GetEntityFacetsRequest } from '@backstage/catalog-client';\nimport { makeValidator } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport TextField from '@material-ui/core/TextField';\nimport Autocomplete from '@material-ui/lab/Autocomplete';\nimport { EntityTagsPickerProps } from './schema';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { scaffolderTranslationRef } from '../../../translation';\nimport {\n ScaffolderField,\n useScaffolderTheme,\n} from '@backstage/plugin-scaffolder-react/alpha';\nimport { Autocomplete as BuiAutocomplete } from '../Autocomplete';\nimport { chipStyle, chipRemoveStyle } from '../buiChipStyles';\n\nexport { EntityTagsPickerSchema } from './schema';\n\n/**\n * The underlying component that is rendered in the form for the `EntityTagsPicker`\n * field extension.\n *\n * @public\n */\nexport const EntityTagsPicker = (props: EntityTagsPickerProps) => {\n const theme = useScaffolderTheme();\n const { t } = useTranslationRef(scaffolderTranslationRef);\n const {\n formData,\n onChange,\n schema: {\n title = t('fields.entityTagsPicker.title'),\n description = t('fields.entityTagsPicker.description'),\n },\n uiSchema,\n rawErrors,\n required,\n errors,\n } = props;\n const catalogApi = useApi(catalogApiRef);\n const [tagOptions, setTagOptions] = useState<string[]>([]);\n const [inputValue, setInputValue] = useState('');\n const [inputError, setInputError] = useState(false);\n const tagValidator = makeValidator().isValidTag;\n const kinds = uiSchema['ui:options']?.kinds;\n const showCounts = uiSchema['ui:options']?.showCounts;\n const helperText = uiSchema['ui:options']?.helperText;\n const isDisabled = uiSchema?.['ui:disabled'] ?? false;\n\n const { loading, value: existingTags } = useAsync(async () => {\n const facet = 'metadata.tags';\n const tagsRequest: GetEntityFacetsRequest = { facets: [facet] };\n if (kinds) {\n tagsRequest.filter = { kind: kinds };\n }\n\n const { facets } = await catalogApi.getEntityFacets(tagsRequest);\n\n const tagFacets = Object.fromEntries(\n facets[facet].map(({ value, count }) => [value, count]),\n );\n\n setTagOptions(\n Object.keys(tagFacets).sort((a, b) =>\n showCounts ? tagFacets[b] - tagFacets[a] : a.localeCompare(b),\n ),\n );\n\n return tagFacets;\n });\n\n const setTags = (_: ChangeEvent<{}>, values: string[] | null) => {\n // Reset error state in case all tags were removed\n let hasError = false;\n let addDuplicate = false;\n const currentTags = formData || [];\n\n // If adding a new tag\n if (values?.length && currentTags.length < values.length) {\n const newTag = (values[values.length - 1] = values[values.length - 1]\n .toLocaleLowerCase('en-US')\n .trim());\n hasError = !tagValidator(newTag);\n addDuplicate = currentTags.indexOf(newTag) !== -1;\n }\n\n setInputError(hasError);\n setInputValue(!hasError ? '' : inputValue);\n if (!hasError && !addDuplicate) {\n onChange(values || []);\n }\n };\n\n // BUI: computed tag options\n const buiTagOptions = useMemo(() => {\n if (!existingTags) return [];\n return Object.keys(existingTags).sort((a, b) =>\n showCounts ? existingTags[b] - existingTags[a] : a.localeCompare(b),\n );\n }, [existingTags, showCounts]);\n\n const selectedTags = useMemo(() => formData || [], [formData]);\n const availableOptions = useMemo(\n () =>\n buiTagOptions\n .filter(tag => !selectedTags.includes(tag))\n .map(tag => ({\n value: tag,\n label:\n showCounts && existingTags ? `${tag} (${existingTags[tag]})` : tag,\n })),\n [buiTagOptions, selectedTags, showCounts, existingTags],\n );\n\n const handleSelectionChange = useCallback(\n (key: Key | null) => {\n if (key !== null) {\n const tag = String(key);\n if (!selectedTags.includes(tag)) {\n onChange([...selectedTags, tag]);\n }\n setInputValue('');\n setInputError(false);\n } else if (inputValue) {\n const newTag = inputValue.toLocaleLowerCase('en-US').trim();\n const isValid = tagValidator(newTag);\n const isDuplicate = selectedTags.includes(newTag);\n setInputError(!isValid);\n\n if (isValid && !isDuplicate) {\n onChange([...selectedTags, newTag]);\n setInputValue('');\n }\n }\n },\n [selectedTags, onChange, inputValue, tagValidator],\n );\n\n const handleRemove = useCallback(\n (tag: string) => {\n onChange(selectedTags.filter(item => item !== tag));\n },\n [selectedTags, onChange],\n );\n\n // Initialize field to always return an array\n useEffectOnce(() => onChange(formData || []));\n\n if (theme === 'bui') {\n return (\n <ScaffolderField\n rawErrors={rawErrors}\n rawDescription={\n (helperText as string) ?? uiSchema['ui:description'] ?? description\n }\n required={required}\n disabled={isDisabled}\n errors={errors}\n >\n <div>\n {selectedTags.length > 0 && (\n <div\n style={{\n display: 'flex',\n flexWrap: 'wrap',\n gap: 'var(--bui-space-1)',\n marginBottom: 'var(--bui-space-2)',\n }}\n >\n {selectedTags.map(tag => (\n <span key={tag} style={chipStyle}>\n {tag}\n {!isDisabled && (\n <button\n type=\"button\"\n onClick={() => handleRemove(tag)}\n style={chipRemoveStyle}\n aria-label={`Remove ${tag}`}\n >\n &times;\n </button>\n )}\n </span>\n ))}\n </div>\n )}\n <BuiAutocomplete\n label={title}\n isRequired={required}\n isDisabled={isDisabled}\n selectedKey={null}\n inputValue={inputValue}\n onInputChange={setInputValue}\n onSelectionChange={handleSelectionChange}\n isLoading={loading}\n options={availableOptions}\n allowsCustomValue\n isInvalid={inputError || (rawErrors && rawErrors.length > 0)}\n />\n </div>\n </ScaffolderField>\n );\n }\n\n return (\n <ScaffolderField\n rawErrors={rawErrors}\n rawDescription={helperText ?? uiSchema['ui:description'] ?? description}\n required={required}\n disabled={isDisabled}\n errors={errors}\n >\n <Autocomplete\n multiple\n freeSolo\n filterSelectedOptions\n onChange={setTags}\n disabled={isDisabled}\n value={formData || []}\n inputValue={inputValue}\n loading={loading}\n options={tagOptions}\n ChipProps={{ size: 'small' }}\n renderOption={option =>\n showCounts ? `${option} (${existingTags?.[option]})` : option\n }\n renderInput={params => (\n <TextField\n {...params}\n label={title}\n disabled={isDisabled}\n onChange={e => setInputValue(e.target.value)}\n error={inputError}\n FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }}\n />\n )}\n />\n </ScaffolderField>\n );\n};\n"],"names":["BuiAutocomplete","Autocomplete","TextField"],"mappings":"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"}
@@ -1,16 +1,18 @@
1
- import { jsx } from 'react/jsx-runtime';
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
3
3
  import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
4
4
  import { useApi } from '@backstage/core-plugin-api';
5
5
  import { catalogApiRef, entityPresentationApiRef, EntityDisplayName } from '@backstage/plugin-catalog-react';
6
- import TextField from '@material-ui/core/TextField';
7
- import Autocomplete, { createFilterOptions } from '@material-ui/lab/Autocomplete';
8
- import { useState, useCallback, useEffect } from 'react';
6
+ import MuiTextField from '@material-ui/core/TextField';
7
+ import MuiAutocomplete, { createFilterOptions } from '@material-ui/lab/Autocomplete';
8
+ import { useState, useCallback, useMemo, useEffect } from 'react';
9
9
  import useAsync from 'react-use/esm/useAsync';
10
10
  import { VirtualizedListbox } from '../VirtualizedListbox.esm.js';
11
- import { ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';
11
+ import { useScaffolderTheme, ScaffolderField } from '@backstage/plugin-scaffolder-react/alpha';
12
12
  import { useTranslationRef } from '@backstage/frontend-plugin-api';
13
13
  import { scaffolderTranslationRef } from '../../../translation.esm.js';
14
+ import { Autocomplete } from '../Autocomplete/Autocomplete.esm.js';
15
+ import { chipStyle, chipRemoveStyle } from '../buiChipStyles.esm.js';
14
16
  export { MultiEntityPickerSchema } from './schema.esm.js';
15
17
 
16
18
  const FREE_SOLO_EVENTS = [
@@ -18,6 +20,7 @@ const FREE_SOLO_EVENTS = [
18
20
  "create-option"
19
21
  ];
20
22
  const MultiEntityPicker = (props) => {
23
+ const theme = useScaffolderTheme();
21
24
  const { t } = useTranslationRef(scaffolderTranslationRef);
22
25
  const {
23
26
  onChange,
@@ -36,6 +39,8 @@ const MultiEntityPicker = (props) => {
36
39
  const defaultKind = uiSchema["ui:options"]?.defaultKind;
37
40
  const defaultNamespace = uiSchema["ui:options"]?.defaultNamespace || void 0;
38
41
  const isDisabled = uiSchema?.["ui:disabled"] ?? false;
42
+ const allowArbitraryValues = uiSchema["ui:options"]?.allowArbitraryValues ?? true;
43
+ const maxItems = props.schema.maxItems;
39
44
  const [noOfItemsSelected, setNoOfItemsSelected] = useState(0);
40
45
  const catalogApi = useApi(catalogApiRef);
41
46
  const entityPresentationApi = useApi(entityPresentationApiRef);
@@ -53,8 +58,6 @@ const MultiEntityPicker = (props) => {
53
58
  );
54
59
  return { entities: items, entityRefToPresentation };
55
60
  });
56
- const allowArbitraryValues = uiSchema["ui:options"]?.allowArbitraryValues ?? true;
57
- const maxItems = props.schema.maxItems;
58
61
  const onSelect = useCallback(
59
62
  (_, refs, reason) => {
60
63
  const values = refs.map((ref) => {
@@ -85,11 +88,126 @@ const MultiEntityPicker = (props) => {
85
88
  },
86
89
  [onChange, formData, defaultKind, defaultNamespace, allowArbitraryValues]
87
90
  );
91
+ const allOptions = useMemo(
92
+ () => (entities?.entities || []).map((entity) => {
93
+ const entityRef = stringifyEntityRef(entity);
94
+ const presentation = entities?.entityRefToPresentation.get(entityRef);
95
+ return {
96
+ value: entityRef,
97
+ label: presentation?.primaryTitle || entityRef
98
+ };
99
+ }),
100
+ [entities]
101
+ );
102
+ const selectedValues = useMemo(() => formData || [], [formData]);
103
+ const availableOptions = useMemo(
104
+ () => allOptions.filter((o) => !selectedValues.includes(o.value)),
105
+ [allOptions, selectedValues]
106
+ );
107
+ const [inputValue, setInputValue] = useState("");
108
+ const atMaxItems = maxItems !== void 0 && selectedValues.length >= maxItems;
109
+ const handleSelectionChange = useCallback(
110
+ (key) => {
111
+ if (atMaxItems) return;
112
+ if (key !== null) {
113
+ const newValue = String(key);
114
+ if (!selectedValues.includes(newValue)) {
115
+ onChange([...selectedValues, newValue]);
116
+ }
117
+ } else if (allowArbitraryValues && inputValue) {
118
+ let entityRef = inputValue;
119
+ try {
120
+ entityRef = stringifyEntityRef(
121
+ parseEntityRef(inputValue, { defaultKind, defaultNamespace })
122
+ );
123
+ } catch {
124
+ }
125
+ if (!selectedValues.includes(entityRef)) {
126
+ onChange([...selectedValues, entityRef]);
127
+ }
128
+ }
129
+ setInputValue("");
130
+ },
131
+ [
132
+ atMaxItems,
133
+ selectedValues,
134
+ onChange,
135
+ allowArbitraryValues,
136
+ inputValue,
137
+ defaultKind,
138
+ defaultNamespace
139
+ ]
140
+ );
141
+ const handleRemove = useCallback(
142
+ (value) => {
143
+ onChange(selectedValues.filter((v) => v !== value));
144
+ },
145
+ [selectedValues, onChange]
146
+ );
88
147
  useEffect(() => {
89
148
  if (required && !allowArbitraryValues && entities?.entities?.length === 1) {
90
- onChange([stringifyEntityRef(entities?.entities[0])]);
149
+ onChange([stringifyEntityRef(entities.entities[0])]);
91
150
  }
92
151
  }, [entities, onChange, required, allowArbitraryValues]);
152
+ if (theme === "bui") {
153
+ const isAutoSelected = required && !allowArbitraryValues && entities?.entities?.length === 1;
154
+ return /* @__PURE__ */ jsx(
155
+ ScaffolderField,
156
+ {
157
+ rawErrors,
158
+ rawDescription: uiSchema["ui:description"] ?? description,
159
+ required,
160
+ disabled: isDisabled,
161
+ errors,
162
+ children: /* @__PURE__ */ jsxs("div", { children: [
163
+ selectedValues.length > 0 && /* @__PURE__ */ jsx(
164
+ "div",
165
+ {
166
+ style: {
167
+ display: "flex",
168
+ flexWrap: "wrap",
169
+ gap: "var(--bui-space-1)",
170
+ marginBottom: "var(--bui-space-2)"
171
+ },
172
+ children: selectedValues.map((value) => {
173
+ const opt = allOptions.find((o) => o.value === value);
174
+ return /* @__PURE__ */ jsxs("span", { style: chipStyle, children: [
175
+ opt?.label || value,
176
+ !isDisabled && !isAutoSelected && /* @__PURE__ */ jsx(
177
+ "button",
178
+ {
179
+ type: "button",
180
+ onClick: () => handleRemove(value),
181
+ style: chipRemoveStyle,
182
+ "aria-label": `Remove ${opt?.label || value}`,
183
+ children: "\xD7"
184
+ }
185
+ )
186
+ ] }, value);
187
+ })
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsx(
191
+ Autocomplete,
192
+ {
193
+ id: idSchema?.$id,
194
+ label: title,
195
+ isRequired: required,
196
+ isDisabled: isDisabled || isAutoSelected || atMaxItems,
197
+ selectedKey: null,
198
+ inputValue,
199
+ onInputChange: setInputValue,
200
+ onSelectionChange: handleSelectionChange,
201
+ isLoading: loading,
202
+ options: availableOptions,
203
+ allowsCustomValue: allowArbitraryValues,
204
+ isInvalid: rawErrors && rawErrors.length > 0
205
+ }
206
+ )
207
+ ] })
208
+ }
209
+ );
210
+ }
93
211
  return /* @__PURE__ */ jsx(
94
212
  ScaffolderField,
95
213
  {
@@ -99,7 +217,7 @@ const MultiEntityPicker = (props) => {
99
217
  disabled: isDisabled,
100
218
  errors,
101
219
  children: /* @__PURE__ */ jsx(
102
- Autocomplete,
220
+ MuiAutocomplete,
103
221
  {
104
222
  multiple: true,
105
223
  filterSelectedOptions: true,
@@ -118,7 +236,7 @@ const MultiEntityPicker = (props) => {
118
236
  autoSelect: true,
119
237
  freeSolo: allowArbitraryValues,
120
238
  renderInput: (params) => /* @__PURE__ */ jsx(
121
- TextField,
239
+ MuiTextField,
122
240
  {
123
241
  ...params,
124
242
  label: title,