Merge branch 'master' into scaffolder/EntityPicker-arbitrary-values

This commit is contained in:
Jason Nguyen
2021-12-21 17:50:16 -07:00
committed by GitHub
539 changed files with 9860 additions and 3498 deletions
+18
View File
@@ -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)
+5 -5
View File
@@ -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",
-1
View File
@@ -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 = {
@@ -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',
},
];
+2
View File
@@ -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';
+8
View File
@@ -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',
}),
);