Update scaffolder filters to use humanizeEntityRef but stringifyEntityRef in the backend.

Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
Aramis Sennyey
2023-02-10 12:42:04 -05:00
parent 42b2f65421
commit f36840b466
2 changed files with 80 additions and 94 deletions
@@ -14,7 +14,11 @@
* limitations under the License.
*/
import { type EntityFilterQuery } from '@backstage/catalog-client';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import {
Entity,
parseEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import {
catalogApiRef,
@@ -22,8 +26,10 @@ import {
} from '@backstage/plugin-catalog-react';
import { TextField } from '@material-ui/core';
import FormControl from '@material-ui/core/FormControl';
import Autocomplete from '@material-ui/lab/Autocomplete';
import React, { useCallback, useEffect, useState } from 'react';
import Autocomplete, {
AutocompleteChangeReason,
} from '@material-ui/lab/Autocomplete';
import React, { useCallback, useEffect } from 'react';
import useAsync from 'react-use/lib/useAsync';
import { EntityPickerProps } from './schema';
@@ -46,7 +52,6 @@ export const EntityPicker = (props: EntityPickerProps) => {
idSchema,
} = props;
const allowedKinds = uiSchema['ui:options']?.allowedKinds;
console.log(formData);
const catalogFilter: EntityFilterQuery | undefined =
uiSchema['ui:options']?.catalogFilter ||
@@ -56,7 +61,6 @@ export const EntityPicker = (props: EntityPickerProps) => {
const defaultNamespace = uiSchema['ui:options']?.defaultNamespace;
const catalogApi = useApi(catalogApiRef);
const [value, setValue] = useState(formData);
const { value: entities, loading } = useAsync(async () => {
const { items } = await catalogApi.getEntities(
@@ -64,16 +68,36 @@ export const EntityPicker = (props: EntityPickerProps) => {
);
return items;
});
const allowArbitraryValues =
uiSchema['ui:options']?.allowArbitraryValues ?? true;
const onSelect = useCallback(
(_: any, ref: string | Entity | null) => {
let entityRef: string = typeof ref === 'string' ? ref : '';
if (typeof ref !== 'string')
entityRef = ref ? stringifyEntityRef(ref as Entity) : '';
setValue(entityRef);
onChange(entityRef);
(_: any, ref: string | Entity | null, reason: AutocompleteChangeReason) => {
// if ref == string
if (typeof ref !== 'string') {
onChange(ref ? stringifyEntityRef(ref as Entity) : '');
} else {
if (reason === 'blur') {
// Add in default namespace, etc.
let entityRef = ref;
try {
// Attempt to parse the entity ref into it's full form.
entityRef = stringifyEntityRef(
parseEntityRef(ref as string, {
defaultKind,
defaultNamespace: defaultNamespace || undefined,
}),
);
} catch (err) {
// If the passed in value isn't an entity ref, do nothing.
}
if (formData !== entityRef) {
onChange(ref);
}
}
}
},
[onChange, setValue],
[onChange, formData, defaultKind, defaultNamespace],
);
useEffect(() => {
@@ -91,17 +115,22 @@ export const EntityPicker = (props: EntityPickerProps) => {
<Autocomplete
disabled={entities?.length === 1}
id={idSchema?.$id}
value={entities?.find(e => e.metadata.name === formData)}
value={
// Since free solo is usually enabled, attempt to
entities?.find(e => stringifyEntityRef(e) === formData) ??
(allowArbitraryValues ? formData : '')
}
loading={loading}
onChange={onSelect}
options={entities || []}
getOptionLabel={option =>
// option can be a string due to freeSolo.
typeof option === 'string'
? option
: humanizeEntityRef(option, { defaultKind, defaultNamespace })!
}
autoSelect
freeSolo={uiSchema['ui:options']?.allowArbitraryValues ?? true}
freeSolo={allowArbitraryValues}
renderInput={params => (
<TextField
{...params}
@@ -13,18 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { GetEntitiesResponse } from '@backstage/catalog-client';
import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model';
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
import {
catalogApiRef,
humanizeEntityRef,
} from '@backstage/plugin-catalog-react';
import { TextField } from '@material-ui/core';
import FormControl from '@material-ui/core/FormControl';
import Autocomplete from '@material-ui/lab/Autocomplete';
import React, { useMemo } from 'react';
import React from 'react';
import useAsync from 'react-use/lib/useAsync';
import { EntityPicker } from '../EntityPicker/EntityPicker';
import { OwnedEntityPickerProps } from './schema';
@@ -38,95 +33,57 @@ export { OwnedEntityPickerSchema } from './schema';
*/
export const OwnedEntityPicker = (props: OwnedEntityPickerProps) => {
const {
onChange,
schema: { title = 'Entity', description = 'An entity from the catalog' },
required,
uiSchema,
rawErrors,
formData,
idSchema,
required,
} = props;
const identityApi = useApi(identityApiRef);
const { loading, value: identityRefs } = useAsync(async () => {
const identity = await identityApi.getBackstageIdentity();
return identity.ownershipEntityRefs;
});
const allowedKinds = uiSchema['ui:options']?.allowedKinds;
const defaultKind = uiSchema['ui:options']?.defaultKind;
const defaultNamespace = uiSchema['ui:options']?.defaultNamespace;
const allowArbitraryValues =
uiSchema['ui:options']?.allowArbitraryValues ?? true;
const { ownedEntities, loading } = useOwnedEntities(allowedKinds);
const entities = ownedEntities?.items.filter(n => n);
const onSelect = (_: any, value: Entity | null) => {
onChange(value?.metadata.name || '');
};
return (
<FormControl
margin="normal"
required={required}
error={rawErrors?.length > 0 && !formData}
>
if (loading)
return (
<Autocomplete
id={idSchema?.$id}
// value={(formData as string) || ''}
loading={loading}
onChange={onSelect}
options={entities || []}
autoSelect
freeSolo={allowArbitraryValues}
renderInput={params => (
<TextField
{...params}
label={title}
margin="normal"
margin="dense"
helperText={description}
FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }}
variant="outlined"
required={required}
InputProps={params.InputProps}
/>
)}
options={[]}
/>
</FormControl>
);
return (
<EntityPicker
{...props}
schema={{ title, description }}
allowedKinds={allowedKinds}
catalogFilter={
allowedKinds
? {
filter: {
kind: allowedKinds,
[`relations.${RELATION_OWNED_BY}`]: identityRefs || [],
},
}
: {
filter: {
[`relations.${RELATION_OWNED_BY}`]: identityRefs || [],
},
}
}
/>
);
};
/**
* Takes the relevant parts of the Backstage identity, and translates them into
* a list of entities which are owned by the user. Takes an optional parameter
* to filter the entities based on allowedKinds
*
*
* @param allowedKinds - Array of allowed kinds to filter the entities
*/
function useOwnedEntities(allowedKinds?: string[]): {
loading: boolean;
ownedEntities: GetEntitiesResponse | undefined;
} {
const identityApi = useApi(identityApiRef);
const catalogApi = useApi(catalogApiRef);
const { loading, value: refs } = useAsync(async () => {
const identity = await identityApi.getBackstageIdentity();
const identityRefs = identity.ownershipEntityRefs;
const catalogs = await catalogApi.getEntities(
allowedKinds
? {
filter: {
kind: allowedKinds,
[`relations.${RELATION_OWNED_BY}`]: identityRefs || [],
},
}
: {
filter: {
[`relations.${RELATION_OWNED_BY}`]: identityRefs || [],
},
},
);
return catalogs;
}, []);
const ownedEntities = useMemo(() => {
return refs;
}, [refs]);
return useMemo(() => ({ loading, ownedEntities }), [loading, ownedEntities]);
}