Cleanup catalog-import plugin

This commit is contained in:
Marek Calus
2020-10-31 20:41:08 +01:00
parent a479c9ef34
commit d1e06392a1
20 changed files with 60 additions and 262 deletions
+1 -1
View File
@@ -68,6 +68,7 @@ const catalogRouteRef = createRouteRef({
const AppRoutes = () => (
<Routes>
<Navigate key="/" to="/catalog" />
<Route path="/catalog-import/*" element={<ImportComponentRouter />} />
<Route
path={`${catalogRouteRef.path}/*`}
element={<CatalogRouter EntityPage={EntityPage} />}
@@ -83,7 +84,6 @@ const AppRoutes = () => (
path="/register-component"
element={<RegisterComponentRouter catalogRouteRef={catalogRouteRef} />}
/>
<Route path="/import-component/*" element={<ImportComponentRouter />} />
<Route path="/settings" element={<SettingsRouter />} />
{...deprecatedAppRoutes}
</Routes>
+18 -4
View File
@@ -1,7 +1,21 @@
# Register component plugin
# Catalog import plugin
Welcome to the import-component plugin!
Welcome to the catalog-import plugin!
This plugin allows you to create a component-config YAML file for your repository.
This plugin allows you to bootstrap a component-config YAML file for your repository and open a pull request to add it.
When installed it is accessible on [localhost:3000/import-component](localhost:3000/import-component).
When installed it is accessible on [localhost:3000/catalog-import](localhost:3000/catalog-import).
<img src="./src/assets/catalog-import-screenshot.png" />
## Running
Just run the backstage.
```
yarn start && yarn --cwd packages/backend start
```
## Usage
Pretty straightforward, navigate to [localhost:3000/catalog-import](localhost:3000/catalog-import) and enter your repo's URL.
@@ -13,25 +13,9 @@
* 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';
import { PartialEntity } from '../util/types';
export const catalogImportApiRef = createApiRef<CatalogImportApi>({
id: 'plugin.catalogimport.service',
@@ -52,5 +36,5 @@ export interface CatalogImportApi {
}): Promise<{ errorMessage: string | null }>;
generateEntityDefinitions(options: {
repo: string;
}): Promise<RecursivePartial<Entity>[]>;
}): Promise<PartialEntity[]>;
}
@@ -13,28 +13,12 @@
* 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';
import { PartialEntity } from '../util/types';
export const API_BASE_URL = '/api/catalog/locations';
@@ -49,7 +33,7 @@ export class CatalogImportClient implements CatalogImportApi {
repo,
}: {
repo: string;
}): Promise<RecursivePartial<Entity>[]> {
}): Promise<PartialEntity[]> {
const response = await fetch(
`${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`,
{
@@ -117,7 +101,7 @@ export class CatalogImportClient implements CatalogImportApi {
await octo.repos.createOrUpdateFileContents({
owner,
repo,
path: 'backstage.yaml',
path: 'catalog-info.yaml',
message: 'Add backstage.yaml config file',
content: btoa(fileContent),
branch: 'backstage-integration',
@@ -125,7 +109,7 @@ export class CatalogImportClient implements CatalogImportApi {
const pullRequestRespone = await octo.pulls.create({
owner,
repo,
title: 'Add backstage.yaml config file',
title: 'Add catalog-info.yaml config file',
head: 'backstage-integration',
base: 'master',
});
-16
View File
@@ -13,22 +13,6 @@
* 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';
-38
View File
@@ -1,38 +0,0 @@
/*
* 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;
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -13,32 +13,16 @@
* 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';
import { useGithubRepos } from '../util/useGithubRepos';
import { ConfigSpec } from './ImportComponentPage';
type Props = {
nextStep: () => void;
configFile: { repo: string; config: RecursivePartial<Entity>[] };
configFile: ConfigSpec;
savePRLink: (PRLink: string) => void;
};
@@ -14,25 +14,21 @@
* 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 { 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';
import { ComponentIdValidators } from '../util/validate';
import { useGithubRepos } from '../util/useGithubRepos';
import { ConfigSpec } from './ImportComponentPage';
const useStyles = makeStyles<BackstageTheme>(theme => ({
form: {
@@ -43,21 +39,15 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
submit: {
marginTop: theme.spacing(1),
},
select: {
minWidth: 120,
},
}));
type Props = {
nextStep: () => void;
saveConfig: (configFile: {
repo: string;
config: RecursivePartial<Entity>[];
}) => void;
saveConfig: (configFile: ConfigSpec) => void;
};
export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => {
const { control, register, handleSubmit, errors, formState } = useForm({
const { register, handleSubmit, errors, formState } = useForm({
mode: 'onChange',
});
const classes = useStyles();
@@ -71,22 +61,9 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => {
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,
@@ -103,20 +80,18 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => {
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."
helperText="Enter the full path to the repository in GitHub to start tracking your component."
inputRef={register({
required: true,
validate: ComponentIdValidators,
@@ -130,29 +105,6 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => {
)}
</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"
@@ -1,17 +0,0 @@
/*
* 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';
@@ -24,19 +24,23 @@ import {
SupportButton,
ContentHeader,
} from '@backstage/core';
import { RegisterComponentForm } from '../ImportComponentForm';
import { Entity } from '@backstage/catalog-model';
import { RegisterComponentForm } from './ImportComponentForm';
import ImportStepper from './ImportStepper';
import ComponentConfigDisplay from './ComponentConfigDisplay';
import { ImportFinished } from './ImportFinished';
import { RecursivePartial } from '../../util/types';
import { PartialEntity } from '../util/types';
export type ConfigSpec = {
repo: string;
config: PartialEntity[];
};
export const ImportComponentPage = () => {
const [activeStep, setActiveStep] = useState(0);
const [configFile, setConfigFile] = useState<{
repo: string;
config: RecursivePartial<Entity>[];
}>({ repo: '', config: [] });
const [configFile, setConfigFile] = useState<ConfigSpec>({
repo: '',
config: [],
});
const [PRLink, setPRLink] = useState<string>('');
const nextStep = (options?: { reset: boolean }) => {
setActiveStep(step => (options?.reset ? 0 : step + 1));
@@ -1,17 +0,0 @@
/*
* 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';
@@ -27,17 +27,10 @@ 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 = {
@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { Route, Routes } from 'react-router-dom';
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 />} />
+1 -1
View File
@@ -16,7 +16,7 @@
import { plugin } from './plugin';
describe('import-component', () => {
describe('catalog-import', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
});
+3 -17
View File
@@ -13,21 +13,7 @@
* 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,
@@ -39,11 +25,11 @@ import { CatalogImportClient } from './api/CatalogImportClient';
export const rootRouteRef = createRouteRef({
path: '',
title: 'import-component',
title: 'catalog-import',
});
export const plugin = createPlugin({
id: 'import-component',
id: 'catalog-import',
apis: [
createApiFactory({
api: catalogImportApiRef,
+4
View File
@@ -1,3 +1,5 @@
import { Entity } from '@backstage/catalog-model';
/*
* Copyright 2020 Spotify AB
*
@@ -20,3 +22,5 @@ export type RecursivePartial<T> = {
? RecursivePartial<T[P]>
: T[P];
};
export type PartialEntity = RecursivePartial<Entity>;
@@ -13,35 +13,17 @@
* 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';
import { catalogImportApiRef } from '../api/CatalogImportApi';
import { ConfigSpec } from '../components/ImportComponentPage';
export function useGithubRepos() {
const api = useApi(catalogImportApiRef);
const auth = useApi(githubAuthApiRef);
const submitPRToRepo = async (selectedRepo: {
repo: string;
config: RecursivePartial<Entity>[];
}) => {
const submitPRToRepo = async (selectedRepo: ConfigSpec) => {
const token = await auth.getAccessToken(['repo']);
const [ownerName, repoName] = [
@@ -81,7 +81,7 @@ export const ScaffolderPage = () => {
variant="contained"
color="primary"
component={RouterLink}
to="/import-component"
to="/catalog-import"
style={{ marginRight: '8px' }}
>
Import Repository