Change ragister-component to catalog-import

This commit is contained in:
Marek Calus
2020-11-12 09:08:49 +01:00
parent 737f79a53b
commit be2d7b3376
11 changed files with 185 additions and 85 deletions
+4 -1
View File
@@ -68,7 +68,10 @@ const catalogRouteRef = createRouteRef({
const AppRoutes = () => (
<Routes>
<Navigate key="/" to="/catalog" />
<Route path="/catalog-import/*" element={<ImportComponentRouter />} />
<Route
path="/catalog-import"
element={<ImportComponentRouter catalogRouteRef={catalogRouteRef} />}
/>
<Route
path={`${catalogRouteRef.path}/*`}
element={<CatalogRouter EntityPage={EntityPage} />}
+1
View File
@@ -38,4 +38,5 @@ export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild';
export { plugin as CostInsights } from '@backstage/plugin-cost-insights';
export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights';
export { plugin as CatalogImport } from '@backstage/plugin-catalog-import';
export { plugin as BulkCatalogImport } from '@roadiehq/backstage-plugin-bulk-catalog-import';
export { plugin as UserSettings } from '@backstage/plugin-user-settings';
@@ -15,7 +15,7 @@
*/
import { Logger } from 'winston';
import GitUriParser from 'git-url-parse';
import parseGitUri from 'git-url-parse';
import {
AnalyzeLocationRequest,
AnalyzeLocationResponse,
@@ -31,7 +31,7 @@ export class LocationAnalyzerClient implements LocationAnalyzer {
async generateConfig(
request: AnalyzeLocationRequest,
): Promise<AnalyzeLocationResponse> {
const { owner, name, source } = GitUriParser(request.location.target);
const { owner, name, source } = parseGitUri(request.location.target);
const entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
+1 -1
View File
@@ -34,7 +34,7 @@
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-hook-form": "^6.6.0",
"react-router": "^5.2.0",
"react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"yaml": "^1.10.0"
@@ -15,57 +15,140 @@
*/
import React, { useCallback, useState } from 'react';
import { Button, CircularProgress, Grid, Tooltip } from '@material-ui/core';
import Alert from '@material-ui/lab/Alert';
import {
Button,
CircularProgress,
Grid,
Link,
List,
ListItem,
Typography,
Divider,
} from '@material-ui/core';
import { useGithubRepos } from '../util/useGithubRepos';
import { ConfigSpec } from './ImportComponentPage';
import { errorApiRef, useApi } from '@backstage/core';
import {
errorApiRef,
RouteRef,
StructuredMetadataTable,
useApi,
} from '@backstage/core';
import parseGitUri from 'git-url-parse';
import { PartialEntity } from '../util/types';
import { generatePath, resolvePath } from 'react-router';
import { entityRoute, entityRouteParams } from '@backstage/plugin-catalog';
import { Entity } from '@backstage/catalog-model';
import { Link as RouterLink } from 'react-router-dom';
const getEntityCatalogPath = ({
entity,
catalogRouteRef,
}: {
entity: PartialEntity;
catalogRouteRef: RouteRef;
}) => {
const relativeEntityPathInsideCatalog = generatePath(
entityRoute.path,
entityRouteParams(entity as Entity),
);
const resolvedAbsolutePath = resolvePath(
relativeEntityPathInsideCatalog,
catalogRouteRef.path,
)?.pathname;
return resolvedAbsolutePath;
};
type Props = {
nextStep: () => void;
configFile: ConfigSpec;
savePRLink: (PRLink: string) => void;
catalogRouteRef: RouteRef;
};
const ComponentConfigDisplay = ({
nextStep,
configFile,
savePRLink,
catalogRouteRef,
}: Props) => {
const [submitting, setSubmitting] = useState(false);
const errorApi = useApi(errorApiRef);
const { submitPrToRepo } = useGithubRepos();
const { submitPrToRepo, addLocation } = useGithubRepos();
const onNext = useCallback(async () => {
try {
setSubmitting(true);
const result = await submitPrToRepo(configFile);
savePRLink(result.link);
setSubmitting(false);
nextStep();
if (!parseGitUri(configFile.location).filepathtype) {
const result = await submitPrToRepo(configFile);
savePRLink(result.link);
setSubmitting(false);
nextStep();
} else {
addLocation(configFile.location);
setSubmitting(false);
nextStep();
}
} catch (e) {
setSubmitting(false);
errorApi.post(e);
}
}, [submitPrToRepo, configFile, nextStep, savePRLink, errorApi]);
}, [submitPrToRepo, configFile, nextStep, savePRLink, errorApi, addLocation]);
return (
<Grid container direction="column" spacing={1}>
{!parseGitUri(configFile.location).filepathtype ? (
<Typography>
Following config object will be submitted in a pull request to the
repository{' '}
<Link href={configFile.location}>{configFile.location}</Link> and
added as a new location to the backend
</Typography>
) : (
<Typography>
Following config object will be added as a new location to the backend{' '}
<Link href={configFile.location}>{configFile.location}</Link>
</Typography>
)}
<Grid item>
<Alert severity="success">
config file has been generated. You can optionally edit it before
submitting Pull Request
</Alert>
{!parseGitUri(configFile.location).filepathtype ? (
<pre>{JSON.stringify(configFile.config, null, 2)}</pre>
) : (
<List>
{configFile.config.map((entity: any, index: number) => {
const entityPath = getEntityCatalogPath({
entity,
catalogRouteRef,
});
return (
<React.Fragment
key={`${entity.metadata.namespace}-${entity.metadata.name}`}
>
<ListItem>
<StructuredMetadataTable
dense
metadata={{
name: entity.metadata.name,
type: entity.spec.type,
link: (
<Link component={RouterLink} to={entityPath}>
{entityPath}
</Link>
),
}}
/>
</ListItem>
{index < configFile.config.length - 1 && (
<Divider component="li" />
)}
</React.Fragment>
);
})}
</List>
)}
</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>
<Grid item container spacing={1}>
{submitting ? (
<Grid item>
<CircularProgress size="2rem" />
@@ -26,9 +26,11 @@ import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
import { useForm } from 'react-hook-form';
import { useMountedState } from 'react-use';
import parseGitUri from 'git-url-parse';
import { ComponentIdValidators } from '../util/validate';
import { useGithubRepos } from '../util/useGithubRepos';
import { ConfigSpec } from './ImportComponentPage';
import { catalogApiRef } from '@backstage/plugin-catalog';
const useStyles = makeStyles<BackstageTheme>(theme => ({
form: {
@@ -53,6 +55,7 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => {
const classes = useStyles();
const hasErrors = !!errors.componentLocation;
const dirty = formState?.isDirty;
const catalogApi = useApi(catalogApiRef);
const isMounted = useMountedState();
const errorApi = useApi(errorApiRef);
@@ -62,12 +65,22 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => {
const { componentLocation: target } = formData;
try {
if (!isMounted()) return;
const type = !parseGitUri(target).filepathtype ? 'repo' : 'file';
const config = await generateEntityDefinitions(target);
saveConfig({
repo: target,
config,
});
if (type === 'repo') {
saveConfig({
type,
location: target,
config: await generateEntityDefinitions(target),
});
} else {
const data = await catalogApi.addLocation({ target });
saveConfig({
type,
location: data.location.target,
config: data.entities,
});
}
nextStep();
} catch (e) {
errorApi.post(e);
@@ -111,7 +124,7 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => {
disabled={!dirty || hasErrors}
className={classes.submit}
>
Submit
Next
</Button>
</form>
);
@@ -23,6 +23,7 @@ import {
Header,
SupportButton,
ContentHeader,
RouteRef,
} from '@backstage/core';
import { RegisterComponentForm } from './ImportComponentForm';
import ImportStepper from './ImportStepper';
@@ -31,29 +32,35 @@ import { ImportFinished } from './ImportFinished';
import { PartialEntity } from '../util/types';
export type ConfigSpec = {
repo: string;
type: 'repo' | 'file';
location: string;
config: PartialEntity[];
};
export const ImportComponentPage = () => {
export const ImportComponentPage = ({
catalogRouteRef,
}: {
catalogRouteRef: RouteRef;
}) => {
const [activeStep, setActiveStep] = useState(0);
const [configFile, setConfigFile] = useState<ConfigSpec>({
repo: '',
type: 'repo',
location: '',
config: [],
});
const [PRLink, setPRLink] = useState<string>('');
const [endLink, setEndLink] = useState<string>('');
const nextStep = (options?: { reset: boolean }) => {
setActiveStep(step => (options?.reset ? 0 : step + 1));
};
return (
<Page themeId="home">
<Header title="Import repo" />
<Header title="Register an existing component" />
<Content>
<ContentHeader title="Import your repository to Backstage">
<ContentHeader title="Start tracking your component on backstage">
<SupportButton>
Generate a component definition file and automatically submit it as
a pull request. TODO: Add more information about what this is.
Start tracking your component in Backstage. TODO: Add more
information about what this is.
</SupportButton>
</ContentHeader>
<Grid container spacing={3} direction="column">
@@ -62,7 +69,7 @@ export const ImportComponentPage = () => {
<ImportStepper
steps={[
{
step: 'Select GitHub repo and generate config files for it',
step: 'Insert GitHub repo URL or Entity File URL',
content: (
<RegisterComponentForm
nextStep={nextStep}
@@ -71,19 +78,24 @@ export const ImportComponentPage = () => {
),
},
{
step: 'Review generated component config files',
step: 'Review',
content: (
<ComponentConfigDisplay
nextStep={nextStep}
configFile={configFile}
savePRLink={setPRLink}
savePRLink={setEndLink}
catalogRouteRef={catalogRouteRef}
/>
),
},
{
step: 'Finish',
content: (
<ImportFinished nextStep={nextStep} PRLink={PRLink} />
<ImportFinished
nextStep={nextStep}
PRLink={endLink}
type={configFile.type}
/>
),
},
]}
@@ -13,52 +13,44 @@
* 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({
heading: {
fontSize: theme.typography.pxToRem(15),
fontWeight: theme.typography.fontWeightRegular,
},
}),
);
import React from 'react';
import { Alert } from '@material-ui/lab';
import { Link as RouterLink } from 'react-router-dom';
import { Button, Grid, Link } from '@material-ui/core';
type Props = {
type: 'repo' | 'file';
nextStep: (options?: { reset: boolean }) => void;
PRLink: string;
};
export const ImportFinished = ({ nextStep, PRLink }: Props) => {
const classes = useStyles();
export const ImportFinished = ({ nextStep, PRLink, type }: Props) => {
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
{type === 'repo'
? 'Pull requests have been successfully opened. You can start again to import more repositories'
: 'Entity added to catalog successfully'}
</Alert>
</Grid>
<Grid item>
<Typography className={classes.heading}>Pull request link:</Typography>
<Link href={PRLink}>{PRLink}</Link>
</Grid>
<Grid item style={{ alignSelf: 'flex-end' }}>
{type === 'repo' ? (
<Link
component={RouterLink}
to={PRLink}
style={{ marginRight: '8px' }}
>
'View pull request on GitHub'
</Link>
) : null}
<Button
variant="contained"
color="primary"
onClick={() => nextStep({ reset: true })}
>
start again
Start again
</Button>
</Grid>
</Grid>
@@ -14,12 +14,15 @@
* limitations under the License.
*/
import { RouteRef } from '@backstage/core';
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import { ImportComponentPage } from './ImportComponentPage';
export const Router = () => (
export const Router = ({ catalogRouteRef }: { catalogRouteRef: RouteRef }) => (
<Routes>
<Route element={<ImportComponentPage />} />
<Route
element={<ImportComponentPage catalogRouteRef={catalogRouteRef} />}
/>
</Routes>
);
@@ -26,7 +26,7 @@ export function useGithubRepos() {
const submitPrToRepo = async (selectedRepo: ConfigSpec) => {
const token = await auth.getAccessToken(['repo']);
const [ownerName, repoName] = selectedRepo.repo.split('/').slice(-2);
const [ownerName, repoName] = selectedRepo.location.split('/').slice(-2);
const submitPRResponse = await api
.submitPrToRepo({
oAuthToken: token,
@@ -55,5 +55,7 @@ export function useGithubRepos() {
submitPrToRepo,
generateEntityDefinitions: (repo: string) =>
api.generateEntityDefinitions({ repo }),
addLocation: (location: string) =>
api.createRepositoryLocation({ location }),
};
}
@@ -82,15 +82,6 @@ export const ScaffolderPage = () => {
color="primary"
component={RouterLink}
to="/catalog-import"
style={{ marginRight: '8px' }}
>
Import Repository
</Button>
<Button
variant="contained"
color="primary"
component={RouterLink}
to="/register-component"
>
Register Existing Component
</Button>