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 ? (
+
+ 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 ? (
+
+ 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 (
<>
- {/* Show this for dev.azure.com only */}
- {host === 'dev.azure.com' && (
- 0 && !organization}
- >
- Organization
-
- 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;
+ };
+}