diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/NewRepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/NewRepoUrlPicker.tsx deleted file mode 100644 index 50a8101165..0000000000 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/NewRepoUrlPicker.tsx +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 85fe992935..ba85bdaeb1 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -13,371 +13,117 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - Progress, - Select, - SelectedItems, - SelectItem, -} from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-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 { FieldProps } from '@rjsf/core'; -import React, { useCallback, useEffect } from 'react'; -import useAsync from 'react-use/lib/useAsync'; -import { scaffolderApiRef } from '../../../api'; +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'; +import { splitFormData, serializeFormData } from './utils'; -function splitFormData(url: string | undefined, allowedOwners?: string[]) { - let host = undefined; - let owner = undefined; - let repo = undefined; - let organization = undefined; - let workspace = undefined; - let project = undefined; - - try { - if (url) { - const parsed = new URL(`https://${url}`); - host = parsed.host; - owner = parsed.searchParams.get('owner') || allowedOwners?.[0]; - repo = parsed.searchParams.get('repo') || undefined; - // This is azure dev ops specific. not used for any other provider. - organization = parsed.searchParams.get('organization') || undefined; - // These are bitbucket specific, not used for any other provider. - workspace = parsed.searchParams.get('workspace') || undefined; - project = parsed.searchParams.get('project') || undefined; - } - } catch { - /* ok */ - } - - return { host, owner, repo, organization, workspace, project }; -} - -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 interface RepoUrlPickerUiOptions { + allowedHosts?: string[]; + allowedOwners?: string[]; } export const RepoUrlPicker = ({ - onChange, uiSchema, + onChange, rawErrors, formData, -}: FieldProps) => { - const scaffolderApi = useApi(scaffolderApiRef); +}: CustomFieldExtension) => { + const [state, setState] = useState<{ + host?: string; + owner?: string; + repo?: string; + organization?: string; + workspace?: string; + project?: string; + }>(splitFormData(formData)); const integrationApi = useApi(scmIntegrationsApiRef); - const allowedHosts = uiSchema['ui:options']?.allowedHosts as string[]; - const allowedOwners = uiSchema['ui:options']?.allowedOwners as string[]; - const { value: integrations, loading } = useAsync(async () => { - return await scaffolderApi.getIntegrationsList({ allowedHosts }); - }); - - const { host, owner, repo, organization, workspace, project } = splitFormData( - formData, - allowedOwners, - ); - const updateHost = useCallback( - (value: SelectedItems) => { - onChange( - serializeFormData({ - host: value as string, - owner, - repo, - organization, - workspace, - project, - }), - ); - }, - [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( - serializeFormData({ - host, - owner: evt.target.value as string, - repo, - organization, - workspace, - project, - }), - ), - [onChange, host, repo, organization, workspace, project], - ); - - const updateRepo = useCallback( - (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => - onChange( - serializeFormData({ - host, - owner, - repo: evt.target.value as string, - organization, - workspace, - project, - }), - ), - [onChange, host, owner, organization, workspace, project], - ); - - const updateOrganization = useCallback( - (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => - onChange( - serializeFormData({ - host, - owner, - repo, - organization: evt.target.value as string, - workspace, - project, - }), - ), - [onChange, host, owner, repo, workspace, project], - ); - - const updateWorkspace = useCallback( - (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => - onChange( - serializeFormData({ - host, - owner, - repo, - organization, - workspace: evt.target.value as string, - project, - }), - ), - [onChange, host, owner, repo, organization, project], - ); - - const updateProject = useCallback( - (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => - onChange( - serializeFormData({ - host, - owner, - repo, - organization, - workspace, - project: evt.target.value as string, - }), - ), - [onChange, host, owner, repo, organization, workspace], - ); + const allowedHosts = uiSchema?.['ui:options']?.allowedHosts ?? []; + const allowedOwners = uiSchema?.['ui:options']?.allowedOwners ?? []; useEffect(() => { - if (host === undefined && integrations?.length) { - onChange( - serializeFormData({ - host: integrations[0].host, - owner, - repo, - organization, - workspace, - project, - }), - ); - } - }, [ - onChange, - integrations, - host, - owner, - repo, - organization, - workspace, - project, - ]); - - if (loading) { - return ; - } - - 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' }]; + onChange(serializeFormData(state)); + }, [state, onChange]); return ( <> - 0 && !host} - > - + 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 })) + } /> - The name of the organization - - )} - {host && integrationApi.byHost(host)?.type === 'bitbucket' && ( - <> - {/* Show this for bitbucket.org only */} - {host === 'bitbucket.org' && ( - 0 && !workspace} - > - Workspace - - - The workspace where the repository will be created - - - )} - 0 && !project} - > - Project - - - The project where the repository will be created - - - - )} - {/* Show this for all hosts except bitbucket */} - {host && - integrationApi.byHost(host)?.type !== 'bitbucket' && - !allowedOwners && ( - <> - 0 && !owner} - > - Owner - - - The organization, user or project that this repo will belong to - - - )} - {/* Show this for all hosts except bitbucket where allowed owner is set */} - {host && - integrationApi.byHost(host)?.type !== 'bitbucket' && - allowedOwners && ( - <> - 0 && !owner} - > - - The name of the repository - + {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/index.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts index 99bcff332b..7e14d6d62b 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 './NewRepoUrlPicker'; +export { RepoUrlPicker } from './RepoUrlPicker'; export { repoPickerValidation } from './validation'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/utils.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/utils.ts new file mode 100644 index 0000000000..8ef109f4c8 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/utils.ts @@ -0,0 +1,71 @@ +/* + * 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 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 function splitFormData(url: string | undefined) { + let host = undefined; + let owner = undefined; + let repo = undefined; + let organization = undefined; + let workspace = undefined; + let project = undefined; + + try { + if (url) { + const parsed = new URL(`https://${url}`); + host = parsed.host; + owner = parsed.searchParams.get('owner') || undefined; + repo = parsed.searchParams.get('repo') || undefined; + organization = parsed.searchParams.get('organization') || undefined; + workspace = parsed.searchParams.get('workspace') || undefined; + project = parsed.searchParams.get('project') || undefined; + } + } catch { + /* ok */ + } + + return { host, owner, repo, organization, workspace, project }; +}