Update scaffolder filters to use humanizeEntityRef but stringifyEntityRef in the backend.
Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
@@ -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]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user