chore: starting the refactor and split of the RepoUrlPicker

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2021-12-22 04:28:45 +01:00
parent 0ca7eacd64
commit 49299ac371
9 changed files with 552 additions and 26 deletions
@@ -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 (
<>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !org}
>
<InputLabel htmlFor="orgInput">Organization</InputLabel>
<Input
id="orgInput"
onChange={e => onOrgChange(e.target.value)}
value={org}
/>
<FormHelperText>
The organization that this repo will belong to
</FormHelperText>
</FormControl>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !owner}
>
<InputLabel htmlFor="ownerInput">Owner</InputLabel>
<Input
id="ownerInput"
onChange={e => onOwnerChange(e.target.value)}
value={owner}
/>
<FormHelperText>The Owner that this repo will belong to</FormHelperText>
</FormControl>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !repoName}
>
<InputLabel htmlFor="repoInput">Repository</InputLabel>
<Input
id="repoInput"
onChange={e => onRepoNameChange(e.target.value)}
value={repoName}
/>
<FormHelperText>The name of the repository</FormHelperText>
</FormControl>
</>
);
};
@@ -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 (
<>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !project && !workspace}
>
<InputLabel htmlFor="ownerInput">
{host === 'bitbucket.org' ? 'Workspace' : 'Project'}
</InputLabel>
<Input
id="ownerInput"
onChange={e => {
return host === 'bitbucket.org'
? onWorkspaceChange(e.target.value)
: onProjectChange(e.target.value);
}}
value={host === 'bitbucket.org' ? workspace : project}
/>
<FormHelperText>
The {host === 'bitbucket.org' ? 'Workspace' : 'Project'}that this repo
will belong to
</FormHelperText>
</FormControl>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !repoName}
>
<InputLabel htmlFor="repoInput">Repository</InputLabel>
<Input
id="repoInput"
onChange={e => onRepoNameChange(e.target.value)}
value={repoName}
/>
<FormHelperText>The name of the repository</FormHelperText>
</FormControl>
</>
);
};
@@ -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 (
<>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !owner}
>
{allowedOwners?.length ? (
<Select
native
label="Owner Available"
onChange={s => onOwnerChange(String(Array.isArray(s) ? s[0] : s))}
disabled={allowedOwners.length === 1}
selected={owner}
items={ownerItems}
/>
) : (
<>
<InputLabel htmlFor="ownerInput">Owner</InputLabel>
<Input
id="ownerInput"
onChange={e => onOwnerChange(e.target.value)}
value={owner}
/>
</>
)}
<FormHelperText>
The organization, user or project that this repo will belong to
</FormHelperText>
</FormControl>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !repoName}
>
<InputLabel htmlFor="repoInput">Repository</InputLabel>
<Input
id="repoInput"
onChange={e => onRepoNameChange(e.target.value)}
value={repoName}
/>
<FormHelperText>The name of the repository</FormHelperText>
</FormControl>
</>
);
};
@@ -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 (
<>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !owner}
>
{allowedOwners?.length ? (
<Select
native
label="Owner Available"
onChange={s => onOwnerChange(String(Array.isArray(s) ? s[0] : s))}
disabled={allowedOwners.length === 1}
selected={owner}
items={ownerItems}
/>
) : (
<>
<InputLabel htmlFor="ownerInput">Owner</InputLabel>
<Input
id="ownerInput"
onChange={e => onOwnerChange(e.target.value)}
value={owner}
/>
</>
)}
<FormHelperText>
The organization, user or project that this repo will belong to
</FormHelperText>
</FormControl>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !repoName}
>
<InputLabel htmlFor="repoInput">Repository</InputLabel>
<Input
id="repoInput"
onChange={e => onRepoNameChange(e.target.value)}
value={repoName}
/>
<FormHelperText>The name of the repository</FormHelperText>
</FormControl>
</>
);
};
@@ -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<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,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 <Progress />;
}
return (
<>
<FormControl
@@ -41,33 +70,17 @@ export const RepoUrlPickerHost = ({
>
<Select
native
disabled={hosts.length === 1}
disabled={hosts?.length === 1 ?? false}
label="Host"
onChange={onChange}
onChange={s => onChange(String(Array.isArray(s) ? s[0] : s))}
selected={host}
items={hosts}
items={hostsOptions}
/>
<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}
/>
<FormHelperText>The name of the organization</FormHelperText>
</FormControl>
)}
</>
);
};
@@ -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';
+10 -2
View File
@@ -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';
@@ -29,3 +29,10 @@ export type FieldExtensionOptions<T = any> = {
component: (props: FieldProps<T>) => JSX.Element | null;
validation?: CustomFieldValidator<T>;
};
export interface CustomFieldExtension<ReturnValue, UiOptions extends {} = {}>
extends FieldProps<ReturnValue> {
uiSchema: {
'ui:options'?: UiOptions;
};
}