From 49299ac371f4400ac42bfc83ec4874609f45f06a Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 22 Dec 2021 04:28:45 +0100 Subject: [PATCH] chore: starting the refactor and split of the RepoUrlPicker Signed-off-by: blam --- .../fields/RepoUrlPicker/AzureRepoPicker.tsx | 84 +++++++++ .../RepoUrlPicker/BitbucketRepoPicker.tsx | 80 +++++++++ .../fields/RepoUrlPicker/GithubRepoPicker.tsx | 87 ++++++++++ .../fields/RepoUrlPicker/GitlabRepoPicker.tsx | 87 ++++++++++ .../fields/RepoUrlPicker/NewRepoUrlPicker.tsx | 160 ++++++++++++++++++ .../{Host.tsx => RepoUrlPickerHost.tsx} | 59 ++++--- .../components/fields/RepoUrlPicker/index.ts | 2 +- plugins/scaffolder/src/extensions/index.tsx | 12 +- plugins/scaffolder/src/extensions/types.ts | 7 + 9 files changed, 552 insertions(+), 26 deletions(-) create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/NewRepoUrlPicker.tsx rename plugins/scaffolder/src/components/fields/RepoUrlPicker/{Host.tsx => RepoUrlPickerHost.tsx} (55%) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx new file mode 100644 index 0000000000..df6b105368 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/AzureRepoPicker.tsx @@ -0,0 +1,84 @@ +/* + * 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 FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Input from '@material-ui/core/Input'; +import InputLabel from '@material-ui/core/InputLabel'; + +export const AzureRepoPicker = ({ + onOrgChange, + onOwnerChange, + onRepoNameChange, + rawErrors, + org, + owner, + repoName, +}: { + onOrgChange: (org: string) => void; + onOwnerChange: (owner: string) => void; + onRepoNameChange: (name: string) => void; + owner?: string; + org?: string; + repoName?: string; + rawErrors: string[]; +}) => { + return ( + <> + 0 && !org} + > + Organization + onOrgChange(e.target.value)} + value={org} + /> + + The organization that this repo will belong to + + + 0 && !owner} + > + Owner + onOwnerChange(e.target.value)} + value={owner} + /> + The Owner that this repo will belong to + + 0 && !repoName} + > + Repository + onRepoNameChange(e.target.value)} + value={repoName} + /> + The name of the repository + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx new file mode 100644 index 0000000000..40828e5852 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/BitbucketRepoPicker.tsx @@ -0,0 +1,80 @@ +/* + * 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 FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Input from '@material-ui/core/Input'; +import InputLabel from '@material-ui/core/InputLabel'; + +export const BitbucketRepoPicker = ({ + onProjectChange, + onWorkspaceChange, + onRepoNameChange, + rawErrors, + workspace, + project, + host, + repoName, +}: { + onProjectChange: (owner: string) => void; + onWorkspaceChange: (name: string) => void; + onRepoNameChange: (name: string) => void; + workspace?: string; + project?: string; + repoName?: string; + host: string; + rawErrors: string[]; +}) => { + return ( + <> + 0 && !project && !workspace} + > + + {host === 'bitbucket.org' ? 'Workspace' : 'Project'} + + { + return host === 'bitbucket.org' + ? onWorkspaceChange(e.target.value) + : onProjectChange(e.target.value); + }} + value={host === 'bitbucket.org' ? workspace : project} + /> + + The {host === 'bitbucket.org' ? 'Workspace' : 'Project'}that this repo + will belong to + + + 0 && !repoName} + > + Repository + onRepoNameChange(e.target.value)} + value={repoName} + /> + The name of the repository + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx new file mode 100644 index 0000000000..53126420f3 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GithubRepoPicker.tsx @@ -0,0 +1,87 @@ +/* + * 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 FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Input from '@material-ui/core/Input'; +import InputLabel from '@material-ui/core/InputLabel'; +import { Select, SelectItem } from '@backstage/core-components'; + +export const GithubRepoPicker = ({ + onOwnerChange, + onRepoNameChange, + allowedOwners = [], + rawErrors, + owner, + repoName, +}: { + onOwnerChange: (owner: string) => void; + onRepoNameChange: (name: string) => void; + allowedOwners: string[]; + owner?: string; + repoName?: string; + rawErrors: string[]; +}) => { + const ownerItems: SelectItem[] = allowedOwners + ? allowedOwners.map(i => ({ label: i, value: i })) + : [{ label: 'Loading...', value: 'loading' }]; + + return ( + <> + 0 && !owner} + > + {allowedOwners?.length ? ( + onOwnerChange(e.target.value)} + value={owner} + /> + + )} + + The organization, user or project that this repo will belong to + + + 0 && !repoName} + > + Repository + onRepoNameChange(e.target.value)} + value={repoName} + /> + The name of the repository + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx new file mode 100644 index 0000000000..2aecc76fbc --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx @@ -0,0 +1,87 @@ +/* + * 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 FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Input from '@material-ui/core/Input'; +import InputLabel from '@material-ui/core/InputLabel'; +import { Select, SelectItem } from '@backstage/core-components'; + +export const GitlabRepoPicker = ({ + onOwnerChange, + onRepoNameChange, + allowedOwners = [], + rawErrors, + owner, + repoName, +}: { + onOwnerChange: (owner: string) => void; + onRepoNameChange: (name: string) => void; + allowedOwners: string[]; + owner?: string; + repoName?: string; + rawErrors: string[]; +}) => { + const ownerItems: SelectItem[] = allowedOwners + ? allowedOwners.map(i => ({ label: i, value: i })) + : [{ label: 'Loading...', value: 'loading' }]; + + return ( + <> + 0 && !owner} + > + {allowedOwners?.length ? ( + onOwnerChange(e.target.value)} + value={owner} + /> + + )} + + The organization, user or project that this repo will belong to + + + 0 && !repoName} + > + Repository + onRepoNameChange(e.target.value)} + value={repoName} + /> + The name of the repository + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/NewRepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/NewRepoUrlPicker.tsx new file mode 100644 index 0000000000..50a8101165 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/NewRepoUrlPicker.tsx @@ -0,0 +1,160 @@ +/* + * 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 { useApi } from '@backstage/core-plugin-api'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; +import React, { useEffect, useState } from 'react'; +import { GithubRepoPicker } from './GithubRepoPicker'; +import { GitlabRepoPicker } from './GitlabRepoPicker'; +import { AzureRepoPicker } from './AzureRepoPicker'; +import { BitbucketRepoPicker } from './BitbucketRepoPicker'; +import { CustomFieldExtension } from '../../../extensions'; +import { RepoUrlPickerHost } from './RepoUrlPickerHost'; + +export interface RepoUrlPickerUiOptions { + allowedHosts?: string[]; + allowedOwners?: string[]; +} + +function serializeFormData(data: { + host?: string; + owner?: string; + repo?: string; + organization?: string; + workspace?: string; + project?: string; +}) { + if (!data.host) { + return undefined; + } + + const params = new URLSearchParams(); + if (data.owner) { + params.set('owner', data.owner); + } + if (data.repo) { + params.set('repo', data.repo); + } + if (data.organization) { + params.set('organization', data.organization); + } + if (data.workspace) { + params.set('workspace', data.workspace); + } + if (data.project) { + params.set('project', data.project); + } + + return `${data.host}?${params.toString()}`; +} + +export const RepoUrlPicker = ({ + uiSchema, + onChange, + rawErrors, + formData, +}: CustomFieldExtension) => { + const [state, setState] = useState<{ + host?: string; + owner?: string; + repo?: string; + organization?: string; + workspace?: string; + project?: string; + }>({}); + const integrationApi = useApi(scmIntegrationsApiRef); + + const allowedHosts = uiSchema?.['ui:options']?.allowedHosts ?? []; + const allowedOwners = uiSchema?.['ui:options']?.allowedOwners ?? []; + + useEffect(() => { + onChange(serializeFormData(state)); + }, [state, onChange]); + + return ( + <> + setState({ host })} + rawErrors={rawErrors} + /> + {state.host && integrationApi.byHost(state.host)?.type === 'github' && ( + + setState(prevState => ({ ...prevState, repo })) + } + onOwnerChange={owner => + setState(prevState => ({ ...prevState, owner })) + } + /> + )} + {state.host && integrationApi.byHost(state.host)?.type === 'gitlab' && ( + + setState(prevState => ({ ...prevState, repo })) + } + onOwnerChange={owner => + setState(prevState => ({ ...prevState, owner })) + } + /> + )} + {state.host && + integrationApi.byHost(state.host)?.type === 'bitbucket' && ( + + setState(prevState => ({ ...prevState, repo })) + } + onProjectChange={project => + setState(prevState => ({ ...prevState, project })) + } + onWorkspaceChange={workspace => + setState(prevState => ({ ...prevState, workspace })) + } + /> + )} + {state.host && integrationApi.byHost(state.host)?.type === 'azure' && ( + + setState(prevState => ({ ...prevState, owner })) + } + onRepoNameChange={repo => + setState(prevState => ({ ...prevState, repo })) + } + onOrgChange={org => + setState(prevState => ({ ...prevState, organization: org })) + } + /> + )} + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/Host.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx similarity index 55% rename from plugins/scaffolder/src/components/fields/RepoUrlPicker/Host.tsx rename to plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx index 4d841d8665..8e270610bc 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/Host.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { Progress, Select, @@ -22,16 +22,45 @@ import { } from '@backstage/core-components'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; -import Input from '@material-ui/core/Input'; -import InputLabel from '@material-ui/core/InputLabel'; +import { useApi } from '@backstage/core-plugin-api'; +import { scaffolderApiRef } from '../../../api'; +import { useAsync } from 'react-use'; export const RepoUrlPickerHost = ({ host, - organization, hosts, onChange, rawErrors, +}: { + host?: string; + hosts?: string[]; + onChange: (host: string) => void; + rawErrors: string[]; }) => { + const scaffolderApi = useApi(scaffolderApiRef); + + const { value: integrations, loading } = useAsync(async () => { + return await scaffolderApi.getIntegrationsList({ + allowedHosts: hosts ?? [], + }); + }); + + useEffect(() => { + if (hosts && !host) { + onChange(hosts[0]); + } + }, [hosts, host, onChange]); + + const hostsOptions: SelectItem[] = integrations + ? integrations + .filter(i => hosts?.includes(i.host)) + .map(i => ({ label: i.title, value: i.host })) + : [{ label: 'Loading...', value: 'loading' }]; + + if (loading) { + return ; + } + return ( <> - The name of the organization - - )} ); }; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts index 7e14d6d62b..99bcff332b 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { RepoUrlPicker } from './RepoUrlPicker'; +export { RepoUrlPicker } from './NewRepoUrlPicker'; export { repoPickerValidation } from './validation'; diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index 131cd105da..9d5cf4f0b4 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -15,7 +15,11 @@ */ import React from 'react'; -import { CustomFieldValidator, FieldExtensionOptions } from './types'; +import { + CustomFieldValidator, + FieldExtensionOptions, + CustomFieldExtension, +} from './types'; import { Extension, attachComponentData } from '@backstage/core-plugin-api'; export const FIELD_EXTENSION_WRAPPER_KEY = 'scaffolder.extensions.wrapper.v1'; @@ -46,6 +50,10 @@ attachComponentData( true, ); -export type { CustomFieldValidator, FieldExtensionOptions }; +export type { + CustomFieldValidator, + FieldExtensionOptions, + CustomFieldExtension, +}; export { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default'; diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index 644897f7f4..12339f5be7 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -29,3 +29,10 @@ export type FieldExtensionOptions = { component: (props: FieldProps) => JSX.Element | null; validation?: CustomFieldValidator; }; + +export interface CustomFieldExtension + extends FieldProps { + uiSchema: { + 'ui:options'?: UiOptions; + }; +}