From 12771f5c3462beb7ec55e9693504808c40df25cb Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 24 Feb 2021 14:47:03 +0100 Subject: [PATCH] feat: added some nice validation and functionality to the repo picker Co-authored-by: Patrik Oldsberg Co-authored-by: Johan Haals Signed-off-by: Johan Haals --- .../test-template-v2/template.yaml | 42 ++--- .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 160 ++++++++++++++++++ .../components/fields/RepoUrlPicker/index.ts | 1 + .../components/fields/RepoUrlPicker/index.tsx | 103 ----------- 4 files changed, 184 insertions(+), 122 deletions(-) create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts delete mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/index.tsx diff --git a/plugins/scaffolder-backend/sample-templates/test-template-v2/template.yaml b/plugins/scaffolder-backend/sample-templates/test-template-v2/template.yaml index 08432e6502..6b9e2142d9 100644 --- a/plugins/scaffolder-backend/sample-templates/test-template-v2/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/test-template-v2/template.yaml @@ -82,25 +82,29 @@ spec: type: service parameters: - title: Fill in some BS params - required: - - name - properties: - name: - title: Name - type: string - description: Unique name of the component - ui:autofocus: true - ui:options: - rows: 5 - repoUrl: - title: Repository Location - type: string - description: Copy https://github.com/aeothgiaetgh/aetkijhahte and hammer in some more letters - ui:field: RepoUrlPicker - ui:options: - allowedHosts: - - github.com + - title: Fill in some parameters for the template + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + ui:options: + rows: 5 + - title: Choose destination + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com + steps: - id: fetch-base diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx new file mode 100644 index 0000000000..49c4213251 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -0,0 +1,160 @@ +/* + * Copyright 2021 Spotify AB + * + * 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, { useState, useCallback, useEffect } from 'react'; +import { Field } from '@rjsf/core'; +import { useApi, Progress } from '@backstage/core'; +import { scaffolderApiRef } from '../../../api'; +import { useAsync } from 'react-use'; +import Select from '@material-ui/core/Select'; +import InputLabel from '@material-ui/core/InputLabel'; +import Input from '@material-ui/core/Input'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; + +function splitFormData(url: string | undefined) { + let host = undefined; + let owner = undefined; + let repo = 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; + } + } catch { + /* ok */ + } + + return { host, owner, repo }; +} + +function serializeFormData(data: { + host?: string; + owner?: string; + repo?: 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); + } + + return `${data.host}?${params.toString()}`; +} + +export const RepoUrlPicker: Field = ({ + onChange, + uiSchema, + rawErrors, + formData, +}) => { + const api = useApi(scaffolderApiRef); + // const allowedHosts = uiSchema['ui:options']?.allowedHosts as string[]; + const allowedHosts = React.useMemo( + () => ['github.com', 'gitlab.com'] as string[], + [], + ); + + const { value: integrations, loading } = useAsync(async () => { + return await api.getIntegrationsList({ allowedHosts }); + }); + + const { host, owner, repo } = splitFormData(formData); + const updateHost = useCallback( + (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => + onChange( + serializeFormData({ host: evt.target.value as string, owner, repo }), + ), + [onChange, owner, repo], + ); + + const updateOwner = useCallback( + (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => + onChange( + serializeFormData({ host, owner: evt.target.value as string, repo }), + ), + [onChange, host, repo], + ); + + const updateRepo = useCallback( + (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => + onChange( + serializeFormData({ host, owner, repo: evt.target.value as string }), + ), + [onChange, host, owner], + ); + + useEffect(() => { + if (host === undefined && integrations?.length) { + onChange(serializeFormData({ host: integrations[0].host, owner, repo })); + } + }, [integrations, host]); + + if (loading) { + return ; + } + + return ( + <> + 0 && !host} + > + Host + + + The host where the repository will be created + + + 0 && !owner} + > + Owner + + + The organization, user or project that this repo will belong to + + + 0 && !repo} + > + Repository + + The name of the repository + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts new file mode 100644 index 0000000000..1f7f9e0327 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.ts @@ -0,0 +1 @@ +export { RepoUrlPicker } from './RepoUrlPicker'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.tsx deleted file mode 100644 index a20f748d3a..0000000000 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/index.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * 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, { useState, useCallback, useEffect } from 'react'; -import { Field } from '@rjsf/core'; -import { useApi, Progress } from '@backstage/core'; -import { scaffolderApiRef } from '../../../api'; -import { useAsync } from 'react-use'; -import Select from '@material-ui/core/Select'; -import InputLabel from '@material-ui/core/InputLabel'; -import Input from '@material-ui/core/Input'; -import FormControl from '@material-ui/core/FormControl'; -import { Typography } from '@material-ui/core'; -import { rest } from 'msw/lib/types'; - -export const RepoUrlPicker: Field = ({ onChange, uiSchema, ...rest }) => { - const api = useApi(scaffolderApiRef); - const allowedHosts = uiSchema['ui:options']?.allowedHosts as string[]; - - const { value: integrations, loading } = useAsync(async () => { - return await api.getIntegrationsList({ allowedHosts }); - }); - - console.log(rest); - - const [host, setHost] = useState(''); - const [owner, setOwner] = useState(''); - const [repo, setRepo] = useState(''); - - const updateHost = useCallback( - (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => - setHost(evt.target.value as string), - [setHost], - ); - - const updateOwner = useCallback( - (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => - setOwner(evt.target.value as string), - [setOwner], - ); - - const updateRepo = useCallback( - (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => - setRepo(evt.target.value as string), - [setRepo], - ); - - useEffect(() => { - if (host === '' && integrations?.length) { - setHost(integrations[0].host); - } - }, [integrations, host]); - - useEffect(() => { - const params = new URLSearchParams(); - params.set('owner', owner); - params.set('repo', repo); - - onChange(`${encodeURIComponent(host)}?${params.toString()}`); - }, [host, owner, repo, onChange]); - - if (loading) { - return ; - } - - return ( - <> - Repository Location - - Host - - - - Owner - - - - Repository - - - - ); -};