Merge branch 'master' into scaffolder/EntityPicker-arbitrary-values
This commit is contained in:
@@ -96,6 +96,24 @@ export type FieldExtensionOptions<T = any> = {
|
||||
validation?: CustomFieldValidator<T>;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "OwnedEntityPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const OwnedEntityPicker: ({
|
||||
onChange,
|
||||
schema: { title, description },
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
idSchema,
|
||||
}: FieldProps<string>) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "OwnedEntityPickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const OwnedEntityPickerFieldExtension: () => null;
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
"@backstage/catalog-client": "^0.5.2",
|
||||
"@backstage/catalog-model": "^0.9.7",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/core-components": "^0.8.0",
|
||||
"@backstage/core-plugin-api": "^0.3.0",
|
||||
"@backstage/core-components": "^0.8.1",
|
||||
"@backstage/core-plugin-api": "^0.3.1",
|
||||
"@backstage/errors": "^0.1.5",
|
||||
"@backstage/integration": "^0.6.10",
|
||||
"@backstage/integration-react": "^0.1.15",
|
||||
@@ -66,10 +66,10 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/plugin-catalog": "^0.7.4",
|
||||
"@backstage/cli": "^0.10.1",
|
||||
"@backstage/core-app-api": "^0.2.0",
|
||||
"@backstage/cli": "^0.10.2",
|
||||
"@backstage/core-app-api": "^0.2.1",
|
||||
"@backstage/dev-utils": "^0.2.14",
|
||||
"@backstage/plugin-catalog": "^0.7.4",
|
||||
"@backstage/test-utils": "^0.1.24",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -30,7 +30,6 @@ import { ListActionsResponse, ScaffolderTask, Status } from './types';
|
||||
|
||||
export const scaffolderApiRef = createApiRef<ScaffolderApi>({
|
||||
id: 'plugin.scaffolder.service',
|
||||
description: 'Used to make requests towards the scaffolder backend',
|
||||
});
|
||||
|
||||
type TemplateParameterSchema = {
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { MarkdownContent } from '@backstage/core-components';
|
||||
import { FieldProps } from '@rjsf/core';
|
||||
|
||||
export const DescriptionField = ({ description }: FieldProps) =>
|
||||
description && <MarkdownContent content={description} />;
|
||||
@@ -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 { DescriptionField } from './DescriptionField';
|
||||
@@ -30,6 +30,7 @@ import { Theme as MuiTheme } from '@rjsf/material-ui';
|
||||
import React, { useState } from 'react';
|
||||
import { transformSchemaToProps } from './schema';
|
||||
import { Content, StructuredMetadataTable } from '@backstage/core-components';
|
||||
import * as fieldOverrides from './FieldOverrides';
|
||||
|
||||
const Form = withTheme(MuiTheme);
|
||||
type Step = {
|
||||
@@ -152,7 +153,7 @@ export const MultistepJsonForm = ({
|
||||
<StepContent key={title}>
|
||||
<Form
|
||||
showErrorList={false}
|
||||
fields={fields}
|
||||
fields={{ ...fieldOverrides, ...fields }}
|
||||
widgets={widgets}
|
||||
noHtml5Validate
|
||||
formData={formData}
|
||||
|
||||
@@ -109,13 +109,15 @@ export const ScaffolderPageContents = ({
|
||||
</div>
|
||||
<div>
|
||||
{groups &&
|
||||
groups.map(group => (
|
||||
groups.map((group, index) => (
|
||||
<TemplateList
|
||||
key={index}
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
group={group}
|
||||
/>
|
||||
))}
|
||||
<TemplateList
|
||||
key="other"
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
group={otherTemplatesGroup}
|
||||
/>
|
||||
|
||||
@@ -24,11 +24,12 @@ import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
ItemCardGrid,
|
||||
Link,
|
||||
Progress,
|
||||
WarningPanel,
|
||||
} from '@backstage/core-components';
|
||||
import { useEntityListProvider } from '@backstage/plugin-catalog-react';
|
||||
import { Link, Typography } from '@material-ui/core';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { TemplateCard } from '../TemplateCard';
|
||||
|
||||
export type TemplateListProps = {
|
||||
@@ -73,7 +74,7 @@ export const TemplateList = ({
|
||||
{!error && !loading && !entities.length && (
|
||||
<Typography variant="body2">
|
||||
No templates found that match your filter. Learn more about{' '}
|
||||
<Link href="https://backstage.io/docs/features/software-templates/adding-templates">
|
||||
<Link to="https://backstage.io/docs/features/software-templates/adding-templates">
|
||||
adding templates
|
||||
</Link>
|
||||
.
|
||||
|
||||
@@ -22,7 +22,7 @@ import { TextField } from '@material-ui/core';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import { FieldProps, UiSchema } from '@rjsf/core';
|
||||
import React from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
export const allowArbitraryValues = (uiSchema: UiSchema): boolean =>
|
||||
@@ -51,9 +51,18 @@ export const EntityPicker = ({
|
||||
formatEntityRefTitle(e, { defaultKind }),
|
||||
);
|
||||
|
||||
const onSelect = (_: any, value: string | null) => {
|
||||
onChange(value || '');
|
||||
};
|
||||
const onSelect = useCallback(
|
||||
(_: any, value: string | null) => {
|
||||
onChange(value || '');
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (entityRefs?.length === 1) {
|
||||
onChange(entityRefs[0]);
|
||||
}
|
||||
}, [entityRefs, onChange]);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
@@ -62,6 +71,7 @@ export const EntityPicker = ({
|
||||
error={rawErrors?.length > 0 && !formData}
|
||||
>
|
||||
<Autocomplete
|
||||
disabled={entityRefs?.length === 1}
|
||||
id={idSchema?.$id}
|
||||
value={(formData as string) || ''}
|
||||
loading={loading}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 {
|
||||
formatEntityRefTitle,
|
||||
useOwnedEntities,
|
||||
} 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 { FieldProps } from '@rjsf/core';
|
||||
import React from 'react';
|
||||
|
||||
export const OwnedEntityPicker = ({
|
||||
onChange,
|
||||
schema: { title = 'Entity', description = 'An entity from the catalog' },
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
idSchema,
|
||||
}: FieldProps<string>) => {
|
||||
const allowedKinds = uiSchema['ui:options']?.allowedKinds as string[];
|
||||
const defaultKind = uiSchema['ui:options']?.defaultKind as string | undefined;
|
||||
const { ownedEntities, loading } = useOwnedEntities(allowedKinds);
|
||||
|
||||
const entityRefs = ownedEntities?.items
|
||||
.map(e => formatEntityRefTitle(e, { defaultKind }))
|
||||
.filter(n => n);
|
||||
|
||||
const onSelect = (_: any, value: string | null) => {
|
||||
onChange(value || '');
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
margin="normal"
|
||||
required={required}
|
||||
error={rawErrors?.length > 0 && !formData}
|
||||
>
|
||||
<Autocomplete
|
||||
id={idSchema?.$id}
|
||||
value={(formData as string) || ''}
|
||||
loading={loading}
|
||||
onChange={onSelect}
|
||||
options={entityRefs || []}
|
||||
autoSelect
|
||||
freeSolo
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={title}
|
||||
margin="normal"
|
||||
helperText={description}
|
||||
variant="outlined"
|
||||
required={required}
|
||||
InputProps={params.InputProps}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -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 { OwnedEntityPicker } from './OwnedEntityPicker';
|
||||
@@ -13,19 +13,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { FieldProps } from '@rjsf/core';
|
||||
import { scaffolderApiRef } from '../../../api';
|
||||
import {
|
||||
Progress,
|
||||
Select,
|
||||
SelectedItems,
|
||||
SelectItem,
|
||||
} from '@backstage/core-components';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
import { useAsync } from 'react-use';
|
||||
import Select from '@material-ui/core/Select';
|
||||
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 { useApi } from '@backstage/core-plugin-api';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import { FieldProps } from '@rjsf/core';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { scaffolderApiRef } from '../../../api';
|
||||
|
||||
function splitFormData(url: string | undefined, allowedOwners?: string[]) {
|
||||
let host = undefined;
|
||||
@@ -106,10 +109,10 @@ export const RepoUrlPicker = ({
|
||||
allowedOwners,
|
||||
);
|
||||
const updateHost = useCallback(
|
||||
(evt: React.ChangeEvent<{ name?: string; value: unknown }>) => {
|
||||
(value: SelectedItems) => {
|
||||
onChange(
|
||||
serializeFormData({
|
||||
host: evt.target.value as string,
|
||||
host: value as string,
|
||||
owner,
|
||||
repo,
|
||||
organization,
|
||||
@@ -121,6 +124,21 @@ export const RepoUrlPicker = ({
|
||||
[onChange, owner, repo, organization, workspace, project],
|
||||
);
|
||||
|
||||
const updateOwnerSelect = useCallback(
|
||||
(value: SelectedItems) =>
|
||||
onChange(
|
||||
serializeFormData({
|
||||
host,
|
||||
owner: value as string,
|
||||
repo,
|
||||
organization,
|
||||
workspace,
|
||||
project,
|
||||
}),
|
||||
),
|
||||
[onChange, host, repo, organization, workspace, project],
|
||||
);
|
||||
|
||||
const updateOwner = useCallback(
|
||||
(evt: React.ChangeEvent<{ name?: string; value: unknown }>) =>
|
||||
onChange(
|
||||
@@ -224,6 +242,16 @@ export const RepoUrlPicker = ({
|
||||
return <Progress />;
|
||||
}
|
||||
|
||||
const hostsOptions: SelectItem[] = integrations
|
||||
? integrations
|
||||
.filter(i => allowedHosts?.includes(i.host))
|
||||
.map(i => ({ label: i.title, value: i.host }))
|
||||
: [{ label: 'Loading...', value: 'loading' }];
|
||||
|
||||
const ownersOptions: SelectItem[] = allowedOwners
|
||||
? allowedOwners.map(i => ({ label: i, value: i }))
|
||||
: [{ label: 'Loading...', value: 'loading' }];
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormControl
|
||||
@@ -231,20 +259,15 @@ export const RepoUrlPicker = ({
|
||||
required
|
||||
error={rawErrors?.length > 0 && !host}
|
||||
>
|
||||
<InputLabel htmlFor="hostInput">Host</InputLabel>
|
||||
<Select native id="hostInput" onChange={updateHost} value={host}>
|
||||
{integrations ? (
|
||||
integrations
|
||||
.filter(i => allowedHosts?.includes(i.host))
|
||||
.map(i => (
|
||||
<option key={i.host} value={i.host}>
|
||||
{i.title}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<p>loading</p>
|
||||
)}
|
||||
</Select>
|
||||
<Select
|
||||
native
|
||||
disabled={hostsOptions.length === 1}
|
||||
label="Host"
|
||||
onChange={updateHost}
|
||||
selected={host}
|
||||
items={hostsOptions}
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
The host where the repository will be created
|
||||
</FormHelperText>
|
||||
@@ -330,24 +353,15 @@ export const RepoUrlPicker = ({
|
||||
required
|
||||
error={rawErrors?.length > 0 && !owner}
|
||||
>
|
||||
<InputLabel htmlFor="ownerInput">Owner Available</InputLabel>
|
||||
<Select
|
||||
native
|
||||
id="ownerInput"
|
||||
onChange={updateOwner}
|
||||
value={owner}
|
||||
>
|
||||
{allowedOwners ? (
|
||||
allowedOwners.map(i => (
|
||||
<option key={i} value={i}>
|
||||
{i}
|
||||
</option>
|
||||
))
|
||||
) : (
|
||||
<p>loading</p>
|
||||
)}
|
||||
;
|
||||
</Select>
|
||||
label="Owner Available"
|
||||
onChange={updateOwnerSelect}
|
||||
disabled={ownersOptions.length === 1}
|
||||
selected={owner}
|
||||
items={ownersOptions}
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
The organization, user or project that this repo will belong to
|
||||
</FormHelperText>
|
||||
|
||||
@@ -18,3 +18,4 @@ export * from './EntityPicker';
|
||||
export * from './OwnerPicker';
|
||||
export * from './RepoUrlPicker';
|
||||
export * from './TextValuePicker';
|
||||
export * from './OwnedEntityPicker';
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
RepoUrlPicker,
|
||||
} from '../components/fields/RepoUrlPicker';
|
||||
import { FieldExtensionOptions } from './types';
|
||||
import { OwnedEntityPicker } from '../components/fields/OwnedEntityPicker';
|
||||
|
||||
export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS: FieldExtensionOptions[] = [
|
||||
{
|
||||
@@ -44,4 +45,8 @@ export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS: FieldExtensionOptions[] = [
|
||||
component: OwnerPicker,
|
||||
name: 'OwnerPicker',
|
||||
},
|
||||
{
|
||||
component: OwnedEntityPicker,
|
||||
name: 'OwnedEntityPicker',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -31,6 +31,7 @@ export {
|
||||
EntityPickerFieldExtension,
|
||||
EntityNamePickerFieldExtension,
|
||||
OwnerPickerFieldExtension,
|
||||
OwnedEntityPickerFieldExtension,
|
||||
RepoUrlPickerFieldExtension,
|
||||
ScaffolderPage,
|
||||
scaffolderPlugin as plugin,
|
||||
@@ -42,6 +43,7 @@ export {
|
||||
OwnerPicker,
|
||||
RepoUrlPicker,
|
||||
TextValuePicker,
|
||||
OwnedEntityPicker,
|
||||
} from './components/fields';
|
||||
export { FavouriteTemplate } from './components/FavouriteTemplate';
|
||||
export { TemplateList } from './components/TemplateList';
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { OwnedEntityPicker } from './components/fields/OwnedEntityPicker';
|
||||
|
||||
export const scaffolderPlugin = createPlugin({
|
||||
id: 'scaffolder',
|
||||
@@ -95,3 +96,10 @@ export const ScaffolderPage = scaffolderPlugin.provide(
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const OwnedEntityPickerFieldExtension = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
component: OwnedEntityPicker,
|
||||
name: 'OwnedEntityPicker',
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user