Create import-component frontend plugin
This commit is contained in:
@@ -16,12 +16,5 @@
|
||||
|
||||
export { HigherOrderOperations } from './HigherOrderOperations';
|
||||
export { LocationReaders } from './LocationReaders';
|
||||
export type {
|
||||
AddLocationResult,
|
||||
HigherOrderOperation,
|
||||
LocationReader,
|
||||
ReadLocationEntity,
|
||||
ReadLocationError,
|
||||
ReadLocationResult,
|
||||
} from './types';
|
||||
export * from './types';
|
||||
export * from './processors';
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
# Register component plugin
|
||||
|
||||
Welcome to the import-component plugin!
|
||||
|
||||
This plugin allows you to create a component-config YAML file for your repository.
|
||||
|
||||
When installed it is accessible on [localhost:3000/import-component](localhost:3000/import-component).
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 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 { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(plugin)
|
||||
.render();
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@backstage/plugin-import-component",
|
||||
"version": "0.1.1-alpha.26",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"start": "backstage-cli plugin:serve",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"diff": "backstage-cli plugin:diff",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.1.1-alpha.26",
|
||||
"@backstage/core": "^0.1.1-alpha.26",
|
||||
"@backstage/plugin-catalog": "^0.1.1-alpha.26",
|
||||
"@backstage/plugin-catalog-backend": "^0.1.1-alpha.26",
|
||||
"@backstage/theme": "^0.1.1-alpha.26",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@octokit/rest": "^18.0.6",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hook-form": "^6.6.0",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"react-use": "^15.3.3",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.26",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.26",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.26",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
"@testing-library/user-event": "^12.0.7",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^12.0.0",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"msw": "^0.21.2"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2020 Roadie 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 { createApiRef } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RecursivePartial } from '../util/types';
|
||||
|
||||
export const catalogImportApiRef = createApiRef<CatalogImportApi>({
|
||||
id: 'plugin.catalogimport.service',
|
||||
description: 'Used by the catalog import plugin to make requests',
|
||||
});
|
||||
|
||||
export interface CatalogImportApi {
|
||||
submitPRToRepo(options: {
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
fileContent: string;
|
||||
}): Promise<{ errorMessage: string | null; link: string }>;
|
||||
createRepositoryLocation(options: {
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
}): Promise<{ errorMessage: string | null }>;
|
||||
generateEntityDefinitions(options: {
|
||||
repo: string;
|
||||
}): Promise<RecursivePartial<Entity>[]>;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2020 Roadie 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 { Octokit } from '@octokit/rest';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { CatalogImportApi } from './CatalogImportApi';
|
||||
import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend';
|
||||
import { RecursivePartial } from '../util/types';
|
||||
|
||||
export const API_BASE_URL = '/api/catalog/locations';
|
||||
|
||||
export class CatalogImportClient implements CatalogImportApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
|
||||
constructor(options: { discoveryApi: DiscoveryApi }) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
}
|
||||
|
||||
async generateEntityDefinitions({
|
||||
repo,
|
||||
}: {
|
||||
repo: string;
|
||||
}): Promise<RecursivePartial<Entity>[]> {
|
||||
const response = await fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
location: { type: 'github', target: repo },
|
||||
}),
|
||||
},
|
||||
);
|
||||
const payload = (await response.json()) as AnalyzeLocationResponse;
|
||||
return payload.generateEntities.map(x => x.entity);
|
||||
}
|
||||
|
||||
async createRepositoryLocation({
|
||||
owner,
|
||||
repo,
|
||||
}: {
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
}): Promise<{ errorMessage: string | null }> {
|
||||
await fetch(`${await this.discoveryApi.getBaseUrl('catalog')}/locations`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
type: 'github',
|
||||
target: `https://github.com/${owner}/${repo}/blob/master/catalog-info.yaml`,
|
||||
presence: 'optional',
|
||||
}),
|
||||
});
|
||||
return { errorMessage: null };
|
||||
}
|
||||
|
||||
async submitPRToRepo({
|
||||
token,
|
||||
owner,
|
||||
repo,
|
||||
fileContent,
|
||||
}: {
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
fileContent: string;
|
||||
}): Promise<{ errorMessage: string | null; link: string }> {
|
||||
const octo = new Octokit({
|
||||
auth: token,
|
||||
});
|
||||
|
||||
const parentRef = await octo.git.getRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: 'heads/master',
|
||||
});
|
||||
await octo.git.createRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: 'refs/heads/backstage-integration',
|
||||
sha: parentRef.data.object.sha,
|
||||
});
|
||||
await octo.repos.createOrUpdateFileContents({
|
||||
owner,
|
||||
repo,
|
||||
path: 'backstage.yaml',
|
||||
message: 'Add backstage.yaml config file',
|
||||
content: btoa(fileContent),
|
||||
branch: 'backstage-integration',
|
||||
});
|
||||
const pullRequestRespone = await octo.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
title: 'Add backstage.yaml config file',
|
||||
head: 'backstage-integration',
|
||||
base: 'master',
|
||||
});
|
||||
|
||||
return { errorMessage: null, link: pullRequestRespone.data.html_url };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2020 Roadie 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.
|
||||
*/
|
||||
|
||||
export * from './CatalogImportApi';
|
||||
export * from './CatalogImportClient';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2020 Roadie 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.
|
||||
*/
|
||||
|
||||
export type GithubRepoDto = {
|
||||
full_name: string;
|
||||
private: boolean;
|
||||
description: string;
|
||||
html_url: string;
|
||||
language: string;
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2020 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 { Entity } from '@backstage/catalog-model';
|
||||
import { errorApiRef, useApi } from '@backstage/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormHelperText,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
TextField,
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useMountedState } from 'react-use';
|
||||
import { RecursivePartial } from '../../util/types';
|
||||
import { ComponentIdValidators } from '../../util/validate';
|
||||
import { useGithubRepos } from '../ImportComponentPage/useGithubRepos';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
form: {
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
flexFlow: 'column nowrap',
|
||||
},
|
||||
submit: {
|
||||
marginTop: theme.spacing(1),
|
||||
},
|
||||
select: {
|
||||
minWidth: 120,
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
nextStep: () => void;
|
||||
saveConfig: (configFile: {
|
||||
repo: string;
|
||||
config: RecursivePartial<Entity>[];
|
||||
}) => void;
|
||||
};
|
||||
|
||||
export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => {
|
||||
const { control, register, handleSubmit, errors, formState } = useForm({
|
||||
mode: 'onChange',
|
||||
});
|
||||
const classes = useStyles();
|
||||
const hasErrors = !!errors.componentLocation;
|
||||
const dirty = formState?.isDirty;
|
||||
|
||||
const isMounted = useMountedState();
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const { generateEntityDefinitions } = useGithubRepos();
|
||||
|
||||
const onSubmit = async (formData: Record<string, string>) => {
|
||||
const { componentLocation: target } = formData;
|
||||
try {
|
||||
// const typeMapping = [
|
||||
// { url: /^https:\/\/gitlab\.com\/.*/, type: 'gitlab/api' },
|
||||
// { url: /^https:\/\/bitbucket\.org\/.*/, type: 'bitbucket/api' },
|
||||
// { url: /^https:\/\/dev\.azure\.com\/.*/, type: 'azure/api' },
|
||||
// { url: /.*/, type: 'github' },
|
||||
// ];
|
||||
|
||||
// const type =
|
||||
// scmType === 'AUTO'
|
||||
// ? typeMapping.filter(item => item.url.test(target))[0].type
|
||||
// : scmType;
|
||||
|
||||
if (!isMounted()) return;
|
||||
|
||||
const repo = target
|
||||
.split('/')
|
||||
.slice(-2)
|
||||
.join('/');
|
||||
|
||||
const config = await generateEntityDefinitions(repo);
|
||||
saveConfig({
|
||||
repo,
|
||||
config,
|
||||
});
|
||||
nextStep();
|
||||
} catch (e) {
|
||||
errorApi.post(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className={classes.form}
|
||||
data-testid="register-form"
|
||||
>
|
||||
<FormControl>
|
||||
<TextField
|
||||
id="registerComponentInput"
|
||||
variant="outlined"
|
||||
label="Repository URL"
|
||||
data-testid="componentLocationInput"
|
||||
error={hasErrors}
|
||||
placeholder="https://github.com/spotify/backstage"
|
||||
name="componentLocation"
|
||||
required
|
||||
margin="normal"
|
||||
helperText="Enter the full path to the repository in GitHub, GitLab, Bitbucket or Azure to start tracking your component."
|
||||
inputRef={register({
|
||||
required: true,
|
||||
validate: ComponentIdValidators,
|
||||
})}
|
||||
/>
|
||||
|
||||
{errors.componentLocation && (
|
||||
<FormHelperText error={hasErrors} id="register-component-helper-text">
|
||||
{errors.componentLocation.message}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
<FormControl variant="outlined" className={classes.select}>
|
||||
<InputLabel id="scmLabel">Host type</InputLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name="scmType"
|
||||
defaultValue="AUTO"
|
||||
render={({ onChange, onBlur, value }) => (
|
||||
<Select
|
||||
labelId="scmLabel"
|
||||
id="scmSelect"
|
||||
label="scmLabel"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
>
|
||||
<MenuItem value="AUTO">Auto-detect</MenuItem>
|
||||
<MenuItem value="gitlab">GitLab</MenuItem>
|
||||
<MenuItem value="bitbucket/api">Bitbucket</MenuItem>
|
||||
<MenuItem value="azure/api">Azure</MenuItem>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={!dirty || hasErrors}
|
||||
className={classes.submit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
export * from './ImportComponentForm';
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2020 RoadieHQ
|
||||
*
|
||||
* 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, { useCallback, useState } from 'react';
|
||||
import { Button, CircularProgress, Grid, Tooltip } from '@material-ui/core';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { useGithubRepos } from './useGithubRepos';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { RecursivePartial } from '../../util/types';
|
||||
|
||||
type Props = {
|
||||
nextStep: () => void;
|
||||
configFile: { repo: string; config: RecursivePartial<Entity>[] };
|
||||
savePRLink: (PRLink: string) => void;
|
||||
};
|
||||
|
||||
const ComponentConfigDisplay: React.FC<Props> = ({
|
||||
nextStep,
|
||||
configFile,
|
||||
savePRLink,
|
||||
}) => {
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const { submitPRToRepo } = useGithubRepos();
|
||||
const onNext = useCallback(async () => {
|
||||
setSubmitting(true);
|
||||
const result = await submitPRToRepo(configFile);
|
||||
savePRLink(result.link);
|
||||
setSubmitting(false);
|
||||
nextStep();
|
||||
}, [submitPRToRepo, configFile, nextStep, savePRLink]);
|
||||
|
||||
return (
|
||||
<Grid container direction="column" spacing={1}>
|
||||
<Grid item>
|
||||
<Alert severity="success">
|
||||
config file has been generated. You can optionally edit it before
|
||||
submitting Pull Request
|
||||
</Alert>
|
||||
</Grid>
|
||||
<Grid item container justify="flex-end" spacing={1}>
|
||||
<Grid item>
|
||||
<Tooltip title="Not available yet">
|
||||
<span>
|
||||
<Button variant="contained" disabled>
|
||||
Show config objects
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
{submitting ? (
|
||||
<Grid item>
|
||||
<CircularProgress size="2rem" />
|
||||
</Grid>
|
||||
) : null}
|
||||
<Grid item>
|
||||
<Button
|
||||
disabled={submitting}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onNext}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComponentConfigDisplay;
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2020 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 } from 'react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import {
|
||||
InfoCard,
|
||||
Page,
|
||||
Content,
|
||||
Header,
|
||||
SupportButton,
|
||||
ContentHeader,
|
||||
} from '@backstage/core';
|
||||
import { RegisterComponentForm } from '../ImportComponentForm';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import ImportStepper from './ImportStepper';
|
||||
import ComponentConfigDisplay from './ComponentConfigDisplay';
|
||||
import { ImportFinished } from './ImportFinished';
|
||||
import { RecursivePartial } from '../../util/types';
|
||||
|
||||
export const ImportComponentPage = () => {
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [configFile, setConfigFile] = useState<{
|
||||
repo: string;
|
||||
config: RecursivePartial<Entity>[];
|
||||
}>({ repo: '', config: [] });
|
||||
const [PRLink, setPRLink] = useState<string>('');
|
||||
const nextStep = (options?: { reset: boolean }) => {
|
||||
setActiveStep(step => (options?.reset ? 0 : step + 1));
|
||||
};
|
||||
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header title="Import repo" />
|
||||
<Content>
|
||||
<ContentHeader title="Import your repository to Backstage">
|
||||
<SupportButton>
|
||||
Generate a component definition file and automatically submit it as
|
||||
a pull request. TODO: Add more information about what this is.
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item>
|
||||
<InfoCard>
|
||||
<ImportStepper
|
||||
steps={[
|
||||
{
|
||||
step: 'Select GitHub repo and generate config files for it',
|
||||
content: (
|
||||
<RegisterComponentForm
|
||||
nextStep={nextStep}
|
||||
saveConfig={setConfigFile}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
step: 'Review generated component config files',
|
||||
content: (
|
||||
<ComponentConfigDisplay
|
||||
nextStep={nextStep}
|
||||
configFile={configFile}
|
||||
savePRLink={setPRLink}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
step: 'Finish',
|
||||
content: (
|
||||
<ImportFinished nextStep={nextStep} PRLink={PRLink} />
|
||||
),
|
||||
},
|
||||
]}
|
||||
activeStep={activeStep}
|
||||
nextStep={nextStep}
|
||||
/>
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2020 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 from 'react';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import {
|
||||
Grid,
|
||||
Link,
|
||||
Typography,
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
} from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
width: '100%',
|
||||
},
|
||||
heading: {
|
||||
fontSize: theme.typography.pxToRem(15),
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
},
|
||||
linkList: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
}),
|
||||
);
|
||||
type Props = {
|
||||
nextStep: (options?: { reset: boolean }) => void;
|
||||
PRLink: string;
|
||||
};
|
||||
|
||||
export const ImportFinished: React.FC<Props> = ({ nextStep, PRLink }) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Grid container direction="column" spacing={1}>
|
||||
<Grid item>
|
||||
<Alert severity="success">
|
||||
Pull requests have been successfully opened. You can start again to
|
||||
import more repositories
|
||||
</Alert>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography className={classes.heading}>Pull request link:</Typography>
|
||||
<Link href={PRLink}>{PRLink}</Link>
|
||||
</Grid>
|
||||
<Grid item style={{ alignSelf: 'flex-end' }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => nextStep({ reset: true })}
|
||||
>
|
||||
start again
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2020 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 from 'react';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import Step from '@material-ui/core/Step';
|
||||
import StepLabel from '@material-ui/core/StepLabel';
|
||||
import { StepContent } from '@material-ui/core';
|
||||
|
||||
type Props = {
|
||||
steps: { step: string; content: React.ReactNode }[];
|
||||
activeStep: number;
|
||||
nextStep: (options?: { reset: boolean }) => void;
|
||||
};
|
||||
|
||||
export default function ImportStepper({ steps, activeStep }: Props) {
|
||||
return (
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
{steps.map(({ step }) => {
|
||||
const stepProps: { completed?: boolean } = {};
|
||||
return (
|
||||
<Step key={step} {...stepProps}>
|
||||
<StepLabel>{step}</StepLabel>
|
||||
<StepContent>{steps[activeStep].content}</StepContent>
|
||||
</Step>
|
||||
);
|
||||
})}
|
||||
</Stepper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
export * from './ImportComponentPage';
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2020 Roadie
|
||||
*
|
||||
* 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 * as YAML from 'yaml';
|
||||
import { useApi, githubAuthApiRef } from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { catalogImportApiRef } from '../../api/CatalogImportApi';
|
||||
import { RecursivePartial } from '../../util/types';
|
||||
|
||||
export function useGithubRepos() {
|
||||
const api = useApi(catalogImportApiRef);
|
||||
const auth = useApi(githubAuthApiRef);
|
||||
|
||||
const submitPRToRepo = async (selectedRepo: {
|
||||
repo: string;
|
||||
config: RecursivePartial<Entity>[];
|
||||
}) => {
|
||||
const token = await auth.getAccessToken(['repo']);
|
||||
|
||||
const [ownerName, repoName] = [
|
||||
selectedRepo.repo.split('/')[0],
|
||||
selectedRepo.repo.split('/')[1],
|
||||
];
|
||||
const submitPRResponse = await api.submitPRToRepo({
|
||||
token,
|
||||
owner: ownerName,
|
||||
repo: repoName,
|
||||
fileContent: selectedRepo.config
|
||||
.map(entity => `---\n${YAML.stringify(entity)}`)
|
||||
.join('\n'),
|
||||
});
|
||||
|
||||
await api.createRepositoryLocation({
|
||||
token,
|
||||
owner: selectedRepo.repo.split('/')[0],
|
||||
repo: selectedRepo.repo.split('/')[1],
|
||||
});
|
||||
|
||||
return submitPRResponse;
|
||||
};
|
||||
|
||||
return {
|
||||
submitPRToRepo,
|
||||
generateEntityDefinitions: (repo: string) =>
|
||||
api.generateEntityDefinitions({ repo }),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2020 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 from 'react';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import { ImportComponentPage } from './ImportComponentPage';
|
||||
|
||||
// As we don't know which path the catalog's router mounted on
|
||||
// We need to inject this from the app
|
||||
export const Router = () => (
|
||||
<Routes>
|
||||
<Route element={<ImportComponentPage />} />
|
||||
</Routes>
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
export { plugin } from './plugin';
|
||||
export { Router } from './components/Router';
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2020 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 { plugin } from './plugin';
|
||||
|
||||
describe('import-component', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright 2020 Roadie 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 {
|
||||
createApiFactory,
|
||||
createPlugin,
|
||||
createRouteRef,
|
||||
discoveryApiRef,
|
||||
} from '@backstage/core';
|
||||
import { catalogImportApiRef } from './api/CatalogImportApi';
|
||||
import { CatalogImportClient } from './api/CatalogImportClient';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '',
|
||||
title: 'import-component',
|
||||
});
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: 'import-component',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: catalogImportApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef },
|
||||
factory: ({ discoveryApi }) => new CatalogImportClient({ discoveryApi }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2020 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 '@testing-library/jest-dom';
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
export type RecursivePartial<T> = {
|
||||
[P in keyof T]?: T[P] extends (infer U)[]
|
||||
? RecursivePartial<U>[]
|
||||
: T[P] extends object
|
||||
? RecursivePartial<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2020 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 { ComponentIdValidators } from './validate';
|
||||
|
||||
describe('ComponentIdValidators', () => {
|
||||
describe('httpsValidator validator', () => {
|
||||
const errorMessage = 'Must start with https://.';
|
||||
test.each([
|
||||
[true, 'https://example.com'],
|
||||
[errorMessage, 'http://example.com'],
|
||||
[errorMessage, 'example.com'],
|
||||
[errorMessage, 'www.example.com'],
|
||||
[errorMessage, ''],
|
||||
[errorMessage, undefined],
|
||||
])('should return %p for %s', (expected: string | boolean, arg: any) => {
|
||||
expect(ComponentIdValidators.httpsValidator(arg)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
export const ComponentIdValidators = {
|
||||
httpsValidator: (value: any) =>
|
||||
(typeof value === 'string' && value.match(/^https:\/\//) !== null) ||
|
||||
'Must start with https://.',
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src", "dev"],
|
||||
"compilerOptions": {}
|
||||
}
|
||||
Reference in New Issue
Block a user