feat: added some nice validation and functionality to the repo picker

Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Johan Haals <johan.haals@gmail.com>
Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
blam
2021-02-24 14:47:03 +01:00
committed by Johan Haals
parent 2f404bd69d
commit 12771f5c34
4 changed files with 184 additions and 122 deletions
@@ -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
@@ -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 <Progress />;
}
return (
<>
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !host}
>
<InputLabel htmlFor="hostInput">Host</InputLabel>
<Select native id="hostInput" onChange={updateHost} value={host}>
{integrations!
.filter(i => allowedHosts?.includes(i.host))
.map(({ host, title }) => (
<option key={host} value={host}>
{title}
</option>
))}
</Select>
<FormHelperText>
The host where the repository will be created
</FormHelperText>
</FormControl>
<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>
<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>
</>
);
};
@@ -0,0 +1 @@
export { RepoUrlPicker } from './RepoUrlPicker';
@@ -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 <Progress />;
}
return (
<>
<Typography variant="body1">Repository Location</Typography>
<FormControl margin="normal" required>
<InputLabel htmlFor="hostInput">Host</InputLabel>
<Select native id="hostInput" onChange={updateHost}>
{integrations!
.filter(i => allowedHosts?.includes(i.host))
.map(({ host, title }) => (
<option key={host} value={host}>
{title}
</option>
))}
</Select>
</FormControl>
<FormControl margin="normal" required>
<InputLabel htmlFor="ownerInput">Owner</InputLabel>
<Input id="ownerInput" onBlur={updateOwner} />
</FormControl>
<FormControl margin="normal" required>
<InputLabel htmlFor="repoInput">Repository</InputLabel>
<Input id="repoInput" onBlur={updateRepo} />
</FormControl>
</>
);
};