chore: think i've finished the refactor of this bloody component now

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2021-12-22 04:50:54 +01:00
parent 2632ae4c2f
commit a9412268a9
4 changed files with 164 additions and 507 deletions
@@ -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<string, RepoUrlPickerUiOptions>) => {
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 (
<>
<RepoUrlPickerHost
host={state.host}
hosts={allowedHosts}
onChange={host => setState({ host })}
rawErrors={rawErrors}
/>
{state.host && integrationApi.byHost(state.host)?.type === 'github' && (
<GithubRepoPicker
allowedOwners={allowedOwners}
rawErrors={rawErrors}
owner={state.owner}
repoName={state.repo}
onRepoNameChange={repo =>
setState(prevState => ({ ...prevState, repo }))
}
onOwnerChange={owner =>
setState(prevState => ({ ...prevState, owner }))
}
/>
)}
{state.host && integrationApi.byHost(state.host)?.type === 'gitlab' && (
<GitlabRepoPicker
allowedOwners={allowedOwners}
rawErrors={rawErrors}
owner={state.owner}
repoName={state.repo}
onRepoNameChange={repo =>
setState(prevState => ({ ...prevState, repo }))
}
onOwnerChange={owner =>
setState(prevState => ({ ...prevState, owner }))
}
/>
)}
{state.host &&
integrationApi.byHost(state.host)?.type === 'bitbucket' && (
<BitbucketRepoPicker
rawErrors={rawErrors}
host={state.host}
project={state.project}
workspace={state.workspace}
repoName={state.repo}
onRepoNameChange={repo =>
setState(prevState => ({ ...prevState, repo }))
}
onProjectChange={project =>
setState(prevState => ({ ...prevState, project }))
}
onWorkspaceChange={workspace =>
setState(prevState => ({ ...prevState, workspace }))
}
/>
)}
{state.host && integrationApi.byHost(state.host)?.type === 'azure' && (
<AzureRepoPicker
rawErrors={rawErrors}
org={state.organization}
repoName={state.repo}
owner={state.owner}
onOwnerChange={owner =>
setState(prevState => ({ ...prevState, owner }))
}
onRepoNameChange={repo =>
setState(prevState => ({ ...prevState, repo }))
}
onOrgChange={org =>
setState(prevState => ({ ...prevState, organization: org }))
}
/>
)}
</>
);
};
@@ -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<string>) => {
const scaffolderApi = useApi(scaffolderApiRef);
}: CustomFieldExtension<string, RepoUrlPickerUiOptions>) => {
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 <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' }];
onChange(serializeFormData(state));
}, [state, onChange]);
return (
<>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !host}
>
<Select
native
disabled={hostsOptions.length === 1}
label="Host"
onChange={updateHost}
selected={host}
items={hostsOptions}
<RepoUrlPickerHost
host={state.host}
hosts={allowedHosts}
onChange={host => setState({ host })}
rawErrors={rawErrors}
/>
{state.host && integrationApi.byHost(state.host)?.type === 'github' && (
<GithubRepoPicker
allowedOwners={allowedOwners}
rawErrors={rawErrors}
owner={state.owner}
repoName={state.repo}
onRepoNameChange={repo =>
setState(prevState => ({ ...prevState, repo }))
}
onOwnerChange={owner =>
setState(prevState => ({ ...prevState, owner }))
}
/>
<FormHelperText>
The host where the repository will be created
</FormHelperText>
</FormControl>
{/* Show this for dev.azure.com only */}
{host === 'dev.azure.com' && (
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !organization}
>
<InputLabel htmlFor="repoInput">Organization</InputLabel>
<Input
id="repoInput"
onChange={updateOrganization}
value={organization}
)}
{state.host && integrationApi.byHost(state.host)?.type === 'gitlab' && (
<GitlabRepoPicker
allowedOwners={allowedOwners}
rawErrors={rawErrors}
owner={state.owner}
repoName={state.repo}
onRepoNameChange={repo =>
setState(prevState => ({ ...prevState, repo }))
}
onOwnerChange={owner =>
setState(prevState => ({ ...prevState, owner }))
}
/>
)}
{state.host &&
integrationApi.byHost(state.host)?.type === 'bitbucket' && (
<BitbucketRepoPicker
rawErrors={rawErrors}
host={state.host}
project={state.project}
workspace={state.workspace}
repoName={state.repo}
onRepoNameChange={repo =>
setState(prevState => ({ ...prevState, repo }))
}
onProjectChange={project =>
setState(prevState => ({ ...prevState, project }))
}
onWorkspaceChange={workspace =>
setState(prevState => ({ ...prevState, workspace }))
}
/>
<FormHelperText>The name of the organization</FormHelperText>
</FormControl>
)}
{host && integrationApi.byHost(host)?.type === 'bitbucket' && (
<>
{/* Show this for bitbucket.org only */}
{host === 'bitbucket.org' && (
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !workspace}
>
<InputLabel htmlFor="wokrspaceInput">Workspace</InputLabel>
<Input
id="wokrspaceInput"
onChange={updateWorkspace}
value={workspace}
/>
<FormHelperText>
The workspace where the repository will be created
</FormHelperText>
</FormControl>
)}
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !project}
>
<InputLabel htmlFor="wokrspaceInput">Project</InputLabel>
<Input
id="wokrspaceInput"
onChange={updateProject}
value={project}
/>
<FormHelperText>
The project where the repository will be created
</FormHelperText>
</FormControl>
</>
)}
{/* Show this for all hosts except bitbucket */}
{host &&
integrationApi.byHost(host)?.type !== 'bitbucket' &&
!allowedOwners && (
<>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !owner}
>
<InputLabel htmlFor="ownerInput">Owner</InputLabel>
<Input id="ownerInput" onChange={updateOwner} value={owner} />
<FormHelperText>
The organization, user or project that this repo will belong to
</FormHelperText>
</FormControl>
</>
)}
{/* Show this for all hosts except bitbucket where allowed owner is set */}
{host &&
integrationApi.byHost(host)?.type !== 'bitbucket' &&
allowedOwners && (
<>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !owner}
>
<Select
native
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>
</FormControl>
</>
)}
{/* Show this for all hosts */}
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !repo}
>
<InputLabel htmlFor="repoInput">Repository</InputLabel>
<Input id="repoInput" onChange={updateRepo} value={repo} />
<FormHelperText>The name of the repository</FormHelperText>
</FormControl>
{state.host && integrationApi.byHost(state.host)?.type === 'azure' && (
<AzureRepoPicker
rawErrors={rawErrors}
org={state.organization}
repoName={state.repo}
owner={state.owner}
onOwnerChange={owner =>
setState(prevState => ({ ...prevState, owner }))
}
onRepoNameChange={repo =>
setState(prevState => ({ ...prevState, repo }))
}
onOrgChange={org =>
setState(prevState => ({ ...prevState, organization: org }))
}
/>
)}
</>
);
};
@@ -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';
@@ -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 };
}