Make preparePullRequest async
Signed-off-by: Oliver Sand <oliver.sand@sda-se.com>
This commit is contained in:
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { PartialEntity } from '../types';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import { PartialEntity } from '../types';
|
||||
|
||||
export const catalogImportApiRef = createApiRef<CatalogImportApi>({
|
||||
id: 'plugin.catalog-import.service',
|
||||
@@ -42,10 +42,10 @@ export type AnalyzeResult =
|
||||
export interface CatalogImportApi {
|
||||
analyzeUrl(url: string): Promise<AnalyzeResult>;
|
||||
|
||||
preparePullRequest?(): {
|
||||
preparePullRequest?(): Promise<{
|
||||
title: string;
|
||||
body: string;
|
||||
};
|
||||
}>;
|
||||
submitPullRequest(options: {
|
||||
repositoryUrl: string;
|
||||
fileContent: string;
|
||||
|
||||
@@ -115,7 +115,11 @@ describe('CatalogImportClient', () => {
|
||||
scmIntegrationsApi,
|
||||
identityApi,
|
||||
catalogApi,
|
||||
configApi: new ConfigReader({}),
|
||||
configApi: new ConfigReader({
|
||||
app: {
|
||||
baseUrl: 'https://demo.backstage.io/',
|
||||
},
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -444,4 +448,13 @@ describe('CatalogImportClient', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('preparePullRequest', () => {
|
||||
test('should prepare pull request details', async () => {
|
||||
await expect(catalogImportClient.preparePullRequest()).resolves.toEqual({
|
||||
title: 'Add catalog-info.yaml config file',
|
||||
body: expect.any(String),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -118,10 +118,10 @@ export class CatalogImportClient implements CatalogImportApi {
|
||||
};
|
||||
}
|
||||
|
||||
preparePullRequest(): {
|
||||
async preparePullRequest(): Promise<{
|
||||
title: string;
|
||||
body: string;
|
||||
} {
|
||||
}> {
|
||||
const appTitle =
|
||||
this.configApi.getOptionalString('app.title') ?? 'Backstage';
|
||||
const appBaseUrl = this.configApi.getString('app.baseUrl');
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('<ImportInfoCard />', () => {
|
||||
});
|
||||
|
||||
it('renders section on GitHub discovery if supported', async () => {
|
||||
catalogImportApi.preparePullRequest = () => ({ title: '', body: '' });
|
||||
catalogImportApi.preparePullRequest = async () => ({ title: '', body: '' });
|
||||
|
||||
await act(async () => {
|
||||
const { getByText } = render(
|
||||
@@ -82,7 +82,7 @@ describe('<ImportInfoCard />', () => {
|
||||
});
|
||||
|
||||
it('renders section on pull requests if supported', async () => {
|
||||
catalogImportApi.preparePullRequest = () => ({ title: '', body: '' });
|
||||
catalogImportApi.preparePullRequest = async () => ({ title: '', body: '' });
|
||||
|
||||
await act(async () => {
|
||||
const { getByText } = render(
|
||||
|
||||
@@ -136,9 +136,6 @@ export function defaultGenerateStepper(
|
||||
return defaults.prepare(state, opts);
|
||||
}
|
||||
|
||||
const { title, body } =
|
||||
opts.apis.catalogImportApi.preparePullRequest!();
|
||||
|
||||
return {
|
||||
stepLabel: <StepLabel>Create Pull Request</StepLabel>,
|
||||
content: (
|
||||
@@ -146,8 +143,6 @@ export function defaultGenerateStepper(
|
||||
analyzeResult={state.analyzeResult}
|
||||
onPrepare={state.onPrepare}
|
||||
onGoBack={state.onGoBack}
|
||||
defaultTitle={title}
|
||||
defaultBody={body}
|
||||
renderFormFields={({
|
||||
values,
|
||||
setValue,
|
||||
|
||||
+27
-26
@@ -14,23 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { errorApiRef } from '../../../../../packages/core-plugin-api/src';
|
||||
import { AnalyzeResult, catalogImportApiRef } from '../../api';
|
||||
import { asInputRef } from '../helpers';
|
||||
import {
|
||||
generateEntities,
|
||||
StepPrepareCreatePullRequest,
|
||||
} from './StepPrepareCreatePullRequest';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
|
||||
describe('<StepPrepareCreatePullRequest />', () => {
|
||||
const catalogImportApi: jest.Mocked<typeof catalogImportApiRef.T> = {
|
||||
analyzeUrl: jest.fn(),
|
||||
submitPullRequest: jest.fn(),
|
||||
preparePullRequest: jest.fn(),
|
||||
};
|
||||
|
||||
const catalogApi: jest.Mocked<typeof catalogApiRef.T> = {
|
||||
@@ -44,12 +46,16 @@ describe('<StepPrepareCreatePullRequest />', () => {
|
||||
removeEntityByUid: jest.fn(),
|
||||
};
|
||||
|
||||
const errorApi: jest.Mocked<typeof errorApiRef.T> = {
|
||||
error$: jest.fn(),
|
||||
post: jest.fn(),
|
||||
};
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.with(catalogImportApiRef, catalogImportApi).with(
|
||||
catalogApiRef,
|
||||
catalogApi,
|
||||
)}
|
||||
apis={ApiRegistry.with(catalogImportApiRef, catalogImportApi)
|
||||
.with(catalogApiRef, catalogApi)
|
||||
.with(errorApiRef, errorApi)}
|
||||
>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
@@ -77,16 +83,19 @@ describe('<StepPrepareCreatePullRequest />', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
||||
(catalogImportApi.preparePullRequest! as jest.Mock).mockResolvedValue({
|
||||
title: 'My title',
|
||||
body: 'My **body**',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders without exploding', async () => {
|
||||
catalogApi.getEntities.mockReturnValue(Promise.resolve({ items: [] }));
|
||||
|
||||
await act(async () => {
|
||||
const { getByText } = render(
|
||||
const { findByText } = render(
|
||||
<StepPrepareCreatePullRequest
|
||||
defaultTitle="My title"
|
||||
defaultBody="My **body**"
|
||||
analyzeResult={analyzeResult}
|
||||
onPrepare={onPrepareFn}
|
||||
renderFormFields={({ register }) => {
|
||||
@@ -105,8 +114,8 @@ describe('<StepPrepareCreatePullRequest />', () => {
|
||||
},
|
||||
);
|
||||
|
||||
const title = getByText('My title');
|
||||
const description = getByText('body', { selector: 'strong' });
|
||||
const title = await findByText('My title');
|
||||
const description = await findByText('body', { selector: 'strong' });
|
||||
expect(title).toBeInTheDocument();
|
||||
expect(title).toBeVisible();
|
||||
expect(description).toBeInTheDocument();
|
||||
@@ -124,10 +133,8 @@ describe('<StepPrepareCreatePullRequest />', () => {
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await render(
|
||||
render(
|
||||
<StepPrepareCreatePullRequest
|
||||
defaultTitle="My title"
|
||||
defaultBody="My **body**"
|
||||
analyzeResult={analyzeResult}
|
||||
onPrepare={onPrepareFn}
|
||||
renderFormFields={({ register }) => {
|
||||
@@ -154,11 +161,9 @@ describe('<StepPrepareCreatePullRequest />', () => {
|
||||
},
|
||||
);
|
||||
|
||||
await userEvent.type(await screen.getByLabelText('name'), '-changed');
|
||||
await userEvent.type(await screen.getByLabelText('owner'), '-changed');
|
||||
await userEvent.click(
|
||||
await screen.getByRole('button', { name: /Create PR/i }),
|
||||
);
|
||||
userEvent.type(await screen.findByLabelText('name'), '-changed');
|
||||
userEvent.type(await screen.findByLabelText('owner'), '-changed');
|
||||
userEvent.click(screen.getByRole('button', { name: /Create PR/i }));
|
||||
});
|
||||
|
||||
expect(catalogImportApi.submitPullRequest).toBeCalledTimes(1);
|
||||
@@ -212,10 +217,8 @@ spec:
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await render(
|
||||
render(
|
||||
<StepPrepareCreatePullRequest
|
||||
defaultTitle="My title"
|
||||
defaultBody="My **body**"
|
||||
analyzeResult={analyzeResult}
|
||||
onPrepare={onPrepareFn}
|
||||
renderFormFields={({ register }) => {
|
||||
@@ -234,8 +237,8 @@ spec:
|
||||
},
|
||||
);
|
||||
|
||||
await userEvent.click(
|
||||
await screen.getByRole('button', { name: /Create PR/i }),
|
||||
userEvent.click(
|
||||
await screen.findByRole('button', { name: /Create PR/i }),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -261,10 +264,8 @@ spec:
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await render(
|
||||
render(
|
||||
<StepPrepareCreatePullRequest
|
||||
defaultTitle="My title"
|
||||
defaultBody="My **body**"
|
||||
analyzeResult={analyzeResult}
|
||||
onPrepare={onPrepareFn}
|
||||
renderFormFields={renderFormFieldsFn}
|
||||
|
||||
+91
-78
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
formatEntityRefTitle,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Box, FormHelperText, Grid, Typography } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { UnpackNestedValue, UseFormReturn } from 'react-hook-form';
|
||||
import { useAsync } from 'react-use';
|
||||
import YAML from 'yaml';
|
||||
@@ -59,9 +59,6 @@ type Props = {
|
||||
) => void;
|
||||
onGoBack?: () => void;
|
||||
|
||||
defaultTitle: string;
|
||||
defaultBody: string;
|
||||
|
||||
renderFormFields: (
|
||||
props: Pick<
|
||||
UseFormReturn<FormData>,
|
||||
@@ -99,16 +96,30 @@ export const StepPrepareCreatePullRequest = ({
|
||||
onPrepare,
|
||||
onGoBack,
|
||||
renderFormFields,
|
||||
defaultTitle,
|
||||
defaultBody,
|
||||
}: Props) => {
|
||||
const classes = useStyles();
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const catalogInfoApi = useApi(catalogImportApiRef);
|
||||
const catalogImportApi = useApi(catalogImportApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const {
|
||||
loading: prDefaultsLoading,
|
||||
value: prDefaults,
|
||||
error: prDefaultsError,
|
||||
} = useAsync(
|
||||
() => catalogImportApi.preparePullRequest!(),
|
||||
[catalogImportApi.preparePullRequest],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (prDefaultsError) {
|
||||
errorApi.post(prDefaultsError);
|
||||
}
|
||||
}, [prDefaultsError, errorApi]);
|
||||
|
||||
const { loading: groupsLoading, value: groups } = useAsync(async () => {
|
||||
const groupEntities = await catalogApi.getEntities({
|
||||
filter: { kind: 'group' },
|
||||
@@ -124,7 +135,7 @@ export const StepPrepareCreatePullRequest = ({
|
||||
setSubmitted(true);
|
||||
|
||||
try {
|
||||
const pr = await catalogInfoApi.submitPullRequest({
|
||||
const pr = await catalogImportApi.submitPullRequest({
|
||||
repositoryUrl: analyzeResult.url,
|
||||
title: data.title,
|
||||
body: data.body,
|
||||
@@ -171,7 +182,7 @@ export const StepPrepareCreatePullRequest = ({
|
||||
analyzeResult.generatedEntities,
|
||||
analyzeResult.integrationType,
|
||||
analyzeResult.url,
|
||||
catalogInfoApi,
|
||||
catalogImportApi,
|
||||
onPrepare,
|
||||
],
|
||||
);
|
||||
@@ -184,79 +195,81 @@ export const StepPrepareCreatePullRequest = ({
|
||||
a Pull Request that creates one.
|
||||
</Typography>
|
||||
|
||||
<PreparePullRequestForm<FormData>
|
||||
onSubmit={handleResult}
|
||||
defaultValues={{
|
||||
title: defaultTitle,
|
||||
body: defaultBody,
|
||||
owner:
|
||||
(analyzeResult.generatedEntities[0]?.spec?.owner as string) || '',
|
||||
componentName:
|
||||
analyzeResult.generatedEntities[0]?.metadata?.name || '',
|
||||
useCodeowners: false,
|
||||
}}
|
||||
render={({ values, formState, register, setValue }) => (
|
||||
<>
|
||||
{renderFormFields({
|
||||
values,
|
||||
formState,
|
||||
register,
|
||||
setValue,
|
||||
groups: groups ?? [],
|
||||
groupsLoading,
|
||||
})}
|
||||
{!prDefaultsLoading && (
|
||||
<PreparePullRequestForm<FormData>
|
||||
onSubmit={handleResult}
|
||||
defaultValues={{
|
||||
title: prDefaults?.title ?? '',
|
||||
body: prDefaults?.body ?? '',
|
||||
owner:
|
||||
(analyzeResult.generatedEntities[0]?.spec?.owner as string) || '',
|
||||
componentName:
|
||||
analyzeResult.generatedEntities[0]?.metadata?.name || '',
|
||||
useCodeowners: false,
|
||||
}}
|
||||
render={({ values, formState, register, setValue }) => (
|
||||
<>
|
||||
{renderFormFields({
|
||||
values,
|
||||
formState,
|
||||
register,
|
||||
setValue,
|
||||
groups: groups ?? [],
|
||||
groupsLoading,
|
||||
})}
|
||||
|
||||
<Box marginTop={2}>
|
||||
<Typography variant="h6">Preview Pull Request</Typography>
|
||||
</Box>
|
||||
<Box marginTop={2}>
|
||||
<Typography variant="h6">Preview Pull Request</Typography>
|
||||
</Box>
|
||||
|
||||
<PreviewPullRequestComponent
|
||||
title={values.title}
|
||||
description={values.body}
|
||||
classes={{
|
||||
card: classes.previewCard,
|
||||
cardContent: classes.previewCardContent,
|
||||
}}
|
||||
/>
|
||||
<PreviewPullRequestComponent
|
||||
title={values.title}
|
||||
description={values.body}
|
||||
classes={{
|
||||
card: classes.previewCard,
|
||||
cardContent: classes.previewCardContent,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Box marginTop={2} marginBottom={1}>
|
||||
<Typography variant="h6">Preview Entities</Typography>
|
||||
</Box>
|
||||
<Box marginTop={2} marginBottom={1}>
|
||||
<Typography variant="h6">Preview Entities</Typography>
|
||||
</Box>
|
||||
|
||||
<PreviewCatalogInfoComponent
|
||||
entities={generateEntities(
|
||||
analyzeResult.generatedEntities,
|
||||
values.componentName,
|
||||
values.owner,
|
||||
)}
|
||||
repositoryUrl={analyzeResult.url}
|
||||
classes={{
|
||||
card: classes.previewCard,
|
||||
cardContent: classes.previewCardContent,
|
||||
}}
|
||||
/>
|
||||
|
||||
{error && <FormHelperText error>{error}</FormHelperText>}
|
||||
|
||||
<Grid container spacing={0}>
|
||||
{onGoBack && (
|
||||
<BackButton onClick={onGoBack} disabled={submitted} />
|
||||
)}
|
||||
<NextButton
|
||||
type="submit"
|
||||
disabled={Boolean(
|
||||
formState.errors.title ||
|
||||
formState.errors.body ||
|
||||
formState.errors.owner,
|
||||
<PreviewCatalogInfoComponent
|
||||
entities={generateEntities(
|
||||
analyzeResult.generatedEntities,
|
||||
values.componentName,
|
||||
values.owner,
|
||||
)}
|
||||
loading={submitted}
|
||||
>
|
||||
Create PR
|
||||
</NextButton>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
repositoryUrl={analyzeResult.url}
|
||||
classes={{
|
||||
card: classes.previewCard,
|
||||
cardContent: classes.previewCardContent,
|
||||
}}
|
||||
/>
|
||||
|
||||
{error && <FormHelperText error>{error}</FormHelperText>}
|
||||
|
||||
<Grid container spacing={0}>
|
||||
{onGoBack && (
|
||||
<BackButton onClick={onGoBack} disabled={submitted} />
|
||||
)}
|
||||
<NextButton
|
||||
type="submit"
|
||||
disabled={Boolean(
|
||||
formState.errors.title ||
|
||||
formState.errors.body ||
|
||||
formState.errors.owner,
|
||||
)}
|
||||
loading={submitted}
|
||||
>
|
||||
Create PR
|
||||
</NextButton>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user