feat: add i18n support for catalog-import plugin

Signed-off-by: JounQin <admin@1stg.me>
This commit is contained in:
JounQin
2024-12-11 18:07:38 +08:00
committed by benjdlambert
parent b5dc7e4021
commit 66a1140dd8
21 changed files with 552 additions and 230 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-import': patch
---
Add i18n support for catalog-import plugin
+62 -1
View File
@@ -16,8 +16,69 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha';
export const catalogImportTranslationRef: TranslationRef<
'catalog-import',
{
readonly pageTitle: 'Register an existing component';
readonly 'buttons.back': 'Back';
readonly 'defaultImportPage.headerTitle': 'Register an existing component';
readonly 'defaultImportPage.contentHeaderTitle': 'Start tracking your component in {{appTitle}}';
readonly 'defaultImportPage.supportTitle': 'Start tracking your component in {{appTitle}} by adding it to the software catalog.';
readonly 'importInfoCard.title': 'Register an existing component';
readonly 'importInfoCard.deepLinkTitle': 'Learn more about the Software Catalog';
readonly 'importInfoCard.linkDescription': 'Enter the URL to your source code repository to add it to {{appTitle}}.';
readonly 'importInfoCard.fileLinkTitle': 'Link to an existing entity file';
readonly 'importInfoCard.examplePrefix': 'Example: ';
readonly 'importInfoCard.fileLinkDescription': 'The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.';
readonly 'importInfoCard.githubIntegration.label': 'GitHub only';
readonly 'importInfoCard.githubIntegration.title': 'Link to a repository';
readonly 'importStepper.finish.title': 'Finish';
readonly 'importStepper.noLocation.title': 'Create Pull Request';
readonly 'importStepper.noLocation.createPr.ownerLabel': 'Entity Owner';
readonly 'importStepper.noLocation.createPr.detailsTitle': 'Pull Request Details';
readonly 'importStepper.noLocation.createPr.titleLabel': 'Pull Request Title';
readonly 'importStepper.noLocation.createPr.titlePlaceholder': 'Add Backstage catalog entity descriptor files';
readonly 'importStepper.noLocation.createPr.bodyLabel': 'Pull Request Body';
readonly 'importStepper.noLocation.createPr.bodyPlaceholder': 'A describing text with Markdown support';
readonly 'importStepper.noLocation.createPr.configurationTitle': 'Entity Configuration';
readonly 'importStepper.noLocation.createPr.componentNameLabel': 'Name of the created component';
readonly 'importStepper.noLocation.createPr.componentNamePlaceholder': 'my-component';
readonly 'importStepper.noLocation.createPr.ownerLoadingText': 'Loading groups…';
readonly 'importStepper.noLocation.createPr.ownerHelperText': 'Select an owner from the list or enter a reference to a Group or a User';
readonly 'importStepper.noLocation.createPr.ownerErrorHelperText': 'required value';
readonly 'importStepper.noLocation.createPr.ownerPlaceholder': 'my-group';
readonly 'importStepper.noLocation.createPr.codeownersHelperText': 'WARNING: This may fail if no CODEOWNERS file is found at the target location.';
readonly 'importStepper.singleLocation.title': 'Select Locations';
readonly 'importStepper.singleLocation.description': 'Discovered Locations: 1';
readonly 'importStepper.multipleLocations.title': 'Select Locations';
readonly 'importStepper.multipleLocations.description': 'Discovered Locations: {{length, number}}';
readonly 'importStepper.analyze.title': 'Select URL';
readonly 'importStepper.prepare.title': 'Import Actions';
readonly 'importStepper.prepare.description': 'Optional';
readonly 'importStepper.review.title': 'Review';
readonly 'stepFinishImportLocation.repository.title': 'The following Pull Request has been opened: ';
readonly 'stepFinishImportLocation.repository.description': 'Your entities will be imported as soon as the Pull Request is merged.';
readonly 'stepFinishImportLocation.backButtonText': 'Register another';
readonly 'stepFinishImportLocation.locations.new': 'The following entities have been added to the catalog:';
readonly 'stepFinishImportLocation.locations.backButtonText': 'Register another';
readonly 'stepFinishImportLocation.locations.existing': 'A refresh was triggered for the following locations:';
readonly 'stepFinishImportLocation.locations.viewButtonText': 'View Component';
readonly 'stepInitAnalyzeUrl.error.default': 'Received unknown analysis result of type {{type}}. Please contact the support team.';
readonly 'stepInitAnalyzeUrl.error.url': 'Must start with http:// or https://.';
readonly 'stepInitAnalyzeUrl.error.repository': "Couldn't generate entities for your repository";
readonly 'stepInitAnalyzeUrl.error.locations': 'There are no entities at this location';
readonly 'stepInitAnalyzeUrl.urlHelperText': 'Enter the full path to your entity file to start tracking your component';
readonly 'stepInitAnalyzeUrl.nextButtonText': 'Analyze';
readonly 'stepPrepareCreatePullRequest.nextButtonText': 'Create PR';
readonly 'stepPrepareCreatePullRequest.previewPr.title': 'Preview Pull Request';
readonly 'stepPrepareCreatePullRequest.previewPr.subheader': 'Create a new Pull Request';
readonly 'stepPrepareCreatePullRequest.previewCatalogInfo.title': 'Preview Entities';
readonly 'stepPrepareSelectLocations.locations.description': 'Select one or more locations that are present in your git repository:';
readonly 'stepPrepareSelectLocations.locations.selectAll': 'Select All';
readonly 'stepPrepareSelectLocations.nextButtonText': 'Review';
readonly 'stepPrepareSelectLocations.existingLocations.description': 'These locations already exist in the catalog:';
readonly 'stepReviewLocation.refresh': 'Refresh';
readonly 'stepReviewLocation.import': 'Import';
readonly 'stepReviewLocation.catalog.new': 'The following entities will be added to the catalog:';
readonly 'stepReviewLocation.catalog.exists': 'The following locations already exist in the catalog:';
readonly 'stepReviewLocation.prepareResult.title': 'The following Pull Request has been opened: ';
readonly 'stepReviewLocation.prepareResult.description': 'You can already import the location and {{appTitle}} will fetch the entities as soon as the Pull Request is merged.';
}
>;
+3
View File
@@ -6,6 +6,7 @@
import { ApiRef } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha';
import { ComponentProps } from 'react';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { ConfigApi } from '@backstage/core-plugin-api';
@@ -24,6 +25,7 @@ import { ScmAuthApi } from '@backstage/integration-react';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { SubmitHandler } from 'react-hook-form';
import { TextFieldProps } from '@material-ui/core/TextField/TextField';
import { TranslationFunction } from '@backstage/core-plugin-api/alpha';
import { UseFormProps } from 'react-hook-form';
import { UseFormReturn } from 'react-hook-form';
@@ -145,6 +147,7 @@ export { catalogImportPlugin as plugin };
export function defaultGenerateStepper(
flow: ImportFlows,
defaults: StepperProvider,
t: TranslationFunction<typeof catalogImportTranslationRef.T>,
): StepperProvider;
// @public
+2
View File
@@ -92,3 +92,5 @@ export default createFrontendPlugin({
importPage: convertLegacyRouteRef(rootRouteRef),
},
});
export { catalogImportTranslationRef } from './translation';
@@ -154,7 +154,7 @@ function mockPrEndpoint() {
describe('RepoApiClient', () => {
const server = setupServer();
registerMswTestHooks(server);
const testToken = new Date().toString();
const testToken = new Date().toLocaleString('en-US');
const sut = new RepoApiClient({
project: 'project',
tenantUrl: 'https://dev.azure.com/acme',
@@ -316,7 +316,7 @@ describe('createAzurePullRequest', () => {
});
it('should create a new Pull request', async () => {
const testToken = new Date().getTime().toString();
const testToken = new Date().toLocaleString('en-US');
const options: AzurePrOptions = {
tenantUrl: 'https://dev.azure.com/acme',
repository: 'test',
@@ -15,11 +15,14 @@
*/
import { LinkButton } from '@backstage/core-components';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import Button from '@material-ui/core/Button';
import CircularProgress from '@material-ui/core/CircularProgress';
import { makeStyles } from '@material-ui/core/styles';
import { ComponentProps } from 'react';
import { catalogImportTranslationRef } from '../../translation';
const useStyles = makeStyles(theme => ({
wrapper: {
marginTop: theme.spacing(1),
@@ -62,11 +65,12 @@ export const NextButton = (
};
export const BackButton = (props: ComponentProps<typeof Button>) => {
const { t } = useTranslationRef(catalogImportTranslationRef);
const classes = useStyles();
return (
<Button variant="outlined" className={classes.button} {...props}>
{props.children || 'Back'}
{props.children || t('buttons.back')}
</Button>
);
};
@@ -22,13 +22,14 @@ import {
SupportButton,
} from '@backstage/core-components';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import Grid from '@material-ui/core/Grid';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { useTheme } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { catalogImportTranslationRef } from '../../translation';
import { ImportInfoCard } from '../ImportInfoCard';
import { ImportStepper } from '../ImportStepper';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { catalogImportTranslationRef } from '../../translation';
/**
* The default catalog import page.
@@ -42,8 +43,6 @@ export const DefaultImportPage = () => {
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const appTitle = configApi.getOptionalString('app.title') || 'Backstage';
const supportTitle = `Start tracking your component in ${appTitle} by adding it to the software catalog.`;
const contentItems = [
<Grid key={0} item xs={12} md={4} lg={6} xl={8}>
<ImportInfoCard />
@@ -56,10 +55,14 @@ export const DefaultImportPage = () => {
return (
<Page themeId="home">
<Header title={t('pageTitle')} />
<Header title={t('defaultImportPage.headerTitle')} />
<Content>
<ContentHeader title={`Start tracking your component in ${appTitle}`}>
<SupportButton>{supportTitle}</SupportButton>
<ContentHeader
title={t('defaultImportPage.contentHeaderTitle', { appTitle })}
>
<SupportButton>
{t('defaultImportPage.supportTitle', { appTitle })}
</SupportButton>
</ContentHeader>
<Grid container spacing={2}>
@@ -16,11 +16,12 @@
import { InfoCard } from '@backstage/core-components';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import Chip from '@material-ui/core/Chip';
import Typography from '@material-ui/core/Typography';
import { catalogImportApiRef } from '../../api';
import { useCatalogFilename } from '../../hooks';
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { catalogImportTranslationRef } from '../../translation';
/**
@@ -58,31 +59,37 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => {
title={t('importInfoCard.title')}
titleTypographyProps={{ component: 'h3' }}
deepLink={{
title: 'Learn more about the Software Catalog',
title: t('importInfoCard.deepLinkTitle'),
link: 'https://backstage.io/docs/features/software-catalog/',
}}
>
<Typography variant="body2" paragraph>
Enter the URL to your source code repository to add it to {appTitle}.
{t('importInfoCard.linkDescription', { appTitle })}
</Typography>
<Typography component="h4" variant="h6">
Link to an existing entity file
{t('importInfoCard.fileLinkTitle')}
</Typography>
<Typography variant="subtitle2" color="textSecondary" paragraph>
Example: <code>{exampleLocationUrl}</code>
{t('importInfoCard.examplePrefix')}
<code>{exampleLocationUrl}</code>
</Typography>
<Typography variant="body2" paragraph>
The wizard analyzes the file, previews the entities, and adds them to
the {appTitle} catalog.
{t('importInfoCard.fileLinkDescription', { appTitle })}
</Typography>
{hasGithubIntegration && (
<>
<Typography component="h4" variant="h6">
Link to a repository{' '}
<Chip label="GitHub only" variant="outlined" size="small" />
{t('importInfoCard.githubIntegration.title')}
<Chip
label={t('importInfoCard.githubIntegration.label')}
variant="outlined"
size="small"
style={{ marginLeft: 8, marginBottom: 0 }}
/>
</Typography>
<Typography variant="subtitle2" color="textSecondary" paragraph>
Example: <code>{exampleRepositoryUrl}</code>
{t('importInfoCard.examplePrefix')}
<code>{exampleRepositoryUrl}</code>
</Typography>
<Typography variant="body2" paragraph>
The wizard discovers all <code>{catalogFilename}</code> files in the
@@ -16,12 +16,15 @@
import { InfoCard, InfoCardVariants } from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import Step from '@material-ui/core/Step';
import StepContent from '@material-ui/core/StepContent';
import Stepper from '@material-ui/core/Stepper';
import { makeStyles } from '@material-ui/core/styles';
import { useMemo } from 'react';
import { catalogImportApiRef } from '../../api';
import { catalogImportTranslationRef } from '../../translation';
import { ImportFlows, ImportState, useImportState } from '../useImportState';
import {
defaultGenerateStepper,
@@ -56,6 +59,7 @@ export interface ImportStepperProps {
* @public
*/
export const ImportStepper = (props: ImportStepperProps) => {
const { t } = useTranslationRef(catalogImportTranslationRef);
const {
initialUrl,
generateStepper = defaultGenerateStepper,
@@ -67,8 +71,8 @@ export const ImportStepper = (props: ImportStepperProps) => {
const state = useImportState({ initialUrl });
const states = useMemo<StepperProvider>(
() => generateStepper(state.activeFlow, defaultStepper),
[generateStepper, state.activeFlow],
() => generateStepper(state.activeFlow, defaultStepper, t),
[generateStepper, state.activeFlow, t],
);
const render = (step: StepConfiguration) => {
@@ -90,25 +94,25 @@ export const ImportStepper = (props: ImportStepperProps) => {
{render(
states.analyze(
state as Extract<ImportState, { activeState: 'analyze' }>,
{ apis: { catalogImportApi } },
{ apis: { catalogImportApi }, t },
),
)}
{render(
states.prepare(
state as Extract<ImportState, { activeState: 'prepare' }>,
{ apis: { catalogImportApi } },
{ apis: { catalogImportApi }, t },
),
)}
{render(
states.review(
state as Extract<ImportState, { activeState: 'review' }>,
{ apis: { catalogImportApi } },
{ apis: { catalogImportApi }, t },
),
)}
{render(
states.finish(
state as Extract<ImportState, { activeState: 'finish' }>,
{ apis: { catalogImportApi } },
{ apis: { catalogImportApi }, t },
),
)}
</Stepper>
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { TranslationFunction } from '@backstage/core-plugin-api/alpha';
import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha';
import Box from '@material-ui/core/Box';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
@@ -48,19 +50,31 @@ export type StepConfiguration = {
export interface StepperProvider {
analyze: (
s: Extract<ImportState, { activeState: 'analyze' }>,
opts: { apis: StepperApis },
opts: {
apis: StepperApis;
t: TranslationFunction<typeof catalogImportTranslationRef.T>;
},
) => StepConfiguration;
prepare: (
s: Extract<ImportState, { activeState: 'prepare' }>,
opts: { apis: StepperApis },
opts: {
apis: StepperApis;
t: TranslationFunction<typeof catalogImportTranslationRef.T>;
},
) => StepConfiguration;
review: (
s: Extract<ImportState, { activeState: 'review' }>,
opts: { apis: StepperApis },
opts: {
apis: StepperApis;
t: TranslationFunction<typeof catalogImportTranslationRef.T>;
},
) => StepConfiguration;
finish: (
s: Extract<ImportState, { activeState: 'finish' }>,
opts: { apis: StepperApis },
opts: {
apis: StepperApis;
t: TranslationFunction<typeof catalogImportTranslationRef.T>;
},
) => StepConfiguration;
}
@@ -77,6 +91,7 @@ export interface StepperProvider {
export function defaultGenerateStepper(
flow: ImportFlows,
defaults: StepperProvider,
t: TranslationFunction<typeof catalogImportTranslationRef.T>,
): StepperProvider {
switch (flow) {
// the prepare step is skipped but the label of the step is updated
@@ -88,11 +103,11 @@ export function defaultGenerateStepper(
<StepLabel
optional={
<Typography variant="caption">
Discovered Locations: 1
{t('importStepper.singleLocation.description')}
</Typography>
}
>
Select Locations
{t('importStepper.singleLocation.title')}
</StepLabel>
),
content: <></>,
@@ -113,11 +128,13 @@ export function defaultGenerateStepper(
<StepLabel
optional={
<Typography variant="caption">
Discovered Locations: {state.analyzeResult.locations.length}
{t('importStepper.multipleLocations.description', {
length: state.analyzeResult.locations.length,
})}
</Typography>
}
>
Select Locations
{t('importStepper.multipleLocations.title')}
</StepLabel>
),
content: (
@@ -141,7 +158,9 @@ export function defaultGenerateStepper(
}
return {
stepLabel: <StepLabel>Create Pull Request</StepLabel>,
stepLabel: (
<StepLabel>{t('importStepper.noLocation.title')}</StepLabel>
),
content: (
<StepPrepareCreatePullRequest
analyzeResult={state.analyzeResult}
@@ -157,7 +176,9 @@ export function defaultGenerateStepper(
}) => (
<>
<Box marginTop={2}>
<Typography variant="h6">Pull Request Details</Typography>
<Typography variant="h6">
{t('importStepper.noLocation.createPr.detailsTitle')}
</Typography>
</Box>
<TextField
@@ -166,8 +187,10 @@ export function defaultGenerateStepper(
required: true,
}),
)}
label="Pull Request Title"
placeholder="Add Backstage catalog entity descriptor files"
label={t('importStepper.noLocation.createPr.titleLabel')}
placeholder={t(
'importStepper.noLocation.createPr.titlePlaceholder',
)}
margin="normal"
variant="outlined"
fullWidth
@@ -181,8 +204,10 @@ export function defaultGenerateStepper(
required: true,
}),
)}
label="Pull Request Body"
placeholder="A describing text with Markdown support"
label={t('importStepper.noLocation.createPr.bodyLabel')}
placeholder={t(
'importStepper.noLocation.createPr.bodyPlaceholder',
)}
margin="normal"
variant="outlined"
fullWidth
@@ -192,15 +217,23 @@ export function defaultGenerateStepper(
/>
<Box marginTop={2}>
<Typography variant="h6">Entity Configuration</Typography>
<Typography variant="h6">
{t(
'importStepper.noLocation.createPr.configurationTitle',
)}
</Typography>
</Box>
<TextField
{...asInputRef(
register('componentName', { required: true }),
)}
label="Name of the created component"
placeholder="my-component"
label={t(
'importStepper.noLocation.createPr.componentNameLabel',
)}
placeholder={t(
'importStepper.noLocation.createPr.componentNamePlaceholder',
)}
margin="normal"
variant="outlined"
fullWidth
@@ -214,12 +247,22 @@ export function defaultGenerateStepper(
errors={formState.errors}
options={groups || []}
loading={groupsLoading}
loadingText="Loading groups…"
helperText="Select an owner from the list or enter a reference to a Group or a User"
errorHelperText="required value"
loadingText={t(
'importStepper.noLocation.createPr.ownerLoadingText',
)}
helperText={t(
'importStepper.noLocation.createPr.ownerHelperText',
)}
errorHelperText={t(
'importStepper.noLocation.createPr.ownerErrorHelperText',
)}
textFieldProps={{
label: 'Entity Owner',
placeholder: 'my-group',
label: t(
'importStepper.noLocation.createPr.ownerLabel',
),
placeholder: t(
'importStepper.noLocation.createPr.ownerPlaceholder',
),
}}
rules={{ required: true }}
required
@@ -244,8 +287,9 @@ export function defaultGenerateStepper(
}
/>
<FormHelperText>
WARNING: This may fail if no CODEOWNERS file is found at
the target location.
{t(
'importStepper.noLocation.createPr.codeownersHelperText',
)}
</FormHelperText>
</>
)}
@@ -261,8 +305,8 @@ export function defaultGenerateStepper(
}
export const defaultStepper: StepperProvider = {
analyze: (state, { apis }) => ({
stepLabel: <StepLabel>Select URL</StepLabel>,
analyze: (state, { apis, t }) => ({
stepLabel: <StepLabel>{t('importStepper.analyze.title')}</StepLabel>,
content: (
<StepInitAnalyzeUrl
key="analyze"
@@ -273,17 +317,23 @@ export const defaultStepper: StepperProvider = {
),
}),
prepare: state => ({
prepare: (state, { t }) => ({
stepLabel: (
<StepLabel optional={<Typography variant="caption">Optional</Typography>}>
Import Actions
<StepLabel
optional={
<Typography variant="caption">
{t('importStepper.prepare.description')}
</Typography>
}
>
{t('importStepper.prepare.title')}
</StepLabel>
),
content: <BackButton onClick={state.onGoBack} />,
}),
review: state => ({
stepLabel: <StepLabel>Review</StepLabel>,
review: (state, { t }) => ({
stepLabel: <StepLabel>{t('importStepper.review.title')}</StepLabel>,
content: (
<StepReviewLocation
prepareResult={state.prepareResult}
@@ -293,8 +343,8 @@ export const defaultStepper: StepperProvider = {
),
}),
finish: state => ({
stepLabel: <StepLabel>Finish</StepLabel>,
finish: (state, { t }) => ({
stepLabel: <StepLabel>{t('importStepper.finish.title')}</StepLabel>,
content: (
<StepFinishImportLocation
prepareResult={state.prepareResult}
@@ -14,17 +14,20 @@
* limitations under the License.
*/
import { CompoundEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { Link } from '@backstage/core-components';
import { useRouteRef } from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { entityRouteRef } from '@backstage/plugin-catalog-react';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import LocationOnIcon from '@material-ui/icons/LocationOn';
import partition from 'lodash/partition';
import { catalogImportTranslationRef } from '../../translation';
import { BackButton, ViewComponentButton } from '../Buttons';
import { EntityListComponent } from '../EntityListComponent';
import { PrepareResult } from '../useImportState';
import { Link } from '@backstage/core-components';
import partition from 'lodash/partition';
import { CompoundEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { entityRouteRef } from '@backstage/plugin-catalog-react';
import { useRouteRef } from '@backstage/core-plugin-api';
type Props = {
prepareResult: PrepareResult;
@@ -60,13 +63,14 @@ const filterComponentEntity = (
};
export const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => {
const { t } = useTranslationRef(catalogImportTranslationRef);
const entityRoute = useRouteRef(entityRouteRef);
if (prepareResult.type === 'repository') {
return (
<>
<Typography paragraph>
The following Pull Request has been opened:{' '}
{t('stepFinishImportLocation.repository.title')}
<Link
to={prepareResult.pullRequest.url}
target="_blank"
@@ -76,10 +80,12 @@ export const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => {
</Link>
</Typography>
<Typography paragraph>
Your entities will be imported as soon as the Pull Request is merged.
{t('stepFinishImportLocation.repository.description')}
</Typography>
<Grid container spacing={0}>
<BackButton onClick={onReset}>Register another</BackButton>
<BackButton onClick={onReset}>
{t('stepFinishImportLocation.backButtonText')}
</BackButton>
</Grid>
</>
);
@@ -94,9 +100,7 @@ export const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => {
<>
{newLocations.length > 0 && (
<>
<Typography>
The following entities have been added to the catalog:
</Typography>
<Typography>{t('stepFinishImportLocation.locations.new')}</Typography>
<EntityListComponent
locations={newLocations}
@@ -108,7 +112,7 @@ export const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => {
{existingLocations.length > 0 && (
<>
<Typography>
A refresh was triggered for the following locations:
{t('stepFinishImportLocation.locations.existing')}
</Typography>
<EntityListComponent
@@ -121,10 +125,12 @@ export const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => {
<Grid container spacing={0}>
{newComponentEntity && (
<ViewComponentButton to={entityRoute(newComponentEntity)}>
View Component
{t('stepFinishImportLocation.locations.viewButtonText')}
</ViewComponentButton>
)}
<BackButton onClick={onReset}>Register another</BackButton>
<BackButton onClick={onReset}>
{t('stepFinishImportLocation.backButtonText')}
</BackButton>
</Grid>
</>
);
@@ -15,8 +15,8 @@
*/
import { errorApiRef } from '@backstage/core-plugin-api';
import { TestApiProvider } from '@backstage/test-utils';
import { act, render, screen } from '@testing-library/react';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ReactNode } from 'react';
import { AnalyzeResult, catalogImportApiRef } from '../../api/';
@@ -60,23 +60,24 @@ describe('<StepInitAnalyzeUrl />', () => {
});
it('renders without exploding', async () => {
render(<StepInitAnalyzeUrl onAnalysis={() => undefined} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={() => undefined} />
</Wrapper>,
);
expect(screen.getByRole('textbox', { name: /URL/i })).toBeInTheDocument();
expect(screen.getByRole('textbox', { name: /URL/i })).toHaveValue('');
});
it('should use default analysis url', async () => {
render(
<StepInitAnalyzeUrl
onAnalysis={() => undefined}
analysisUrl="https://default"
/>,
{
wrapper: Wrapper,
},
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl
onAnalysis={() => undefined}
analysisUrl="https://default"
/>
</Wrapper>,
);
expect(screen.getByRole('textbox', { name: /URL/i })).toBeInTheDocument();
@@ -88,9 +89,11 @@ describe('<StepInitAnalyzeUrl />', () => {
it('should not analyze without url', async () => {
const onAnalysisFn = jest.fn();
render(<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />
</Wrapper>,
);
await act(async () => {
try {
@@ -108,9 +111,11 @@ describe('<StepInitAnalyzeUrl />', () => {
it('should not analyze invalid value', async () => {
const onAnalysisFn = jest.fn();
render(<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />
</Wrapper>,
);
await act(async () => {
await userEvent.type(
@@ -136,9 +141,11 @@ describe('<StepInitAnalyzeUrl />', () => {
locations: [location],
} as AnalyzeResult;
render(<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />
</Wrapper>,
);
catalogImportApi.analyzeUrl.mockReturnValueOnce(
Promise.resolve(analyzeResult),
@@ -170,9 +177,11 @@ describe('<StepInitAnalyzeUrl />', () => {
locations: [location, location],
} as AnalyzeResult;
render(<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />
</Wrapper>,
);
catalogImportApi.analyzeUrl.mockReturnValueOnce(
Promise.resolve(analyzeResult),
@@ -203,9 +212,11 @@ describe('<StepInitAnalyzeUrl />', () => {
locations: [],
} as AnalyzeResult;
render(<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />
</Wrapper>,
);
catalogImportApi.analyzeUrl.mockReturnValueOnce(
Promise.resolve(analyzeResult),
@@ -244,9 +255,11 @@ describe('<StepInitAnalyzeUrl />', () => {
],
} as AnalyzeResult;
render(<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />
</Wrapper>,
);
catalogImportApi.analyzeUrl.mockReturnValueOnce(
Promise.resolve(analyzeResult),
@@ -279,9 +292,11 @@ describe('<StepInitAnalyzeUrl />', () => {
generatedEntities: [],
} as AnalyzeResult;
render(<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />
</Wrapper>,
);
catalogImportApi.analyzeUrl.mockReturnValueOnce(
Promise.resolve(analyzeResult),
@@ -320,11 +335,10 @@ describe('<StepInitAnalyzeUrl />', () => {
],
} as AnalyzeResult;
render(
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} disablePullRequest />,
{
wrapper: Wrapper,
},
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} disablePullRequest />
</Wrapper>,
);
catalogImportApi.analyzeUrl.mockReturnValueOnce(
@@ -349,9 +363,11 @@ describe('<StepInitAnalyzeUrl />', () => {
it('should report unknown type to the errorapi', async () => {
const onAnalysisFn = jest.fn();
render(<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />, {
wrapper: Wrapper,
});
await renderInTestApp(
<Wrapper>
<StepInitAnalyzeUrl onAnalysis={onAnalysisFn} />
</Wrapper>,
);
catalogImportApi.analyzeUrl.mockReturnValueOnce(
Promise.resolve({ type: 'unknown' } as any as AnalyzeResult),
@@ -15,12 +15,15 @@
*/
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import FormHelperText from '@material-ui/core/FormHelperText';
import Grid from '@material-ui/core/Grid';
import TextField from '@material-ui/core/TextField';
import { useCallback, useState } from 'react';
import { useForm } from 'react-hook-form';
import { AnalyzeResult, catalogImportApiRef } from '../../api';
import { catalogImportTranslationRef } from '../../translation';
import { NextButton } from '../Buttons';
import { asInputRef } from '../helpers';
import { ImportFlows, PrepareResult } from '../useImportState';
@@ -55,6 +58,7 @@ export interface StepInitAnalyzeUrlProps {
* @public
*/
export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => {
const { t } = useTranslationRef(catalogImportTranslationRef);
const {
onAnalysis,
analysisUrl = '',
@@ -95,7 +99,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => {
) {
onAnalysis('no-location', url, analysisResult);
} else {
setError("Couldn't generate entities for your repository");
setError(t('stepInitAnalyzeUrl.error.repository'));
setSubmitted(false);
}
break;
@@ -108,16 +112,16 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => {
} else if (analysisResult.locations.length > 1) {
onAnalysis('multiple-locations', url, analysisResult);
} else {
setError('There are no entities at this location');
setError(t('stepInitAnalyzeUrl.error.locations'));
setSubmitted(false);
}
break;
}
default: {
const err = `Received unknown analysis result of type ${
(analysisResult as any).type
}. Please contact the support team.`;
const err = t('stepInitAnalyzeUrl.error.default', {
type: (analysisResult as any).type,
});
setError(err);
setSubmitted(false);
@@ -130,7 +134,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => {
setSubmitted(false);
}
},
[catalogImportApi, disablePullRequest, errorApi, onAnalysis],
[catalogImportApi, disablePullRequest, errorApi, onAnalysis, t],
);
return (
@@ -143,7 +147,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => {
httpsValidator: (value: any) =>
(typeof value === 'string' &&
value.match(/^http[s]?:\/\//) !== null) ||
'Must start with http:// or https://.',
t('stepInitAnalyzeUrl.error.url'),
},
}),
)}
@@ -151,7 +155,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => {
id="url"
label="URL"
placeholder={exampleLocationUrl}
helperText="Enter the full path to your entity file to start tracking your component"
helperText={t('stepInitAnalyzeUrl.urlHelperText')}
margin="normal"
variant="outlined"
error={Boolean(errors.url)}
@@ -167,7 +171,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => {
loading={submitted}
type="submit"
>
Analyze
{t('stepInitAnalyzeUrl.nextButtonText')}
</NextButton>
</Grid>
</form>
@@ -14,8 +14,9 @@
* limitations under the License.
*/
import { renderInTestApp } from '@backstage/test-utils';
import { makeStyles } from '@material-ui/core/styles';
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { renderHook } from '@testing-library/react';
import { PreviewPullRequestComponent } from './PreviewPullRequestComponent';
@@ -27,7 +28,7 @@ const useStyles = makeStyles({
describe('<PreviewPullRequestComponent />', () => {
it('renders without exploding', async () => {
render(
await renderInTestApp(
<PreviewPullRequestComponent
title="My Title"
description="My **description**"
@@ -45,7 +46,7 @@ describe('<PreviewPullRequestComponent />', () => {
it('renders card with custom styles', async () => {
const { result } = renderHook(() => useStyles());
render(
await renderInTestApp(
<PreviewPullRequestComponent
title="My Title"
description="My **description**"
@@ -56,15 +57,21 @@ describe('<PreviewPullRequestComponent />', () => {
const title = screen.getByText('My Title');
const description = screen.getByText('description', { selector: 'strong' });
expect(title).toBeInTheDocument();
expect(title).not.toBeVisible();
expect(description).toBeInTheDocument();
expect(description).not.toBeVisible();
// FIXME: https://github.com/testing-library/jest-dom/issues/444
// expect(title).not.toBeVisible();
// expect(description).not.toBeVisible();
const card = title.closest(`.${result.current.displayNone}`);
expect(card).toBeInTheDocument();
expect(description.closest(`.${result.current.displayNone}`)).toBe(card);
});
it('renders with custom styles', async () => {
const { result } = renderHook(() => useStyles());
render(
await renderInTestApp(
<PreviewPullRequestComponent
title="My Title"
description="My **description**"
@@ -14,10 +14,13 @@
* limitations under the License.
*/
import { MarkdownContent } from '@backstage/core-components';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import CardHeader from '@material-ui/core/CardHeader';
import { MarkdownContent } from '@backstage/core-components';
import { catalogImportTranslationRef } from '../../translation';
/**
* Props for {@link PreviewPullRequestComponent}.
@@ -39,9 +42,13 @@ export const PreviewPullRequestComponent = (
props: PreviewPullRequestComponentProps,
) => {
const { title, description, classes } = props;
const { t } = useTranslationRef(catalogImportTranslationRef);
return (
<Card variant="outlined" className={classes?.card}>
<CardHeader title={title} subheader="Create a new Pull Request" />
<CardHeader
title={title}
subheader={t('stepPrepareCreatePullRequest.previewPr.subheader')}
/>
<CardContent className={classes?.cardContent}>
<MarkdownContent content={description} />
</CardContent>
@@ -16,9 +16,14 @@
import { configApiRef, errorApiRef } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { TestApiProvider, mockApis } from '@backstage/test-utils';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
import {
mockApis,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
import TextField from '@material-ui/core/TextField';
import { render, screen, waitFor } from '@testing-library/react';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ReactNode } from 'react';
import { AnalyzeResult, catalogImportApiRef } from '../../api';
@@ -27,7 +32,6 @@ import {
generateEntities,
StepPrepareCreatePullRequest,
} from './StepPrepareCreatePullRequest';
import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
describe('<StepPrepareCreatePullRequest />', () => {
const catalogImportApi: jest.Mocked<typeof catalogImportApiRef.T> = {
@@ -90,24 +94,23 @@ describe('<StepPrepareCreatePullRequest />', () => {
it('renders without exploding', async () => {
catalogApi.getEntities.mockReturnValue(Promise.resolve({ items: [] }));
render(
<StepPrepareCreatePullRequest
analyzeResult={analyzeResult}
onPrepare={onPrepareFn}
renderFormFields={({ register }) => {
return (
<>
<TextField {...asInputRef(register('title'))} />
<TextField {...asInputRef(register('body'))} />
<TextField {...asInputRef(register('componentName'))} />
<TextField {...asInputRef(register('owner'))} />
</>
);
}}
/>,
{
wrapper: Wrapper,
},
await renderInTestApp(
<Wrapper>
<StepPrepareCreatePullRequest
analyzeResult={analyzeResult}
onPrepare={onPrepareFn}
renderFormFields={({ register }) => {
return (
<>
<TextField {...asInputRef(register('title'))} />
<TextField {...asInputRef(register('body'))} />
<TextField {...asInputRef(register('componentName'))} />
<TextField {...asInputRef(register('owner'))} />
</>
);
}}
/>
</Wrapper>,
);
const title = await screen.findByText('My title');
@@ -129,32 +132,31 @@ describe('<StepPrepareCreatePullRequest />', () => {
}),
);
render(
<StepPrepareCreatePullRequest
analyzeResult={analyzeResult}
onPrepare={onPrepareFn}
renderFormFields={({ register }) => {
return (
<>
<TextField {...asInputRef(register('title'))} />
<TextField {...asInputRef(register('body'))} />
<TextField
{...asInputRef(register('componentName'))}
id="name"
label="name"
/>
<TextField
{...asInputRef(register('owner'))}
id="owner"
label="owner"
/>
</>
);
}}
/>,
{
wrapper: Wrapper,
},
await renderInTestApp(
<Wrapper>
<StepPrepareCreatePullRequest
analyzeResult={analyzeResult}
onPrepare={onPrepareFn}
renderFormFields={({ register }) => {
return (
<>
<TextField {...asInputRef(register('title'))} />
<TextField {...asInputRef(register('body'))} />
<TextField
{...asInputRef(register('componentName'))}
id="name"
label="name"
/>
<TextField
{...asInputRef(register('owner'))}
id="owner"
label="owner"
/>
</>
);
}}
/>
</Wrapper>,
);
await userEvent.type(await screen.findByLabelText('name'), '-changed');
@@ -211,24 +213,23 @@ spec:
new Error('some error'),
);
render(
<StepPrepareCreatePullRequest
analyzeResult={analyzeResult}
onPrepare={onPrepareFn}
renderFormFields={({ register }) => {
return (
<>
<TextField {...asInputRef(register('title'))} />
<TextField {...asInputRef(register('body'))} />
<TextField {...asInputRef(register('componentName'))} />
<TextField {...asInputRef(register('owner'))} />
</>
);
}}
/>,
{
wrapper: Wrapper,
},
await renderInTestApp(
<Wrapper>
<StepPrepareCreatePullRequest
analyzeResult={analyzeResult}
onPrepare={onPrepareFn}
renderFormFields={({ register }) => {
return (
<>
<TextField {...asInputRef(register('title'))} />
<TextField {...asInputRef(register('body'))} />
<TextField {...asInputRef(register('componentName'))} />
<TextField {...asInputRef(register('owner'))} />
</>
);
}}
/>
</Wrapper>,
);
await userEvent.click(
@@ -256,15 +257,14 @@ spec:
}),
);
render(
<StepPrepareCreatePullRequest
analyzeResult={analyzeResult}
onPrepare={onPrepareFn}
renderFormFields={renderFormFieldsFn}
/>,
{
wrapper: Wrapper,
},
await renderInTestApp(
<Wrapper>
<StepPrepareCreatePullRequest
analyzeResult={analyzeResult}
onPrepare={onPrepareFn}
renderFormFields={renderFormFieldsFn}
/>
</Wrapper>,
);
await waitFor(() => {
@@ -17,6 +17,7 @@
import { Entity } from '@backstage/catalog-model';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import { assertError } from '@backstage/errors';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import {
catalogApiRef,
humanizeEntityRef,
@@ -30,8 +31,10 @@ import { ReactNode, useCallback, useEffect, useState } from 'react';
import { NestedValue, UseFormReturn } from 'react-hook-form';
import useAsync from 'react-use/esm/useAsync';
import YAML from 'yaml';
import { AnalyzeResult, catalogImportApiRef } from '../../api';
import { useCatalogFilename } from '../../hooks';
import { catalogImportTranslationRef } from '../../translation';
import { PartialEntity } from '../../types';
import { BackButton, NextButton } from '../Buttons';
import { PrepareResult } from '../useImportState';
@@ -127,6 +130,7 @@ export const StepPrepareCreatePullRequest = (
) => {
const { analyzeResult, onPrepare, onGoBack, renderFormFields } = props;
const { t } = useTranslationRef(catalogImportTranslationRef);
const classes = useStyles();
const catalogApi = useApi(catalogApiRef);
const catalogImportApi = useApi(catalogImportApiRef);
@@ -252,7 +256,9 @@ export const StepPrepareCreatePullRequest = (
})}
<Box marginTop={2}>
<Typography variant="h6">Preview Pull Request</Typography>
<Typography variant="h6">
{t('stepPrepareCreatePullRequest.previewPr.title')}
</Typography>
</Box>
<PreviewPullRequestComponent
@@ -265,7 +271,9 @@ export const StepPrepareCreatePullRequest = (
/>
<Box marginTop={2} marginBottom={1}>
<Typography variant="h6">Preview Entities</Typography>
<Typography variant="h6">
{t('stepPrepareCreatePullRequest.previewCatalogInfo.title')}
</Typography>
</Box>
<PreviewCatalogInfoComponent
@@ -296,7 +304,7 @@ export const StepPrepareCreatePullRequest = (
)}
loading={submitted}
>
Create PR
{t('stepPrepareCreatePullRequest.nextButtonText')}
</NextButton>
</Grid>
</>
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import Checkbox from '@material-ui/core/Checkbox';
import Grid from '@material-ui/core/Grid';
import ListItem from '@material-ui/core/ListItem';
@@ -21,12 +22,14 @@ import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import Typography from '@material-ui/core/Typography';
import LocationOnIcon from '@material-ui/icons/LocationOn';
import partition from 'lodash/partition';
import { useCallback, useState } from 'react';
import { AnalyzeResult } from '../../api';
import { catalogImportTranslationRef } from '../../translation';
import { BackButton, NextButton } from '../Buttons';
import { EntityListComponent } from '../EntityListComponent';
import { PrepareResult } from '../useImportState';
import partition from 'lodash/partition';
type Props = {
analyzeResult: Extract<AnalyzeResult, { type: 'locations' }>;
@@ -49,6 +52,8 @@ export const StepPrepareSelectLocations = ({
onPrepare,
onGoBack,
}: Props) => {
const { t } = useTranslationRef(catalogImportTranslationRef);
const [selectedUrls, setSelectedUrls] = useState<string[]>(
prepareResult?.locations.map(l => l.target) || [],
);
@@ -82,8 +87,7 @@ export const StepPrepareSelectLocations = ({
{locations.length > 0 && (
<>
<Typography>
Select one or more locations that are present in your git
repository:
{t('stepPrepareSelectLocations.locations.description')}
</Typography>
<EntityListComponent
firstListItem={
@@ -100,7 +104,9 @@ export const StepPrepareSelectLocations = ({
disableRipple
/>
</ListItemIcon>
<ListItemText primary="Select All" />
<ListItemText
primary={t('stepPrepareSelectLocations.locations.selectAll')}
/>
</ListItem>
}
onItemClick={onItemClick}
@@ -120,7 +126,9 @@ export const StepPrepareSelectLocations = ({
{existingLocations.length > 0 && (
<>
<Typography>These locations already exist in the catalog:</Typography>
<Typography>
{t('stepPrepareSelectLocations.existingLocations.description')}
</Typography>
<EntityListComponent
locations={existingLocations}
locationListItemIcon={() => <LocationOnIcon />}
@@ -133,7 +141,7 @@ export const StepPrepareSelectLocations = ({
<Grid container spacing={0}>
{onGoBack && <BackButton onClick={onGoBack} />}
<NextButton disabled={selectedUrls.length === 0} onClick={handleResult}>
Review
{t('stepPrepareSelectLocations.nextButtonText')}
</NextButton>
</Grid>
</>
@@ -14,20 +14,22 @@
* limitations under the License.
*/
import { stringifyEntityRef } from '@backstage/catalog-model';
import { Link } from '@backstage/core-components';
import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api';
import { assertError } from '@backstage/errors';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import FormHelperText from '@material-ui/core/FormHelperText';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import LocationOnIcon from '@material-ui/icons/LocationOn';
import { useCallback, useState } from 'react';
import { BackButton, NextButton } from '../Buttons';
import { EntityListComponent } from '../EntityListComponent';
import { PrepareResult, ReviewResult } from '../useImportState';
import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api';
import { Link } from '@backstage/core-components';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { assertError } from '@backstage/errors';
import { catalogImportTranslationRef } from '../../translation';
type Props = {
prepareResult: PrepareResult;
@@ -40,6 +42,7 @@ export const StepReviewLocation = ({
onReview,
onGoBack,
}: Props) => {
const { t } = useTranslationRef(catalogImportTranslationRef);
const catalogApi = useApi(catalogApiRef);
const configApi = useApi(configApiRef);
const analytics = useAnalytics();
@@ -119,7 +122,7 @@ export const StepReviewLocation = ({
{prepareResult.type === 'repository' && (
<>
<Typography paragraph>
The following Pull Request has been opened:{' '}
{t('stepReviewLocation.prepareResult.title')}
<Link
to={prepareResult.pullRequest.url}
target="_blank"
@@ -130,16 +133,15 @@ export const StepReviewLocation = ({
</Typography>
<Typography paragraph>
You can already import the location and {appTitle} will fetch the
entities as soon as the Pull Request is merged.
{t('stepReviewLocation.prepareResult.description', { appTitle })}
</Typography>
</>
)}
<Typography>
{exists
? 'The following locations already exist in the catalog:'
: 'The following entities will be added to the catalog:'}
? t('stepReviewLocation.catalog.exists')
: t('stepReviewLocation.catalog.new')}
</Typography>
<EntityListComponent
@@ -156,7 +158,9 @@ export const StepReviewLocation = ({
loading={submitted}
onClick={() => handleClick()}
>
{exists ? 'Refresh' : 'Import'}
{exists
? t('stepReviewLocation.refresh')
: t('stepReviewLocation.import')}
</NextButton>
</Grid>
</>
+124 -1
View File
@@ -20,9 +20,132 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha';
export const catalogImportTranslationRef = createTranslationRef({
id: 'catalog-import',
messages: {
pageTitle: 'Register an existing component',
buttons: {
back: 'Back',
},
defaultImportPage: {
headerTitle: 'Register an existing component',
contentHeaderTitle: 'Start tracking your component in {{appTitle}}',
supportTitle:
'Start tracking your component in {{appTitle}} by adding it to the software catalog.',
},
importInfoCard: {
title: 'Register an existing component',
deepLinkTitle: 'Learn more about the Software Catalog',
linkDescription:
'Enter the URL to your source code repository to add it to {{appTitle}}.',
fileLinkTitle: 'Link to an existing entity file',
examplePrefix: 'Example: ',
fileLinkDescription:
'The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.',
githubIntegration: {
title: 'Link to a repository',
label: 'GitHub only',
},
},
importStepper: {
singleLocation: {
title: 'Select Locations',
description: 'Discovered Locations: 1',
},
multipleLocations: {
title: 'Select Locations',
description: 'Discovered Locations: {{length, number}}',
},
noLocation: {
title: 'Create Pull Request',
createPr: {
detailsTitle: 'Pull Request Details',
titleLabel: 'Pull Request Title',
titlePlaceholder: 'Add Backstage catalog entity descriptor files',
bodyLabel: 'Pull Request Body',
bodyPlaceholder: 'A describing text with Markdown support',
configurationTitle: 'Entity Configuration',
componentNameLabel: 'Name of the created component',
componentNamePlaceholder: 'my-component',
ownerLoadingText: 'Loading groups…',
ownerHelperText:
'Select an owner from the list or enter a reference to a Group or a User',
ownerErrorHelperText: 'required value',
ownerLabel: 'Entity Owner',
ownerPlaceholder: 'my-group',
codeownersHelperText:
'WARNING: This may fail if no CODEOWNERS file is found at the target location.',
},
},
analyze: {
title: 'Select URL',
},
prepare: {
title: 'Import Actions',
description: 'Optional',
},
review: {
title: 'Review',
},
finish: {
title: 'Finish',
},
},
stepFinishImportLocation: {
backButtonText: 'Register another',
repository: {
title: 'The following Pull Request has been opened: ',
description:
'Your entities will be imported as soon as the Pull Request is merged.',
},
locations: {
new: 'The following entities have been added to the catalog:',
existing: 'A refresh was triggered for the following locations:',
viewButtonText: 'View Component',
backButtonText: 'Register another',
},
},
stepInitAnalyzeUrl: {
error: {
repository: "Couldn't generate entities for your repository",
locations: 'There are no entities at this location',
default:
'Received unknown analysis result of type {{type}}. Please contact the support team.',
url: 'Must start with http:// or https://.',
},
urlHelperText:
'Enter the full path to your entity file to start tracking your component',
nextButtonText: 'Analyze',
},
stepPrepareCreatePullRequest: {
previewPr: {
title: 'Preview Pull Request',
subheader: 'Create a new Pull Request',
},
previewCatalogInfo: {
title: 'Preview Entities',
},
nextButtonText: 'Create PR',
},
stepPrepareSelectLocations: {
locations: {
description:
'Select one or more locations that are present in your git repository:',
selectAll: 'Select All',
},
existingLocations: {
description: 'These locations already exist in the catalog:',
},
nextButtonText: 'Review',
},
stepReviewLocation: {
prepareResult: {
title: 'The following Pull Request has been opened: ',
description:
'You can already import the location and {{appTitle}} will fetch the entities as soon as the Pull Request is merged.',
},
catalog: {
exists: 'The following locations already exist in the catalog:',
new: 'The following entities will be added to the catalog:',
},
refresh: 'Refresh',
import: 'Import',
},
},
});
+1 -1
View File
@@ -344,8 +344,8 @@ export const scaffolderReactTranslationRef: TranslationRef<
readonly 'stepper.backButtonText': 'Back';
readonly 'stepper.createButtonText': 'Create';
readonly 'stepper.reviewButtonText': 'Review';
readonly 'stepper.stepIndexLabel': 'Step {{index, number}}';
readonly 'stepper.nextButtonText': 'Next';
readonly 'stepper.stepIndexLabel': 'Step {{index, number}}';
readonly 'templateCategoryPicker.title': 'Categories';
readonly 'templateCard.noDescription': 'No description';
readonly 'templateCard.chooseButtonText': 'Choose';