feat: add i18n for scaffolder
Signed-off-by: mario ma <mario.ma.node@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
add i18n for scaffolder
|
||||
@@ -106,3 +106,5 @@ export default createFrontendPlugin({
|
||||
}),
|
||||
extensions: [scaffolderApi, scaffolderPage, scaffolderNavItem],
|
||||
});
|
||||
|
||||
export * from './translation';
|
||||
@@ -58,6 +58,8 @@ import {
|
||||
rootRouteRef,
|
||||
scaffolderListTaskRouteRef,
|
||||
} from '../../routes';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
code: {
|
||||
@@ -115,6 +117,7 @@ const ExamplesTable = (props: { examples: ActionExample[] }) => {
|
||||
|
||||
const ActionPageContent = () => {
|
||||
const api = useApi(scaffolderApiRef);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const classes = useStyles();
|
||||
const { loading, value, error } = useAsync(async () => {
|
||||
@@ -132,8 +135,8 @@ const ActionPageContent = () => {
|
||||
<ErrorPanel error={error} />
|
||||
<EmptyState
|
||||
missing="info"
|
||||
title="No information to display"
|
||||
description="There are no actions installed or there was an issue communicating with backend."
|
||||
title={t('actionsPage.content.emptyState.title')}
|
||||
description={t('actionsPage.content.emptyState.description')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -141,17 +144,21 @@ const ActionPageContent = () => {
|
||||
|
||||
const renderTable = (rows?: JSX.Element[]) => {
|
||||
if (!rows || rows.length < 1) {
|
||||
return <Typography>No schema defined</Typography>;
|
||||
return (
|
||||
<Typography>{t('actionsPage.content.noRowsDescription')}</Typography>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Title</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>{t('actionsPage.content.tableCell.name')}</TableCell>
|
||||
<TableCell>{t('actionsPage.content.tableCell.title')}</TableCell>
|
||||
<TableCell>
|
||||
{t('actionsPage.content.tableCell.description')}
|
||||
</TableCell>
|
||||
<TableCell>{t('actionsPage.content.tableCell.type')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>{rows}</TableBody>
|
||||
@@ -295,7 +302,7 @@ const ActionPageContent = () => {
|
||||
{action.schema?.input && (
|
||||
<Box pb={2}>
|
||||
<Typography variant="h5" component="h3">
|
||||
Input
|
||||
{t('actionsPage.action.input')}
|
||||
</Typography>
|
||||
{renderTable(
|
||||
formatRows(`${action.id}.input`, action?.schema?.input),
|
||||
@@ -306,7 +313,7 @@ const ActionPageContent = () => {
|
||||
{action.schema?.output && (
|
||||
<Box pb={2}>
|
||||
<Typography variant="h5" component="h3">
|
||||
Output
|
||||
{t('actionsPage.action.output')}
|
||||
</Typography>
|
||||
{renderTable(
|
||||
formatRows(`${action.id}.output`, action?.schema?.output),
|
||||
@@ -317,7 +324,7 @@ const ActionPageContent = () => {
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography variant="h5" component="h3">
|
||||
Examples
|
||||
{t('actionsPage.action.examples')}
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
@@ -336,6 +343,7 @@ export const ActionsPage = () => {
|
||||
const editorLink = useRouteRef(editRouteRef);
|
||||
const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
|
||||
const createLink = useRouteRef(rootRouteRef);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const scaffolderPageContextMenuProps = {
|
||||
onEditorClicked: () => navigate(editorLink()),
|
||||
@@ -347,9 +355,9 @@ export const ActionsPage = () => {
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride="Create a New Component"
|
||||
title="Installed actions"
|
||||
subtitle="This is the collection of all installed actions"
|
||||
pageTitleOverride={t('actionsPage.pageTitle')}
|
||||
title={t('actionsPage.title')}
|
||||
subtitle={t('actionsPage.subtitle')}
|
||||
>
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</Header>
|
||||
|
||||
@@ -41,6 +41,8 @@ import {
|
||||
import { actionsRouteRef, editRouteRef, rootRouteRef } from '../../routes';
|
||||
import { ScaffolderPageContextMenu } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
export interface MyTaskPageProps {
|
||||
initiallySelectedFilter?: 'owned' | 'all';
|
||||
@@ -48,6 +50,7 @@ export interface MyTaskPageProps {
|
||||
|
||||
const ListTaskPageContent = (props: MyTaskPageProps) => {
|
||||
const { initiallySelectedFilter = 'owned' } = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
const rootLink = useRouteRef(rootRouteRef);
|
||||
@@ -76,8 +79,8 @@ const ListTaskPageContent = (props: MyTaskPageProps) => {
|
||||
<ErrorPanel error={error} />
|
||||
<EmptyState
|
||||
missing="info"
|
||||
title="No information to display"
|
||||
description="There are no tasks or there was an issue communicating with backend."
|
||||
title={t('listTaskPage.content.emptyState.title')}
|
||||
description={t('listTaskPage.content.emptyState.description')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -94,17 +97,17 @@ const ListTaskPageContent = (props: MyTaskPageProps) => {
|
||||
<CatalogFilterLayout.Content>
|
||||
<Table<ScaffolderTask>
|
||||
data={value?.tasks ?? []}
|
||||
title="Tasks"
|
||||
title={t('listTaskPage.content.tableTitle')}
|
||||
columns={[
|
||||
{
|
||||
title: 'Task ID',
|
||||
title: t('listTaskPage.content.tableCell.taskID'),
|
||||
field: 'id',
|
||||
render: row => (
|
||||
<Link to={`${rootLink()}/tasks/${row.id}`}>{row.id}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Template',
|
||||
title: t('listTaskPage.content.tableCell.template'),
|
||||
field: 'spec.templateInfo.entity.metadata.title',
|
||||
render: row => (
|
||||
<TemplateTitleColumn
|
||||
@@ -113,19 +116,19 @@ const ListTaskPageContent = (props: MyTaskPageProps) => {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Created',
|
||||
title: t('listTaskPage.content.tableCell.created'),
|
||||
field: 'createdAt',
|
||||
render: row => <CreatedAtColumn createdAt={row.createdAt} />,
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
title: t('listTaskPage.content.tableCell.owner'),
|
||||
field: 'createdBy',
|
||||
render: row => (
|
||||
<OwnerEntityColumn entityRef={row.spec?.user?.ref} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
title: t('listTaskPage.content.tableCell.status'),
|
||||
field: 'status',
|
||||
render: row => <TaskStatusColumn status={row.status} />,
|
||||
},
|
||||
@@ -141,6 +144,7 @@ export const ListTasksPage = (props: MyTaskPageProps) => {
|
||||
const editorLink = useRouteRef(editRouteRef);
|
||||
const actionsLink = useRouteRef(actionsRouteRef);
|
||||
const createLink = useRouteRef(rootRouteRef);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const scaffolderPageContextMenuProps = {
|
||||
onEditorClicked: () => navigate(editorLink()),
|
||||
@@ -151,9 +155,9 @@ export const ListTasksPage = (props: MyTaskPageProps) => {
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride="Templates Tasks"
|
||||
title="List template tasks"
|
||||
subtitle="All tasks that have been started"
|
||||
pageTitleOverride={t('listTaskPage.pageTitle')}
|
||||
title={t('listTaskPage.title')}
|
||||
subtitle={t('listTaskPage.subtitle')}
|
||||
>
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</Header>
|
||||
|
||||
@@ -23,6 +23,11 @@ import Typography from '@material-ui/core/Typography';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import React, { Fragment } from 'react';
|
||||
import {
|
||||
TranslationFunction,
|
||||
useTranslationRef,
|
||||
} from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
import AllIcon from '@material-ui/icons/FontDownload';
|
||||
|
||||
@@ -64,19 +69,21 @@ export type ButtonGroup = {
|
||||
}[];
|
||||
};
|
||||
|
||||
function getFilterGroups(): ButtonGroup[] {
|
||||
function getFilterGroups(
|
||||
t: TranslationFunction<typeof scaffolderTranslationRef.T>,
|
||||
): ButtonGroup[] {
|
||||
return [
|
||||
{
|
||||
name: 'Task Owner',
|
||||
name: t('ownerListPicker.title'),
|
||||
items: [
|
||||
{
|
||||
id: 'owned',
|
||||
label: 'Owned',
|
||||
label: t('ownerListPicker.options.owned'),
|
||||
icon: SettingsIcon,
|
||||
},
|
||||
{
|
||||
id: 'all',
|
||||
label: 'All',
|
||||
label: t('ownerListPicker.options.all'),
|
||||
icon: AllIcon,
|
||||
},
|
||||
],
|
||||
@@ -90,8 +97,9 @@ export const OwnerListPicker = (props: {
|
||||
}) => {
|
||||
const { filter, onSelectOwner } = props;
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const filterGroups = getFilterGroups();
|
||||
const filterGroups = getFilterGroups(t);
|
||||
return (
|
||||
<Card className={classes.root}>
|
||||
{filterGroups.map(group => (
|
||||
|
||||
@@ -36,6 +36,8 @@ import {
|
||||
taskReadPermission,
|
||||
taskCreatePermission,
|
||||
} from '@backstage/plugin-scaffolder-common/alpha';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
type ContextMenuProps = {
|
||||
cancelEnabled?: boolean;
|
||||
@@ -69,6 +71,7 @@ export const ContextMenu = (props: ContextMenuProps) => {
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
const analytics = useAnalytics();
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => {
|
||||
if (taskId) {
|
||||
@@ -118,14 +121,24 @@ export const ContextMenu = (props: ContextMenuProps) => {
|
||||
<ListItemIcon>
|
||||
<Toc fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={logsVisible ? 'Hide Logs' : 'Show Logs'} />
|
||||
<ListItemText
|
||||
primary={
|
||||
logsVisible
|
||||
? t('ongoingTask.contextMenu.hideLogs')
|
||||
: t('ongoingTask.contextMenu.showLogs')
|
||||
}
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => onToggleButtonBar?.(!buttonBarVisible)}>
|
||||
<ListItemIcon>
|
||||
<ControlPointIcon fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={buttonBarVisible ? 'Hide Button Bar' : 'Show Button Bar'}
|
||||
primary={
|
||||
buttonBarVisible
|
||||
? t('ongoingTask.contextMenu.hideButtonBar')
|
||||
: t('ongoingTask.contextMenu.showButtonBar')
|
||||
}
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
@@ -136,7 +149,7 @@ export const ContextMenu = (props: ContextMenuProps) => {
|
||||
<ListItemIcon>
|
||||
<Retry fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Start Over" />
|
||||
<ListItemText primary={t('ongoingTask.contextMenu.startOver')} />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={cancel}
|
||||
@@ -150,7 +163,7 @@ export const ContextMenu = (props: ContextMenuProps) => {
|
||||
<ListItemIcon>
|
||||
<Cancel fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Cancel" />
|
||||
<ListItemText primary={t('ongoingTask.contextMenu.cancel')} />
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Popover>
|
||||
|
||||
@@ -42,6 +42,8 @@ import {
|
||||
taskReadPermission,
|
||||
taskCreatePermission,
|
||||
} from '@backstage/plugin-scaffolder-common/alpha';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
contentWrapper: {
|
||||
@@ -85,6 +87,7 @@ export const OngoingTask = (props: {
|
||||
})) ?? [],
|
||||
[taskStream],
|
||||
);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const [logsVisible, setLogVisibleState] = useState(false);
|
||||
const [buttonBarVisible, setButtonBarVisibleState] = useState(true);
|
||||
@@ -166,7 +169,7 @@ export const OngoingTask = (props: {
|
||||
const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs;
|
||||
|
||||
const templateName =
|
||||
taskStream.task?.spec.templateInfo?.entity?.metadata.name;
|
||||
taskStream.task?.spec.templateInfo?.entity?.metadata.name || '';
|
||||
|
||||
const cancelEnabled = !(taskStream.cancelled || taskStream.completed);
|
||||
|
||||
@@ -174,14 +177,16 @@ export const OngoingTask = (props: {
|
||||
<Page themeId="website">
|
||||
<Header
|
||||
pageTitleOverride={
|
||||
templateName ? `Run of ${templateName}` : `Scaffolder Run`
|
||||
templateName
|
||||
? t('ongoingTask.pageTitle.hasTemplateName', { templateName })
|
||||
: t('ongoingTask.pageTitle.noTemplateName')
|
||||
}
|
||||
title={
|
||||
<div>
|
||||
Run of <code>{templateName}</code>
|
||||
{t('ongoingTask.title')} <code>{templateName}</code>
|
||||
</div>
|
||||
}
|
||||
subtitle={`Task ${taskId}`}
|
||||
subtitle={t('ongoingTask.subtitle', { taskId: taskId as string })}
|
||||
>
|
||||
<ContextMenu
|
||||
cancelEnabled={cancelEnabled}
|
||||
@@ -230,7 +235,7 @@ export const OngoingTask = (props: {
|
||||
onClick={triggerCancel}
|
||||
data-testid="cancel-button"
|
||||
>
|
||||
Cancel
|
||||
{t('ongoingTask.cancelButtonTitle')}
|
||||
</Button>
|
||||
<Button
|
||||
className={classes.logsVisibilityButton}
|
||||
@@ -238,7 +243,9 @@ export const OngoingTask = (props: {
|
||||
variant="outlined"
|
||||
onClick={() => setLogVisibleState(!logsVisible)}
|
||||
>
|
||||
{logsVisible ? 'Hide Logs' : 'Show Logs'}
|
||||
{logsVisible
|
||||
? t('ongoingTask.hideLogsButtonTitle')
|
||||
: t('ongoingTask.showLogsButtonTitle')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -247,7 +254,7 @@ export const OngoingTask = (props: {
|
||||
onClick={startOver}
|
||||
data-testid="start-over-button"
|
||||
>
|
||||
Start Over
|
||||
{t('ongoingTask.startOverButtonTitle')}
|
||||
</Button>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
|
||||
import { GetEntityFacetsResponse } from '@backstage/catalog-client';
|
||||
|
||||
const entities: Entity[] = [
|
||||
@@ -86,7 +86,7 @@ const apis = TestApiRegistry.from(
|
||||
|
||||
describe('<TemplateTypePicker/>', () => {
|
||||
it('renders available entity types', async () => {
|
||||
const rendered = await renderWithEffects(
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<MockEntityListContextProvider
|
||||
value={{
|
||||
@@ -109,7 +109,7 @@ describe('<TemplateTypePicker/>', () => {
|
||||
});
|
||||
|
||||
it('sets the selected type filters', async () => {
|
||||
const rendered = await renderWithEffects(
|
||||
const rendered = await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<MockEntityListContextProvider
|
||||
value={{
|
||||
|
||||
@@ -28,6 +28,8 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import { useEntityTypeFilter } from '@backstage/plugin-catalog-react';
|
||||
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
|
||||
const checkedIcon = <CheckBoxIcon fontSize="small" />;
|
||||
@@ -41,6 +43,7 @@ export const TemplateTypePicker = () => {
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const { error, loading, availableTypes, selectedTypes, setSelectedTypes } =
|
||||
useEntityTypeFilter();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
if (loading) return <Progress />;
|
||||
|
||||
@@ -61,7 +64,7 @@ export const TemplateTypePicker = () => {
|
||||
component="label"
|
||||
htmlFor="categories-picker"
|
||||
>
|
||||
Categories
|
||||
{t('templateTypePicker.title')}
|
||||
</Typography>
|
||||
<Autocomplete<string, true>
|
||||
id="categories-picker"
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
import React from 'react';
|
||||
import { EntityNamePickerProps } from './schema';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export { EntityNamePickerSchema } from './schema';
|
||||
|
||||
@@ -23,10 +25,14 @@ export { EntityNamePickerSchema } from './schema';
|
||||
* EntityName Picker
|
||||
*/
|
||||
export const EntityNamePicker = (props: EntityNamePickerProps) => {
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const {
|
||||
onChange,
|
||||
required,
|
||||
schema: { title = 'Name', description = 'Unique name of the component' },
|
||||
schema: {
|
||||
title = t('fields.entityNamePicker.title'),
|
||||
description = t('fields.entityNamePicker.description'),
|
||||
},
|
||||
rawErrors,
|
||||
formData,
|
||||
uiSchema: { 'ui:autofocus': autoFocus },
|
||||
|
||||
@@ -44,6 +44,8 @@ import {
|
||||
EntityPickerFilterQuery,
|
||||
} from './schema';
|
||||
import { VirtualizedListbox } from '../VirtualizedListbox';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export { EntityPickerSchema } from './schema';
|
||||
|
||||
@@ -54,9 +56,13 @@ export { EntityPickerSchema } from './schema';
|
||||
* @public
|
||||
*/
|
||||
export const EntityPicker = (props: EntityPickerProps) => {
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const {
|
||||
onChange,
|
||||
schema: { title = 'Entity', description = 'An entity from the catalog' },
|
||||
schema: {
|
||||
title = t('fields.entityPicker.title'),
|
||||
description = t('fields.entityPicker.description'),
|
||||
},
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
|
||||
@@ -24,6 +24,8 @@ import FormControl from '@material-ui/core/FormControl';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import { EntityTagsPickerProps } from './schema';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export { EntityTagsPickerSchema } from './schema';
|
||||
|
||||
@@ -43,6 +45,7 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => {
|
||||
const kinds = uiSchema['ui:options']?.kinds;
|
||||
const showCounts = uiSchema['ui:options']?.showCounts;
|
||||
const helperText = uiSchema['ui:options']?.helperText;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const { loading, value: existingTags } = useAsync(async () => {
|
||||
const facet = 'metadata.tags';
|
||||
@@ -109,13 +112,10 @@ export const EntityTagsPicker = (props: EntityTagsPickerProps) => {
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Tags"
|
||||
label={t('fields.entityTagsPicker.title')}
|
||||
onChange={e => setInputValue(e.target.value)}
|
||||
error={inputError}
|
||||
helperText={
|
||||
helperText ??
|
||||
"Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters"
|
||||
}
|
||||
helperText={helperText ?? t('fields.entityTagsPicker.description')}
|
||||
FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { MyGroupsPicker } from './MyGroupsPicker';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import {
|
||||
catalogApiRef,
|
||||
entityPresentationApiRef,
|
||||
@@ -115,7 +115,7 @@ describe('<MyGroupsPicker />', () => {
|
||||
required,
|
||||
} as unknown as FieldProps<string>;
|
||||
|
||||
render(
|
||||
await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
@@ -191,7 +191,7 @@ describe('<MyGroupsPicker />', () => {
|
||||
required,
|
||||
} as unknown as FieldProps<string>;
|
||||
|
||||
const { queryByText, getByRole } = render(
|
||||
const { queryByText, getByRole } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
@@ -252,7 +252,7 @@ describe('<MyGroupsPicker />', () => {
|
||||
required,
|
||||
} as unknown as FieldProps<string>;
|
||||
|
||||
const { getByRole } = render(
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
@@ -316,7 +316,7 @@ describe('<MyGroupsPicker />', () => {
|
||||
formData: 'group:default/group1',
|
||||
} as unknown as FieldProps<string>;
|
||||
|
||||
const { getByRole } = render(
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
|
||||
@@ -36,12 +36,18 @@ import { NotFoundError } from '@backstage/errors';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { VirtualizedListbox } from '../VirtualizedListbox';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export { MyGroupsPickerSchema };
|
||||
|
||||
export const MyGroupsPicker = (props: MyGroupsPickerProps) => {
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const {
|
||||
schema: { title, description },
|
||||
schema: {
|
||||
title = t('fields.myGroupsPicker.title'),
|
||||
description = t('fields.myGroupsPicker.description'),
|
||||
},
|
||||
required,
|
||||
rawErrors,
|
||||
onChange,
|
||||
|
||||
@@ -23,6 +23,8 @@ import { EntityPicker } from '../EntityPicker/EntityPicker';
|
||||
|
||||
import { OwnedEntityPickerProps } from './schema';
|
||||
import { EntityPickerProps } from '../EntityPicker/schema';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export { OwnedEntityPickerSchema } from './schema';
|
||||
|
||||
@@ -33,8 +35,12 @@ export { OwnedEntityPickerSchema } from './schema';
|
||||
* @public
|
||||
*/
|
||||
export const OwnedEntityPicker = (props: OwnedEntityPickerProps) => {
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const {
|
||||
schema: { title = 'Entity', description = 'An entity from the catalog' },
|
||||
schema: {
|
||||
title = t('fields.ownedEntityPicker.title'),
|
||||
description = t('fields.ownedEntityPicker.description'),
|
||||
},
|
||||
uiSchema,
|
||||
required,
|
||||
} = props;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
import React from 'react';
|
||||
import { EntityPicker } from '../EntityPicker/EntityPicker';
|
||||
import { OwnerPickerProps } from './schema';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export { OwnerPickerSchema } from './schema';
|
||||
|
||||
@@ -26,8 +28,12 @@ export { OwnerPickerSchema } from './schema';
|
||||
* @public
|
||||
*/
|
||||
export const OwnerPicker = (props: OwnerPickerProps) => {
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const {
|
||||
schema: { title = 'Owner', description = 'The owner of the component' },
|
||||
schema: {
|
||||
title = t('fields.ownerPicker.title'),
|
||||
description = t('fields.ownerPicker.description'),
|
||||
},
|
||||
uiSchema,
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
import React from 'react';
|
||||
import { AzureRepoPicker } from './AzureRepoPicker';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('AzureRepoPicker', () => {
|
||||
it('renders the two input fields', async () => {
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<AzureRepoPicker onChange={jest.fn()} rawErrors={[]} state={{}} />,
|
||||
);
|
||||
|
||||
@@ -30,9 +31,9 @@ describe('AzureRepoPicker', () => {
|
||||
});
|
||||
|
||||
describe('org field', () => {
|
||||
it('calls onChange when the organisation changes', () => {
|
||||
it('calls onChange when the organisation changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<AzureRepoPicker onChange={onChange} rawErrors={[]} state={{}} />,
|
||||
);
|
||||
|
||||
@@ -45,9 +46,9 @@ describe('AzureRepoPicker', () => {
|
||||
});
|
||||
|
||||
describe('project field', () => {
|
||||
it('calls onChange when the project changes', () => {
|
||||
it('calls onChange when the project changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<AzureRepoPicker onChange={onChange} rawErrors={[]} state={{}} />,
|
||||
);
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { BaseRepoUrlPickerProps } from './types';
|
||||
import { Select, SelectItem } from '@backstage/core-components';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export const AzureRepoPicker = (
|
||||
props: BaseRepoUrlPickerProps<{
|
||||
@@ -34,6 +36,7 @@ export const AzureRepoPicker = (
|
||||
state,
|
||||
onChange,
|
||||
} = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const organizationItems: SelectItem[] = allowedOrganizations
|
||||
? allowedOrganizations.map(i => ({ label: i, value: i }))
|
||||
@@ -53,27 +56,30 @@ export const AzureRepoPicker = (
|
||||
error={rawErrors?.length > 0 && !organization}
|
||||
>
|
||||
{allowedOrganizations?.length ? (
|
||||
<Select
|
||||
native
|
||||
label="Organization"
|
||||
onChange={s =>
|
||||
onChange({ organization: String(Array.isArray(s) ? s[0] : s) })
|
||||
}
|
||||
disabled={allowedOrganizations.length === 1}
|
||||
selected={organization}
|
||||
items={organizationItems}
|
||||
/>
|
||||
<>
|
||||
<Select
|
||||
native
|
||||
label={t('fields.azureRepoPicker.organization.title')}
|
||||
onChange={s =>
|
||||
onChange({ organization: String(Array.isArray(s) ? s[0] : s) })
|
||||
}
|
||||
disabled={allowedOrganizations.length === 1}
|
||||
selected={organization}
|
||||
items={organizationItems}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{t('fields.azureRepoPicker.organization.description')}
|
||||
</FormHelperText>
|
||||
</>
|
||||
) : (
|
||||
<TextField
|
||||
id="orgInput"
|
||||
label="Organization"
|
||||
label={t('fields.azureRepoPicker.organization.title')}
|
||||
onChange={e => onChange({ organization: e.target.value })}
|
||||
helperText={t('fields.azureRepoPicker.organization.description')}
|
||||
value={organization}
|
||||
/>
|
||||
)}
|
||||
<FormHelperText>
|
||||
The Organization that this repo will belong to
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
margin="normal"
|
||||
@@ -81,27 +87,30 @@ export const AzureRepoPicker = (
|
||||
error={rawErrors?.length > 0 && !project}
|
||||
>
|
||||
{allowedProject?.length ? (
|
||||
<Select
|
||||
native
|
||||
label="Project"
|
||||
onChange={s =>
|
||||
onChange({ project: String(Array.isArray(s) ? s[0] : s) })
|
||||
}
|
||||
disabled={allowedProject.length === 1}
|
||||
selected={project}
|
||||
items={projectItems}
|
||||
/>
|
||||
<>
|
||||
<Select
|
||||
native
|
||||
label={t('fields.azureRepoPicker.project.title')}
|
||||
onChange={s =>
|
||||
onChange({ project: String(Array.isArray(s) ? s[0] : s) })
|
||||
}
|
||||
disabled={allowedProject.length === 1}
|
||||
selected={project}
|
||||
items={projectItems}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{t('fields.azureRepoPicker.project.description')}
|
||||
</FormHelperText>
|
||||
</>
|
||||
) : (
|
||||
<TextField
|
||||
id="projectInput"
|
||||
label="Project"
|
||||
label={t('fields.azureRepoPicker.project.title')}
|
||||
onChange={e => onChange({ project: e.target.value })}
|
||||
value={project}
|
||||
helperText={t('fields.azureRepoPicker.project.description')}
|
||||
/>
|
||||
)}
|
||||
<FormHelperText>
|
||||
The Project that this repo will belong to
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
|
||||
+17
-17
@@ -16,9 +16,9 @@
|
||||
|
||||
import React from 'react';
|
||||
import { BitbucketRepoPicker } from './BitbucketRepoPicker';
|
||||
import { render, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { fireEvent, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import {
|
||||
ScaffolderApi,
|
||||
scaffolderApiRef,
|
||||
@@ -36,7 +36,7 @@ describe('BitbucketRepoPicker', () => {
|
||||
|
||||
it('renders a select if there is a list of allowed owners', async () => {
|
||||
const allowedOwners = ['owner1', 'owner2'];
|
||||
const { findByText } = render(
|
||||
const { findByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={jest.fn()}
|
||||
@@ -51,10 +51,10 @@ describe('BitbucketRepoPicker', () => {
|
||||
expect(await findByText('owner2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders workspace input when host is bitbucket.org', () => {
|
||||
it('renders workspace input when host is bitbucket.org', async () => {
|
||||
const state = { host: 'bitbucket.org', workspace: 'lolsWorkspace' };
|
||||
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={jest.fn()}
|
||||
@@ -68,12 +68,12 @@ describe('BitbucketRepoPicker', () => {
|
||||
expect(getAllByRole('textbox')[0]).toHaveValue('lolsWorkspace');
|
||||
});
|
||||
|
||||
it('hides the workspace input when the host is not bitbucket.org', () => {
|
||||
it('hides the workspace input when the host is not bitbucket.org', async () => {
|
||||
const state = {
|
||||
host: 'mycustom.domain.bitbucket.org',
|
||||
};
|
||||
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={jest.fn()}
|
||||
@@ -87,9 +87,9 @@ describe('BitbucketRepoPicker', () => {
|
||||
});
|
||||
|
||||
describe('workspace field', () => {
|
||||
it('calls onChange when the workspace changes', () => {
|
||||
it('calls onChange when the workspace changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={onChange}
|
||||
@@ -114,9 +114,9 @@ describe('BitbucketRepoPicker', () => {
|
||||
});
|
||||
|
||||
describe('project field', () => {
|
||||
it('calls onChange when the project changes', () => {
|
||||
it('calls onChange when the project changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={onChange}
|
||||
@@ -138,7 +138,7 @@ describe('BitbucketRepoPicker', () => {
|
||||
});
|
||||
|
||||
it('Does not render a select if the list of allowed projects does not exist', async () => {
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={jest.fn()}
|
||||
@@ -153,7 +153,7 @@ describe('BitbucketRepoPicker', () => {
|
||||
});
|
||||
|
||||
it('Does not render a select if the list of allowed projects is empty', async () => {
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={jest.fn()}
|
||||
@@ -170,7 +170,7 @@ describe('BitbucketRepoPicker', () => {
|
||||
|
||||
it('Does render a select if there is a list of allowed projects', async () => {
|
||||
const allowedProjects = ['project1', 'project2'];
|
||||
const { findByText } = render(
|
||||
const { findByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={jest.fn()}
|
||||
@@ -190,7 +190,7 @@ describe('BitbucketRepoPicker', () => {
|
||||
it('should populate workspaces if host is set and accessToken is provided', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getAllByRole, getByText } = render(
|
||||
const { getAllByRole, getByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={onChange}
|
||||
@@ -220,7 +220,7 @@ describe('BitbucketRepoPicker', () => {
|
||||
it('should populate projects if host and workspace are set and accessToken is provided', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getAllByRole, getByText } = render(
|
||||
const { getAllByRole, getByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={onChange}
|
||||
@@ -248,7 +248,7 @@ describe('BitbucketRepoPicker', () => {
|
||||
it('should populate repositories if host, workspace and project are set and accessToken is provided', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[scaffolderApiRef, scaffolderApiMock]]}>
|
||||
<BitbucketRepoPicker
|
||||
onChange={onChange}
|
||||
|
||||
@@ -23,6 +23,8 @@ import TextField from '@material-ui/core/TextField';
|
||||
import useDebounce from 'react-use/esm/useDebounce';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
/**
|
||||
* The underlying component that is rendered in the form for the `BitbucketRepoPicker`
|
||||
@@ -48,6 +50,8 @@ export const BitbucketRepoPicker = (
|
||||
state,
|
||||
accessToken,
|
||||
} = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const { host, workspace, project } = state;
|
||||
const ownerItems: SelectItem[] = allowedOwners
|
||||
? allowedOwners?.map(i => ({ label: i, value: i }))
|
||||
@@ -164,7 +168,7 @@ export const BitbucketRepoPicker = (
|
||||
{allowedOwners?.length ? (
|
||||
<Select
|
||||
native
|
||||
label="Allowed Workspaces"
|
||||
label={t('fields.bitbucketRepoPicker.workspaces.title')}
|
||||
onChange={s =>
|
||||
onChange({ workspace: String(Array.isArray(s) ? s[0] : s) })
|
||||
}
|
||||
@@ -180,14 +184,18 @@ export const BitbucketRepoPicker = (
|
||||
}}
|
||||
options={availableWorkspaces}
|
||||
renderInput={params => (
|
||||
<TextField {...params} label="Workspace" required />
|
||||
<TextField
|
||||
{...params}
|
||||
label={t('fields.bitbucketRepoPicker.workspaces.inputTitle')}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
freeSolo
|
||||
autoSelect
|
||||
/>
|
||||
)}
|
||||
<FormHelperText>
|
||||
The Workspace that this repo will belong to
|
||||
{t('fields.bitbucketRepoPicker.workspaces.description')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
)}
|
||||
@@ -199,7 +207,7 @@ export const BitbucketRepoPicker = (
|
||||
{allowedProjects?.length ? (
|
||||
<Select
|
||||
native
|
||||
label="Allowed Projects"
|
||||
label={t('fields.bitbucketRepoPicker.project.title')}
|
||||
onChange={s =>
|
||||
onChange({ project: String(Array.isArray(s) ? s[0] : s) })
|
||||
}
|
||||
@@ -215,14 +223,18 @@ export const BitbucketRepoPicker = (
|
||||
}}
|
||||
options={availableProjects}
|
||||
renderInput={params => (
|
||||
<TextField {...params} label="Project" required />
|
||||
<TextField
|
||||
{...params}
|
||||
label={t('fields.bitbucketRepoPicker.project.inputTitle')}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
freeSolo
|
||||
autoSelect
|
||||
/>
|
||||
)}
|
||||
<FormHelperText>
|
||||
The Project that this repo will belong to
|
||||
{t('fields.bitbucketRepoPicker.project.description')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GerritRepoPicker } from './GerritRepoPicker';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('GerritRepoPicker', () => {
|
||||
describe('owner input field', () => {
|
||||
it('calls onChange when the owner input changes', () => {
|
||||
it('calls onChange when the owner input changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<GerritRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
@@ -39,9 +40,9 @@ describe('GerritRepoPicker', () => {
|
||||
});
|
||||
|
||||
describe('parent field', () => {
|
||||
it('calls onChange when the parent changes', () => {
|
||||
it('calls onChange when the parent changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<GerritRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
|
||||
@@ -18,20 +18,23 @@ import FormControl from '@material-ui/core/FormControl';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { BaseRepoUrlPickerProps } from './types';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => {
|
||||
const { onChange, rawErrors, state } = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const { workspace, owner } = state;
|
||||
return (
|
||||
<>
|
||||
<FormControl margin="normal" error={rawErrors?.length > 0 && !workspace}>
|
||||
<TextField
|
||||
id="ownerInput"
|
||||
label="Owner"
|
||||
label={t('fields.gerritRepoPicker.owner.title')}
|
||||
onChange={e => onChange({ owner: e.target.value })}
|
||||
helperText={t('fields.gerritRepoPicker.owner.description')}
|
||||
value={owner}
|
||||
/>
|
||||
<FormHelperText>The owner of the project (optional)</FormHelperText>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
margin="normal"
|
||||
@@ -40,13 +43,11 @@ export const GerritRepoPicker = (props: BaseRepoUrlPickerProps) => {
|
||||
>
|
||||
<TextField
|
||||
id="parentInput"
|
||||
label="Parent"
|
||||
label={t('fields.gerritRepoPicker.parent.title')}
|
||||
onChange={e => onChange({ workspace: e.target.value })}
|
||||
value={workspace}
|
||||
helperText={t('fields.gerritRepoPicker.parent.description')}
|
||||
/>
|
||||
<FormHelperText>
|
||||
The project parent that the repo will belong to
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GiteaRepoPicker } from './GiteaRepoPicker';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('GiteaRepoPicker', () => {
|
||||
describe('owner input field', () => {
|
||||
it('calls onChange when the owner input changes', () => {
|
||||
it('calls onChange when the owner input changes', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<GiteaRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
|
||||
@@ -19,6 +19,8 @@ import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { Select, SelectItem } from '@backstage/core-components';
|
||||
import { BaseRepoUrlPickerProps } from './types';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export const GiteaRepoPicker = (
|
||||
props: BaseRepoUrlPickerProps<{
|
||||
@@ -27,6 +29,7 @@ export const GiteaRepoPicker = (
|
||||
}>,
|
||||
) => {
|
||||
const { allowedOwners = [], state, onChange, rawErrors } = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const ownerItems: SelectItem[] = allowedOwners
|
||||
? allowedOwners.map(i => ({ label: i, value: i }))
|
||||
: [{ label: 'Loading...', value: 'loading' }];
|
||||
@@ -41,32 +44,36 @@ export const GiteaRepoPicker = (
|
||||
error={rawErrors?.length > 0 && !owner}
|
||||
>
|
||||
{allowedOwners?.length ? (
|
||||
<Select
|
||||
native
|
||||
label="Owner Available"
|
||||
onChange={selected =>
|
||||
onChange({
|
||||
owner: String(Array.isArray(selected) ? selected[0] : selected),
|
||||
})
|
||||
}
|
||||
disabled={allowedOwners.length === 1}
|
||||
selected={owner}
|
||||
items={ownerItems}
|
||||
/>
|
||||
<>
|
||||
<Select
|
||||
native
|
||||
label={t('fields.giteaRepoPicker.owner.title')}
|
||||
onChange={selected =>
|
||||
onChange({
|
||||
owner: String(
|
||||
Array.isArray(selected) ? selected[0] : selected,
|
||||
),
|
||||
})
|
||||
}
|
||||
disabled={allowedOwners.length === 1}
|
||||
selected={owner}
|
||||
items={ownerItems}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{t('fields.giteaRepoPicker.owner.description')}
|
||||
</FormHelperText>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TextField
|
||||
id="ownerInput"
|
||||
label="Owner"
|
||||
label={t('fields.giteaRepoPicker.owner.inputTitle')}
|
||||
onChange={e => onChange({ owner: e.target.value })}
|
||||
helperText={t('fields.giteaRepoPicker.owner.description')}
|
||||
value={owner}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<FormHelperText>
|
||||
Gitea namespace where this repository will belong to. It can be the
|
||||
name of organization, group, subgroup, user, or the project.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GithubRepoPicker } from './GithubRepoPicker';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('GithubRepoPicker', () => {
|
||||
describe('owner field', () => {
|
||||
it('renders a select if there is a list of allowed owners', async () => {
|
||||
const allowedOwners = ['owner1', 'owner2'];
|
||||
const { findByText } = render(
|
||||
const { findByText } = await renderInTestApp(
|
||||
<GithubRepoPicker
|
||||
onChange={jest.fn()}
|
||||
rawErrors={[]}
|
||||
@@ -38,7 +39,7 @@ describe('GithubRepoPicker', () => {
|
||||
it('calls onChange when the owner is changed to a different owner', async () => {
|
||||
const onChange = jest.fn();
|
||||
const allowedOwners = ['owner1', 'owner2'];
|
||||
const { getByRole } = render(
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<GithubRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
@@ -54,10 +55,10 @@ describe('GithubRepoPicker', () => {
|
||||
expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' });
|
||||
});
|
||||
|
||||
it('is disabled picked when only one allowed owner', () => {
|
||||
it('is disabled picked when only one allowed owner', async () => {
|
||||
const onChange = jest.fn();
|
||||
const allowedOwners = ['owner1'];
|
||||
const { getByRole } = render(
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<GithubRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
@@ -71,7 +72,7 @@ describe('GithubRepoPicker', () => {
|
||||
|
||||
it('should display free text if no allowed owners are passed', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<GithubRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
|
||||
@@ -19,6 +19,8 @@ import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { Select, SelectItem } from '@backstage/core-components';
|
||||
import { BaseRepoUrlPickerProps } from './types';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export const GithubRepoPicker = (
|
||||
props: BaseRepoUrlPickerProps<{
|
||||
@@ -26,6 +28,7 @@ export const GithubRepoPicker = (
|
||||
}>,
|
||||
) => {
|
||||
const { allowedOwners = [], rawErrors, state, onChange } = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const ownerItems: SelectItem[] = allowedOwners
|
||||
? allowedOwners.map(i => ({ label: i, value: i }))
|
||||
: [{ label: 'Loading...', value: 'loading' }];
|
||||
@@ -40,27 +43,30 @@ export const GithubRepoPicker = (
|
||||
error={rawErrors?.length > 0 && !owner}
|
||||
>
|
||||
{allowedOwners?.length ? (
|
||||
<Select
|
||||
native
|
||||
label="Owner Available"
|
||||
onChange={s =>
|
||||
onChange({ owner: String(Array.isArray(s) ? s[0] : s) })
|
||||
}
|
||||
disabled={allowedOwners.length === 1}
|
||||
selected={owner}
|
||||
items={ownerItems}
|
||||
/>
|
||||
<>
|
||||
<Select
|
||||
native
|
||||
label={t('fields.githubRepoPicker.owner.title')}
|
||||
onChange={s =>
|
||||
onChange({ owner: String(Array.isArray(s) ? s[0] : s) })
|
||||
}
|
||||
disabled={allowedOwners.length === 1}
|
||||
selected={owner}
|
||||
items={ownerItems}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{t('fields.githubRepoPicker.owner.description')}
|
||||
</FormHelperText>
|
||||
</>
|
||||
) : (
|
||||
<TextField
|
||||
id="ownerInput"
|
||||
label="Owner"
|
||||
label={t('fields.githubRepoPicker.owner.inputTitle')}
|
||||
onChange={e => onChange({ owner: e.target.value })}
|
||||
helperText={t('fields.githubRepoPicker.owner.description')}
|
||||
value={owner}
|
||||
/>
|
||||
)}
|
||||
<FormHelperText>
|
||||
The organization, user or project that this repo will belong to
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
import React from 'react';
|
||||
import { GitlabRepoPicker } from './GitlabRepoPicker';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('GitlabRepoPicker', () => {
|
||||
describe('owner field', () => {
|
||||
it('renders a select if there is a list of allowed owners', async () => {
|
||||
const allowedOwners = ['owner1', 'owner2'];
|
||||
const { findByText } = render(
|
||||
const { findByText } = await renderInTestApp(
|
||||
<GitlabRepoPicker
|
||||
onChange={jest.fn()}
|
||||
rawErrors={[]}
|
||||
@@ -38,7 +39,7 @@ describe('GitlabRepoPicker', () => {
|
||||
it('calls onChange when the owner is changed to a different owner', async () => {
|
||||
const onChange = jest.fn();
|
||||
const allowedOwners = ['owner1', 'owner2'];
|
||||
const { getByRole } = render(
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<GitlabRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
@@ -54,10 +55,10 @@ describe('GitlabRepoPicker', () => {
|
||||
expect(onChange).toHaveBeenCalledWith({ owner: 'owner2' });
|
||||
});
|
||||
|
||||
it('is disabled picked when only one allowed owner', () => {
|
||||
it('is disabled picked when only one allowed owner', async () => {
|
||||
const onChange = jest.fn();
|
||||
const allowedOwners = ['owner1'];
|
||||
const { getByRole } = render(
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<GitlabRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
@@ -71,7 +72,7 @@ describe('GitlabRepoPicker', () => {
|
||||
|
||||
it('should display free text if no allowed owners are passed', async () => {
|
||||
const onChange = jest.fn();
|
||||
const { getAllByRole } = render(
|
||||
const { getAllByRole } = await renderInTestApp(
|
||||
<GitlabRepoPicker
|
||||
onChange={onChange}
|
||||
rawErrors={[]}
|
||||
|
||||
@@ -19,6 +19,8 @@ import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { Select, SelectItem } from '@backstage/core-components';
|
||||
import { BaseRepoUrlPickerProps } from './types';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export const GitlabRepoPicker = (
|
||||
props: BaseRepoUrlPickerProps<{
|
||||
@@ -27,6 +29,7 @@ export const GitlabRepoPicker = (
|
||||
}>,
|
||||
) => {
|
||||
const { allowedOwners = [], state, onChange, rawErrors } = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const ownerItems: SelectItem[] = allowedOwners
|
||||
? allowedOwners.map(i => ({ label: i, value: i }))
|
||||
: [{ label: 'Loading...', value: 'loading' }];
|
||||
@@ -41,30 +44,34 @@ export const GitlabRepoPicker = (
|
||||
error={rawErrors?.length > 0 && !owner}
|
||||
>
|
||||
{allowedOwners?.length ? (
|
||||
<Select
|
||||
native
|
||||
label="Owner Available"
|
||||
onChange={selected =>
|
||||
onChange({
|
||||
owner: String(Array.isArray(selected) ? selected[0] : selected),
|
||||
})
|
||||
}
|
||||
disabled={allowedOwners.length === 1}
|
||||
selected={owner}
|
||||
items={ownerItems}
|
||||
/>
|
||||
<>
|
||||
<Select
|
||||
native
|
||||
label={t('fields.gitlabRepoPicker.owner.title')}
|
||||
onChange={selected =>
|
||||
onChange({
|
||||
owner: String(
|
||||
Array.isArray(selected) ? selected[0] : selected,
|
||||
),
|
||||
})
|
||||
}
|
||||
disabled={allowedOwners.length === 1}
|
||||
selected={owner}
|
||||
items={ownerItems}
|
||||
/>
|
||||
<FormHelperText>
|
||||
{t('fields.gitlabRepoPicker.owner.description')}
|
||||
</FormHelperText>
|
||||
</>
|
||||
) : (
|
||||
<TextField
|
||||
id="ownerInput"
|
||||
label="Owner"
|
||||
label={t('fields.gitlabRepoPicker.owner.inputTitle')}
|
||||
onChange={e => onChange({ owner: e.target.value })}
|
||||
helperText={t('fields.gitlabRepoPicker.owner.description')}
|
||||
value={owner}
|
||||
/>
|
||||
)}
|
||||
<FormHelperText>
|
||||
GitLab namespace where this repository will belong to. It can be the
|
||||
name of organization, group, subgroup, user, or the project.
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -20,6 +20,8 @@ import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export const RepoUrlPickerHost = (props: {
|
||||
host?: string;
|
||||
@@ -28,6 +30,7 @@ export const RepoUrlPickerHost = (props: {
|
||||
rawErrors: string[];
|
||||
}) => {
|
||||
const { host, hosts, onChange, rawErrors } = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
|
||||
const { value: { integrations } = { integrations: [] }, loading } = useAsync(
|
||||
@@ -73,7 +76,7 @@ export const RepoUrlPickerHost = (props: {
|
||||
<Select
|
||||
native
|
||||
disabled={hosts?.length === 1}
|
||||
label="Host"
|
||||
label={t('fields.repoUrlPickerHost.host.title')}
|
||||
onChange={s => onChange(String(Array.isArray(s) ? s[0] : s))}
|
||||
selected={host}
|
||||
items={hostsOptions}
|
||||
@@ -81,7 +84,7 @@ export const RepoUrlPickerHost = (props: {
|
||||
/>
|
||||
|
||||
<FormHelperText>
|
||||
The host where the repository will be created
|
||||
{t('fields.repoUrlPickerHost.host.description')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
|
||||
+6
-5
@@ -15,15 +15,16 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
import { RepoUrlPickerRepoName } from './RepoUrlPickerRepoName';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('RepoUrlPickerRepoName', () => {
|
||||
it('should call onChange with the first allowed repo if there is none set already', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
render(
|
||||
await renderInTestApp(
|
||||
<RepoUrlPickerRepoName
|
||||
onChange={onChange}
|
||||
allowedRepos={['foo', 'bar']}
|
||||
@@ -39,7 +40,7 @@ describe('RepoUrlPickerRepoName', () => {
|
||||
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByRole } = render(
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<RepoUrlPickerRepoName
|
||||
onChange={onChange}
|
||||
allowedRepos={allowedRepos}
|
||||
@@ -59,7 +60,7 @@ describe('RepoUrlPickerRepoName', () => {
|
||||
it('should render a normal text area when no options are passed', async () => {
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByRole } = render(
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<RepoUrlPickerRepoName
|
||||
onChange={onChange}
|
||||
allowedRepos={[]}
|
||||
@@ -85,7 +86,7 @@ describe('RepoUrlPickerRepoName', () => {
|
||||
|
||||
const onChange = jest.fn();
|
||||
|
||||
const { getByRole, getByText } = render(
|
||||
const { getByRole, getByText } = await renderInTestApp(
|
||||
<RepoUrlPickerRepoName
|
||||
onChange={onChange}
|
||||
availableRepos={availableRepos}
|
||||
|
||||
@@ -19,6 +19,8 @@ import FormControl from '@material-ui/core/FormControl';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
export const RepoUrlPickerRepoName = (props: {
|
||||
repoName?: string;
|
||||
@@ -28,6 +30,7 @@ export const RepoUrlPickerRepoName = (props: {
|
||||
availableRepos?: string[];
|
||||
}) => {
|
||||
const { repoName, allowedRepos, onChange, rawErrors, availableRepos } = props;
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
useEffect(() => {
|
||||
// If there is no repoName chosen currently
|
||||
@@ -53,7 +56,7 @@ export const RepoUrlPickerRepoName = (props: {
|
||||
{allowedRepos?.length ? (
|
||||
<Select
|
||||
native
|
||||
label="Repositories Available"
|
||||
label={t('fields.repoUrlPickerRepoName.repository.title')}
|
||||
onChange={selected =>
|
||||
onChange(String(Array.isArray(selected) ? selected[0] : selected))
|
||||
}
|
||||
@@ -69,13 +72,19 @@ export const RepoUrlPickerRepoName = (props: {
|
||||
}}
|
||||
options={availableRepos || []}
|
||||
renderInput={params => (
|
||||
<TextField {...params} label="Repository" required />
|
||||
<TextField
|
||||
{...params}
|
||||
label={t('fields.repoUrlPickerRepoName.repository.inputTitle')}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
freeSolo
|
||||
autoSelect
|
||||
/>
|
||||
)}
|
||||
<FormHelperText>The name of the repository</FormHelperText>
|
||||
<FormHelperText>
|
||||
{t('fields.repoUrlPickerRepoName.repository.description')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -33,6 +33,8 @@ import { Form } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import { TemplateEditorForm } from './TemplateEditorForm';
|
||||
import validator from '@rjsf/validator-ajv8';
|
||||
import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
@@ -68,6 +70,7 @@ export const CustomFieldExplorer = ({
|
||||
onClose?: () => void;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const fieldOptions = customFieldExtensions.filter(field => !!field.schema);
|
||||
const [selectedField, setSelectedField] = useState(fieldOptions[0]);
|
||||
const [fieldFormState, setFieldFormState] = useState({});
|
||||
@@ -120,11 +123,11 @@ export const CustomFieldExplorer = ({
|
||||
<div className={classes.controls}>
|
||||
<FormControl variant="outlined" size="small" fullWidth>
|
||||
<InputLabel id="select-field-label">
|
||||
Choose Custom Field Extension
|
||||
{t('templateEditorPage.customFieldExplorer.selectFieldLabel')}
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={selectedField}
|
||||
label="Choose Custom Field Extension"
|
||||
label={t('templateEditorPage.customFieldExplorer.selectFieldLabel')}
|
||||
labelId="select-field-label"
|
||||
onChange={e =>
|
||||
handleSelectionChange(e.target.value as FieldExtensionOptions)
|
||||
@@ -144,7 +147,9 @@ export const CustomFieldExplorer = ({
|
||||
</div>
|
||||
<div className={classes.fieldForm}>
|
||||
<Card>
|
||||
<CardHeader title="Field Options" />
|
||||
<CardHeader
|
||||
title={t('templateEditorPage.customFieldExplorer.fieldForm.title')}
|
||||
/>
|
||||
<CardContent>
|
||||
<Form
|
||||
showErrorList={false}
|
||||
@@ -165,7 +170,9 @@ export const CustomFieldExplorer = ({
|
||||
type="submit"
|
||||
disabled={!selectedField.schema?.uiOptions}
|
||||
>
|
||||
Apply
|
||||
{t(
|
||||
'templateEditorPage.customFieldExplorer.fieldForm.applyButtonTitle',
|
||||
)}
|
||||
</Button>
|
||||
</Form>
|
||||
</CardContent>
|
||||
@@ -173,7 +180,9 @@ export const CustomFieldExplorer = ({
|
||||
</div>
|
||||
<div className={classes.preview}>
|
||||
<Card>
|
||||
<CardHeader title="Example Template Spec" />
|
||||
<CardHeader
|
||||
title={t('templateEditorPage.customFieldExplorer.preview.title')}
|
||||
/>
|
||||
<CardContent>
|
||||
<CodeMirror
|
||||
readOnly
|
||||
|
||||
@@ -26,6 +26,8 @@ import React, { useEffect, useState } from 'react';
|
||||
import { useDryRun } from '../DryRunContext';
|
||||
import { DryRunResultsList } from './DryRunResultsList';
|
||||
import { DryRunResultsView } from './DryRunResultsView';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
header: {
|
||||
@@ -51,6 +53,7 @@ export function DryRunResults() {
|
||||
const dryRun = useDryRun();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [hidden, setHidden] = useState(true);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const resultsLength = dryRun.results.length;
|
||||
const prevResultsLength = usePrevious(resultsLength);
|
||||
@@ -76,7 +79,7 @@ export function DryRunResults() {
|
||||
className={classes.header}
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
>
|
||||
<Typography>Dry-run results</Typography>
|
||||
<Typography>{t('templateEditorPage.dryRunResults.title')}</Typography>
|
||||
</AccordionSummary>
|
||||
<Divider orientation="horizontal" />
|
||||
<AccordionDetails className={classes.content}>
|
||||
|
||||
@@ -28,6 +28,8 @@ import DownloadIcon from '@material-ui/icons/GetApp';
|
||||
import React from 'react';
|
||||
import { useDryRun } from '../DryRunContext';
|
||||
import { downloadBlob } from '../../../lib/download';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
@@ -49,6 +51,7 @@ const useStyles = makeStyles(theme => ({
|
||||
export function DryRunResultsList() {
|
||||
const classes = useStyles();
|
||||
const dryRun = useDryRun();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
return (
|
||||
<List className={classes.root} dense>
|
||||
@@ -77,12 +80,18 @@ export function DryRunResultsList() {
|
||||
>
|
||||
{failed ? <CancelIcon /> : <CheckIcon />}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={`Result ${result.id}`} />
|
||||
<ListItemText
|
||||
primary={t('templateEditorPage.dryRunResultsList.title', {
|
||||
resultId: `${result.id}`,
|
||||
})}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="download"
|
||||
title="Download as .zip"
|
||||
title={t(
|
||||
'templateEditorPage.dryRunResultsList.downloadButtonTitle',
|
||||
)}
|
||||
disabled={isLoading}
|
||||
onClick={() => downloadResult()}
|
||||
>
|
||||
@@ -91,7 +100,9 @@ export function DryRunResultsList() {
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="delete"
|
||||
title="Delete result"
|
||||
title={t(
|
||||
'templateEditorPage.dryRunResultsList.deleteButtonTitle',
|
||||
)}
|
||||
onClick={() => dryRun.deleteResult(result.id)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
|
||||
@@ -29,6 +29,8 @@ import { DryRunResultsSplitView } from './DryRunResultsSplitView';
|
||||
import { FileBrowser } from '../../../components/FileBrowser';
|
||||
import { TaskPageLinks } from './TaskPageLinks';
|
||||
import { TaskStatusStepper } from './TaskStatusStepper';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
@@ -173,13 +175,23 @@ export function DryRunResultsView() {
|
||||
const [selectedTab, setSelectedTab] = useState<'files' | 'log' | 'output'>(
|
||||
'files',
|
||||
);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Tabs value={selectedTab} onChange={(_, v) => setSelectedTab(v)}>
|
||||
<Tab value="files" label="Files" />
|
||||
<Tab value="log" label="Log" />
|
||||
<Tab value="output" label="Output" />
|
||||
<Tab
|
||||
value="files"
|
||||
label={t('templateEditorPage.dryRunResultsView.tab.files')}
|
||||
/>
|
||||
<Tab
|
||||
value="log"
|
||||
label={t('templateEditorPage.dryRunResultsView.tab.log')}
|
||||
/>
|
||||
<Tab
|
||||
value="output"
|
||||
label={t('templateEditorPage.dryRunResultsView.tab.output')}
|
||||
/>
|
||||
</Tabs>
|
||||
<Divider />
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ import { DateTime, Interval } from 'luxon';
|
||||
import useInterval from 'react-use/esm/useInterval';
|
||||
import humanizeDuration from 'humanize-duration';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../../translation';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -148,6 +150,7 @@ export const TaskStatusStepper = memo(
|
||||
}) => {
|
||||
const { steps, currentStepId, onUserStepChange } = props;
|
||||
const classes = useStyles(props);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
@@ -178,7 +181,11 @@ export const TaskStatusStepper = memo(
|
||||
<div className={classes.labelWrapper}>
|
||||
<Typography variant="subtitle2">{step.name}</Typography>
|
||||
{isSkipped ? (
|
||||
<Typography variant="caption">Skipped</Typography>
|
||||
<Typography variant="caption">
|
||||
{t(
|
||||
'templateEditorPage.taskStatusStepper.skippedStepTitle',
|
||||
)}
|
||||
</Typography>
|
||||
) : (
|
||||
<StepTimeTicker step={step} />
|
||||
)}
|
||||
|
||||
@@ -23,6 +23,8 @@ import SaveIcon from '@material-ui/icons/Save';
|
||||
import React from 'react';
|
||||
import { useDirectoryEditor } from './DirectoryEditorContext';
|
||||
import { FileBrowser } from '../../components/FileBrowser';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
button: {
|
||||
@@ -47,6 +49,7 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) {
|
||||
const classes = useStyles();
|
||||
const directoryEditor = useDirectoryEditor();
|
||||
const changedFiles = directoryEditor.files.filter(file => file.dirty);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const handleClose = () => {
|
||||
if (!props.onClose) {
|
||||
@@ -55,7 +58,7 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) {
|
||||
if (changedFiles.length > 0) {
|
||||
// eslint-disable-next-line no-alert
|
||||
const accepted = window.confirm(
|
||||
'Are you sure? Unsaved changes will be lost',
|
||||
t('templateEditorPage.templateEditorBrowser.closeConfirmMessage'),
|
||||
);
|
||||
if (!accepted) {
|
||||
return;
|
||||
@@ -67,7 +70,9 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) {
|
||||
return (
|
||||
<>
|
||||
<div className={classes.buttons}>
|
||||
<Tooltip title="Save all files">
|
||||
<Tooltip
|
||||
title={t('templateEditorPage.templateEditorBrowser.saveIconTooltip')}
|
||||
>
|
||||
<IconButton
|
||||
className={classes.button}
|
||||
disabled={directoryEditor.files.every(file => !file.dirty)}
|
||||
@@ -76,7 +81,11 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) {
|
||||
<SaveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Reload directory">
|
||||
<Tooltip
|
||||
title={t(
|
||||
'templateEditorPage.templateEditorBrowser.reloadIconTooltip',
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
className={classes.button}
|
||||
onClick={() => directoryEditor.reload()}
|
||||
@@ -85,7 +94,9 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) {
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className={classes.buttonsGap} />
|
||||
<Tooltip title="Close directory">
|
||||
<Tooltip
|
||||
title={t('templateEditorPage.templateEditorBrowser.closeIconTooltip')}
|
||||
>
|
||||
<IconButton className={classes.button} onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
|
||||
@@ -23,6 +23,8 @@ import Typography from '@material-ui/core/Typography';
|
||||
import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { WebFileSystemAccess } from '../../lib/filesystem';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
introText: {
|
||||
@@ -50,6 +52,7 @@ interface EditorIntroProps {
|
||||
export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
const classes = useStyles();
|
||||
const supportsLoad = WebFileSystemAccess.isSupported();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const cardLoadLocal = (
|
||||
<Card className={classes.card} elevation={4}>
|
||||
@@ -65,14 +68,13 @@ export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
color={supportsLoad ? undefined : 'textSecondary'}
|
||||
style={{ display: 'flex', flexFlow: 'row nowrap' }}
|
||||
>
|
||||
Load Template Directory
|
||||
{t('templateEditorPage.templateEditorIntro.loadLocal.title')}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
color={supportsLoad ? undefined : 'textSecondary'}
|
||||
>
|
||||
Load a local template directory, allowing you to both edit and try
|
||||
executing your own template.
|
||||
{t('templateEditorPage.templateEditorIntro.loadLocal.description')}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
@@ -80,7 +82,9 @@ export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
<div className={classes.infoIcon}>
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title="Only supported in some Chromium-based browsers"
|
||||
title={t(
|
||||
'templateEditorPage.templateEditorIntro.loadLocal.unSupportsLoadTooltip',
|
||||
)}
|
||||
>
|
||||
<InfoOutlinedIcon />
|
||||
</Tooltip>
|
||||
@@ -94,11 +98,10 @@ export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
<CardActionArea onClick={() => props.onSelect?.('form')}>
|
||||
<CardContent>
|
||||
<Typography variant="h4" component="h3" gutterBottom>
|
||||
Edit Template Form
|
||||
{t('templateEditorPage.templateEditorIntro.formEditor.title')}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
Preview and edit a template form, either using a sample template or
|
||||
by loading a template from the catalog.
|
||||
{t('templateEditorPage.templateEditorIntro.formEditor.description')}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
@@ -110,11 +113,12 @@ export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
<CardActionArea onClick={() => props.onSelect?.('field-explorer')}>
|
||||
<CardContent>
|
||||
<Typography variant="h4" component="h3" gutterBottom>
|
||||
Custom Field Explorer
|
||||
{t('templateEditorPage.templateEditorIntro.fieldExplorer.title')}
|
||||
</Typography>
|
||||
<Typography variant="body1">
|
||||
View and play around with available installed custom field
|
||||
extensions.
|
||||
{t(
|
||||
'templateEditorPage.templateEditorIntro.fieldExplorer.description',
|
||||
)}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
@@ -124,7 +128,7 @@ export function TemplateEditorIntro(props: EditorIntroProps) {
|
||||
return (
|
||||
<div style={props.style}>
|
||||
<Typography variant="h4" component="h2" className={classes.introText}>
|
||||
Get started by choosing one of the options below
|
||||
{t('templateEditorPage.templateEditorIntro.title')}
|
||||
</Typography>
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -35,6 +35,8 @@ import {
|
||||
rootRouteRef,
|
||||
scaffolderListTaskRouteRef,
|
||||
} from '../../routes';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
type Selection =
|
||||
| {
|
||||
@@ -60,6 +62,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) {
|
||||
const actionsLink = useRouteRef(actionsRouteRef);
|
||||
const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
|
||||
const createLink = useRouteRef(rootRouteRef);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const scaffolderPageContextMenuProps = {
|
||||
onEditorClicked: undefined,
|
||||
@@ -117,8 +120,8 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) {
|
||||
return (
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
title="Template Editor"
|
||||
subtitle="Edit, preview, and try out templates and template forms"
|
||||
title={t('templateEditorPage.title')}
|
||||
subtitle={t('templateEditorPage.subTitle')}
|
||||
>
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</Header>
|
||||
|
||||
@@ -27,6 +27,8 @@ import { useKeyboardEvent } from '@react-hookz/web';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useDirectoryEditor } from './DirectoryEditorContext';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
@@ -66,6 +68,7 @@ export function TemplateEditorTextArea(props: {
|
||||
}) {
|
||||
const { errorText } = props;
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const panelExtension = useMemo(() => {
|
||||
if (!errorText) {
|
||||
@@ -102,7 +105,11 @@ export function TemplateEditorTextArea(props: {
|
||||
<div className={classes.floatingButtons}>
|
||||
<Paper>
|
||||
{props.onSave && (
|
||||
<Tooltip title="Save file">
|
||||
<Tooltip
|
||||
title={t(
|
||||
'templateEditorPage.templateEditorTextArea.saveIconTooltip',
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
className={classes.floatingButton}
|
||||
onClick={() => props.onSave?.()}
|
||||
@@ -112,7 +119,11 @@ export function TemplateEditorTextArea(props: {
|
||||
</Tooltip>
|
||||
)}
|
||||
{props.onReload && (
|
||||
<Tooltip title="Reload file">
|
||||
<Tooltip
|
||||
title={t(
|
||||
'templateEditorPage.templateEditorTextArea.refreshIconTooltip',
|
||||
)}
|
||||
>
|
||||
<IconButton
|
||||
className={classes.floatingButton}
|
||||
onClick={() => props.onReload?.()}
|
||||
|
||||
@@ -36,6 +36,8 @@ import {
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import { TemplateEditorForm } from './TemplateEditorForm';
|
||||
import { TemplateEditorTextArea } from './TemplateEditorTextArea';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
|
||||
parameters:
|
||||
@@ -119,6 +121,7 @@ export const TemplateFormPreviewer = ({
|
||||
layouts?: LayoutOptions[];
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const [selectedTemplate, setSelectedTemplate] = useState('');
|
||||
@@ -176,11 +179,11 @@ export const TemplateFormPreviewer = ({
|
||||
<div className={classes.controls}>
|
||||
<FormControl variant="outlined" size="small" fullWidth>
|
||||
<InputLabel id="select-template-label">
|
||||
Load Existing Template
|
||||
{t('templateEditorPage.templateFormPreviewer.title')}
|
||||
</InputLabel>
|
||||
<Select
|
||||
value={selectedTemplate}
|
||||
label="Load Existing Template"
|
||||
label={t('templateEditorPage.templateFormPreviewer.title')}
|
||||
labelId="select-template-label"
|
||||
onChange={e => handleSelectChange(e.target.value)}
|
||||
>
|
||||
|
||||
@@ -52,6 +52,11 @@ import {
|
||||
} from '../../routes';
|
||||
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react';
|
||||
import {
|
||||
TranslationFunction,
|
||||
useTranslationRef,
|
||||
} from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
@@ -74,17 +79,13 @@ export type TemplateListPageProps = {
|
||||
};
|
||||
};
|
||||
|
||||
const defaultGroup: TemplateGroupFilter = {
|
||||
title: 'Templates',
|
||||
filter: () => true,
|
||||
};
|
||||
|
||||
const createGroupsWithOther = (
|
||||
groups: TemplateGroupFilter[],
|
||||
t: TranslationFunction<typeof scaffolderTranslationRef.T>,
|
||||
): TemplateGroupFilter[] => [
|
||||
...groups,
|
||||
{
|
||||
title: 'Other Templates',
|
||||
title: t('templateListPage.templateGroups.otherTitle'),
|
||||
filter: e => ![...groups].some(({ filter }) => filter(e)),
|
||||
},
|
||||
];
|
||||
@@ -107,10 +108,16 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
|
||||
const viewTechDocsLink = useRouteRef(viewTechDocRouteRef);
|
||||
const templateRoute = useRouteRef(selectedTemplateRouteRef);
|
||||
const app = useApp();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const groups = givenGroups.length
|
||||
? createGroupsWithOther(givenGroups)
|
||||
: [defaultGroup];
|
||||
? createGroupsWithOther(givenGroups, t)
|
||||
: [
|
||||
{
|
||||
title: t('templateListPage.templateGroups.defaultTitle'),
|
||||
filter: () => true,
|
||||
},
|
||||
];
|
||||
|
||||
const scaffolderPageContextMenuProps = {
|
||||
onEditorClicked:
|
||||
@@ -137,13 +144,15 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
|
||||
? [
|
||||
{
|
||||
icon: app.getSystemIcon('docs') ?? DocsIcon,
|
||||
text: 'View TechDocs',
|
||||
text: t(
|
||||
'templateListPage.additionalLinksForEntity.viewTechDocsTitle',
|
||||
),
|
||||
url: viewTechDocsLink({ kind, namespace, name }),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
},
|
||||
[app, viewTechDocsLink],
|
||||
[app, viewTechDocsLink, t],
|
||||
);
|
||||
|
||||
const onTemplateSelected = useCallback(
|
||||
@@ -159,23 +168,23 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
|
||||
<EntityListProvider>
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride="Create a new component"
|
||||
title="Create a new component"
|
||||
subtitle="Create new software components using standard templates in your organization"
|
||||
pageTitleOverride={t('templateListPage.pageTitle')}
|
||||
title={t('templateListPage.title')}
|
||||
subtitle={t('templateListPage.subtitle')}
|
||||
{...headerOptions}
|
||||
>
|
||||
<ScaffolderPageContextMenu {...scaffolderPageContextMenuProps} />
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentHeader title="Available Templates">
|
||||
<ContentHeader title={t('templateListPage.contentHeader.title')}>
|
||||
<RegisterExistingButton
|
||||
title="Register Existing Component"
|
||||
title={t(
|
||||
'templateListPage.contentHeader.registerExistingButtonTitle',
|
||||
)}
|
||||
to={registerComponentLink && registerComponentLink()}
|
||||
/>
|
||||
<SupportButton>
|
||||
Create new software components using standard templates. Different
|
||||
templates create different kinds of components (services,
|
||||
websites, documentation, ...).
|
||||
{t('templateListPage.contentHeader.supportButtonTitle')}
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ import {
|
||||
scaffolderTaskRouteRef,
|
||||
selectedTemplateRouteRef,
|
||||
} from '../../routes';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
import { TemplateWizardPageContextMenu } from './TemplateWizardPageContextMenu';
|
||||
|
||||
@@ -75,6 +77,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
|
||||
const { templateName, namespace } = useRouteRefParams(
|
||||
selectedTemplateRouteRef,
|
||||
);
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
const templateRef = stringifyEntityRef({
|
||||
kind: 'Template',
|
||||
@@ -103,9 +106,9 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => {
|
||||
<AnalyticsContext attributes={{ entityRef: templateRef }}>
|
||||
<Page themeId="website">
|
||||
<Header
|
||||
pageTitleOverride="Create a new component"
|
||||
title="Create a new component"
|
||||
subtitle="Create new software components using standard templates in your organization"
|
||||
pageTitleOverride={t('templateWizardPage.pageTitle')}
|
||||
title={t('templateWizardPage.title')}
|
||||
subtitle={t('templateWizardPage.subtitle')}
|
||||
{...props.headerOptions}
|
||||
>
|
||||
<TemplateWizardPageContextMenu editUrl={editUrl} />
|
||||
|
||||
@@ -24,6 +24,8 @@ import { makeStyles } from '@material-ui/core/styles';
|
||||
import Edit from '@material-ui/icons/Edit';
|
||||
import MoreVert from '@material-ui/icons/MoreVert';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { scaffolderTranslationRef } from '../../translation';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
button: {
|
||||
@@ -41,6 +43,7 @@ export function TemplateWizardPageContextMenu(
|
||||
const { editUrl } = props;
|
||||
const classes = useStyles();
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>();
|
||||
const { t } = useTranslationRef(scaffolderTranslationRef);
|
||||
|
||||
if (!editUrl) {
|
||||
return null;
|
||||
@@ -83,7 +86,11 @@ export function TemplateWizardPageContextMenu(
|
||||
<ListItemIcon>
|
||||
<Edit fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Edit Configuration" />
|
||||
<ListItemText
|
||||
primary={t(
|
||||
'templateWizardPage.pageContextMenu.editConfigurationTitle',
|
||||
)}
|
||||
/>
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Popover>
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* 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 { createTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
/** @alpha */
|
||||
export const scaffolderTranslationRef = createTranslationRef({
|
||||
id: 'scaffolder',
|
||||
messages: {
|
||||
actionsPage: {
|
||||
title: 'Installed actions',
|
||||
pageTitle: 'Create a New Component',
|
||||
subtitle: 'This is the collection of all installed actions',
|
||||
content: {
|
||||
emptyState: {
|
||||
title: 'No information to display',
|
||||
description:
|
||||
'There are no actions installed or there was an issue communicating with backend.',
|
||||
},
|
||||
tableCell: {
|
||||
name: 'Name',
|
||||
title: 'Title',
|
||||
description: 'Description',
|
||||
type: 'Type',
|
||||
},
|
||||
noRowsDescription: 'No schema defined',
|
||||
},
|
||||
action: {
|
||||
input: 'Input',
|
||||
output: 'Output',
|
||||
examples: 'Examples',
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
entityNamePicker: {
|
||||
title: 'Name',
|
||||
description: 'Unique name of the component',
|
||||
},
|
||||
entityPicker: {
|
||||
title: 'Entity',
|
||||
description: 'An entity from the catalog',
|
||||
},
|
||||
entityTagsPicker: {
|
||||
title: 'Tags',
|
||||
description:
|
||||
"Add any relevant tags, hit 'Enter' to add new tags. Valid format: [a-z0-9+#] separated by [-], at most 63 characters",
|
||||
},
|
||||
myGroupsPicker: {
|
||||
title: 'Entity',
|
||||
description: 'An entity from the catalog',
|
||||
},
|
||||
ownedEntityPicker: {
|
||||
title: 'Entity',
|
||||
description: 'An entity from the catalog',
|
||||
},
|
||||
ownerPicker: {
|
||||
title: 'Owner',
|
||||
description: 'The owner of the component',
|
||||
},
|
||||
azureRepoPicker: {
|
||||
organization: {
|
||||
title: 'Organization',
|
||||
description: 'The Organization that this repo will belong to',
|
||||
},
|
||||
project: {
|
||||
title: 'Project',
|
||||
description: 'The Project that this repo will belong to',
|
||||
},
|
||||
},
|
||||
bitbucketRepoPicker: {
|
||||
workspaces: {
|
||||
title: 'Allowed Workspaces',
|
||||
inputTitle: 'Workspaces',
|
||||
description: 'The Workspace that this repo will belong to',
|
||||
},
|
||||
project: {
|
||||
title: 'Allowed Projects',
|
||||
inputTitle: 'Projects',
|
||||
description: 'The Project that this repo will belong to',
|
||||
},
|
||||
},
|
||||
gerritRepoPicker: {
|
||||
owner: {
|
||||
title: 'Owner',
|
||||
description: 'The owner of the project (optional)',
|
||||
},
|
||||
parent: {
|
||||
title: 'Parent',
|
||||
description: 'The project parent that the repo will belong to',
|
||||
},
|
||||
},
|
||||
giteaRepoPicker: {
|
||||
owner: {
|
||||
title: 'Owner Available',
|
||||
inputTitle: 'Owner',
|
||||
description:
|
||||
'Gitea namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.',
|
||||
},
|
||||
},
|
||||
githubRepoPicker: {
|
||||
owner: {
|
||||
title: 'Owner Available',
|
||||
inputTitle: 'Owner',
|
||||
description:
|
||||
'The organization, user or project that this repo will belong to',
|
||||
},
|
||||
},
|
||||
gitlabRepoPicker: {
|
||||
owner: {
|
||||
title: 'Owner Available',
|
||||
inputTitle: 'Owner',
|
||||
description:
|
||||
'GitLab namespace where this repository will belong to. It can be the name of organization, group, subgroup, user, or the project.',
|
||||
},
|
||||
},
|
||||
repoUrlPickerHost: {
|
||||
host: {
|
||||
title: 'Host',
|
||||
description: 'The host where the repository will be created',
|
||||
},
|
||||
},
|
||||
repoUrlPickerRepoName: {
|
||||
repository: {
|
||||
title: 'Repositories Available',
|
||||
inputTitle: 'Repository',
|
||||
description: 'The name of the repository',
|
||||
},
|
||||
},
|
||||
},
|
||||
listTaskPage: {
|
||||
title: 'List template tasks',
|
||||
pageTitle: 'Templates Tasks',
|
||||
subtitle: 'All tasks that have been started',
|
||||
content: {
|
||||
emptyState: {
|
||||
title: 'No information to display',
|
||||
description:
|
||||
'There are no tasks or there was an issue communicating with backend.',
|
||||
},
|
||||
tableTitle: 'Tasks',
|
||||
tableCell: {
|
||||
taskID: 'Task ID',
|
||||
template: 'Template',
|
||||
created: 'Created',
|
||||
owner: 'Owner',
|
||||
status: 'Status',
|
||||
},
|
||||
},
|
||||
},
|
||||
ownerListPicker: {
|
||||
title: 'Task Owner',
|
||||
options: {
|
||||
owned: 'Owned',
|
||||
all: 'All',
|
||||
},
|
||||
},
|
||||
ongoingTask: {
|
||||
title: 'Run of',
|
||||
pageTitle: {
|
||||
hasTemplateName: 'Run of {{templateName}}',
|
||||
noTemplateName: 'Scaffolder Run',
|
||||
},
|
||||
subtitle: 'Task {{taskId}}',
|
||||
cancelButtonTitle: 'Cancel',
|
||||
startOverButtonTitle: 'Start Over',
|
||||
hideLogsButtonTitle: 'Hide Logs',
|
||||
showLogsButtonTitle: 'Show Logs',
|
||||
contextMenu: {
|
||||
hideLogs: 'Hide Logs',
|
||||
showLogs: 'Show Logs',
|
||||
hideButtonBar: 'Hide Button Bar',
|
||||
showButtonBar: 'Show Button Bar',
|
||||
startOver: 'Start Over',
|
||||
cancel: 'Cancel',
|
||||
},
|
||||
},
|
||||
templateTypePicker: {
|
||||
title: 'Categories',
|
||||
},
|
||||
templateEditorPage: {
|
||||
title: 'Template Editor',
|
||||
subTitle: 'Edit, preview, and try out templates and template forms',
|
||||
dryRunResults: {
|
||||
title: 'Dry-run results',
|
||||
},
|
||||
dryRunResultsList: {
|
||||
title: 'Result {{resultId}}',
|
||||
downloadButtonTitle: 'Download as .zip',
|
||||
deleteButtonTitle: 'Delete result',
|
||||
},
|
||||
dryRunResultsView: {
|
||||
tab: {
|
||||
files: 'Files',
|
||||
log: 'Log',
|
||||
output: 'Output',
|
||||
},
|
||||
},
|
||||
taskStatusStepper: {
|
||||
skippedStepTitle: 'Skipped',
|
||||
},
|
||||
customFieldExplorer: {
|
||||
selectFieldLabel: 'Choose Custom Field Extension',
|
||||
fieldForm: {
|
||||
title: 'Field Options',
|
||||
applyButtonTitle: 'Apply',
|
||||
},
|
||||
preview: {
|
||||
title: 'Example Template Spec',
|
||||
},
|
||||
},
|
||||
templateEditorBrowser: {
|
||||
closeConfirmMessage: 'Are you sure? Unsaved changes will be lost',
|
||||
saveIconTooltip: 'Save all files',
|
||||
reloadIconTooltip: 'Reload directory',
|
||||
closeIconTooltip: 'Close directory',
|
||||
},
|
||||
templateEditorIntro: {
|
||||
title: 'Get started by choosing one of the options below',
|
||||
loadLocal: {
|
||||
title: 'Load Template Directory',
|
||||
description:
|
||||
'Load a local template directory, allowing you to both edit and try executing your own template.',
|
||||
unSupportsLoadTooltip:
|
||||
'Only supported in some Chromium-based browsers',
|
||||
},
|
||||
formEditor: {
|
||||
title: 'Edit Template Form',
|
||||
description:
|
||||
'Preview and edit a template form, either using a sample template or by loading a template from the catalog.',
|
||||
},
|
||||
fieldExplorer: {
|
||||
title: 'Custom Field Explorer',
|
||||
description:
|
||||
'View and play around with available installed custom field extensions.',
|
||||
},
|
||||
},
|
||||
templateEditorTextArea: {
|
||||
saveIconTooltip: 'Save file',
|
||||
refreshIconTooltip: 'Reload file',
|
||||
},
|
||||
templateFormPreviewer: {
|
||||
title: 'Load Existing Template',
|
||||
},
|
||||
},
|
||||
templateListPage: {
|
||||
title: 'Create a new component',
|
||||
subtitle:
|
||||
'Create new software components using standard templates in your organization',
|
||||
pageTitle: 'Create a new component',
|
||||
templateGroups: {
|
||||
defaultTitle: 'Templates',
|
||||
otherTitle: 'Other Templates',
|
||||
},
|
||||
contentHeader: {
|
||||
title: 'Available Templates',
|
||||
registerExistingButtonTitle: 'Register Existing Component',
|
||||
supportButtonTitle:
|
||||
'Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).',
|
||||
},
|
||||
additionalLinksForEntity: {
|
||||
viewTechDocsTitle: 'View TechDocs',
|
||||
},
|
||||
},
|
||||
templateWizardPage: {
|
||||
title: 'Create a new component',
|
||||
subtitle:
|
||||
'Create new software components using standard templates in your organization',
|
||||
pageTitle: 'Create a new component',
|
||||
pageContextMenu: {
|
||||
editConfigurationTitle: 'Edit Configuration',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user