Merge pull request #20932 from mario-mui/feat/core-components-i18n
add i18n for core components
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Support i18n for core components
|
||||
@@ -0,0 +1,59 @@
|
||||
## API Report File for "@backstage/core-components"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { TranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const coreComponentsTranslationRef: TranslationRef<
|
||||
'core-components',
|
||||
{
|
||||
readonly 'link.openNewWindow': 'Opens in a new window';
|
||||
readonly 'table.filter.title': 'Filters';
|
||||
readonly 'table.filter.clearAll': 'Clear all';
|
||||
readonly 'signIn.title': 'Sign In';
|
||||
readonly 'signIn.loginFailed': 'Login failed';
|
||||
readonly 'signIn.customProvider.title': 'Custom User';
|
||||
readonly 'signIn.customProvider.subtitle': 'Enter your own User ID and credentials.\n This selection will not be stored.';
|
||||
readonly 'signIn.customProvider.userId': 'User ID';
|
||||
readonly 'signIn.customProvider.tokenInvalid': 'Token is not a valid OpenID Connect JWT Token';
|
||||
readonly 'signIn.customProvider.continue': 'Continue';
|
||||
readonly 'signIn.customProvider.idToken': 'ID Token (optional)';
|
||||
readonly 'signIn.guestProvider.title': 'Guest';
|
||||
readonly 'signIn.guestProvider.enter': 'Enter';
|
||||
readonly 'signIn.guestProvider.subtitle': 'Enter as a Guest User.\n You will not have a verified identity, meaning some features might be unavailable.';
|
||||
readonly skipToContent: 'Skip to content';
|
||||
readonly 'copyTextButton.tooltipText': 'Text copied to clipboard';
|
||||
readonly 'simpleStepper.finish': 'Finish';
|
||||
readonly 'simpleStepper.reset': 'Reset';
|
||||
readonly 'simpleStepper.next': 'Next';
|
||||
readonly 'simpleStepper.skip': 'Skip';
|
||||
readonly 'simpleStepper.back': 'Back';
|
||||
readonly 'errorPage.title': 'Looks like someone dropped the mic!';
|
||||
readonly 'errorPage.subtitle': 'ERROR {{status}}: {{statusMessage}}';
|
||||
readonly 'errorPage.goBack': 'Go back';
|
||||
readonly 'errorPage.showMoreDetails': 'Show more details';
|
||||
readonly 'errorPage.showLessDetails': 'Show less details';
|
||||
readonly 'emptyState.missingAnnotation.title': 'Missing Annotation';
|
||||
readonly 'emptyState.missingAnnotation.actionTitle': 'Add the annotation to your component YAML as shown in the highlighted example below:';
|
||||
readonly 'emptyState.missingAnnotation.readMore': 'Read more';
|
||||
readonly 'supportConfig.default.title': 'Support Not Configured';
|
||||
readonly 'supportConfig.default.linkTitle': 'Add `app.support` config key';
|
||||
readonly 'errorBoundary.title': 'Please contact {{slackChannel}} for help.';
|
||||
readonly 'oauthRequestDialog.title': 'Login Required';
|
||||
readonly 'oauthRequestDialog.authRedirectTitle': 'This will trigger a http redirect to OAuth Login.';
|
||||
readonly 'oauthRequestDialog.login': 'Log in';
|
||||
readonly 'oauthRequestDialog.rejectAll': 'Reject All';
|
||||
readonly 'supportButton.title': 'Support';
|
||||
readonly 'supportButton.close': 'Close';
|
||||
readonly 'alertDisplay.message_one': '({{ count }} older message)';
|
||||
readonly 'alertDisplay.message_other': '({{ count }} older messages)';
|
||||
readonly 'autoLogout.stillTherePrompt.title': 'Logging out due to inactivity';
|
||||
readonly 'autoLogout.stillTherePrompt.buttonText': "Yes! Don't log me out";
|
||||
readonly 'proxiedSignInPage.title': 'You do not appear to be signed in. Please try reloading the browser page.';
|
||||
}
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -22,11 +22,15 @@
|
||||
"types": "src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./alpha": "./src/alpha.ts",
|
||||
"./testUtils": "./src/testUtils.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"alpha": [
|
||||
"src/alpha.ts"
|
||||
],
|
||||
"testUtils": [
|
||||
"src/testUtils.ts"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 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.
|
||||
*/
|
||||
export * from './translation';
|
||||
@@ -14,13 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { alertApiRef, AlertMessage, useApi } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import pluralize from 'pluralize';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
|
||||
/**
|
||||
* Properties for {@link AlertDisplay}
|
||||
@@ -63,6 +64,7 @@ export type AlertDisplayProps = {
|
||||
export function AlertDisplay(props: AlertDisplayProps) {
|
||||
const [messages, setMessages] = useState<Array<AlertMessage>>([]);
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const {
|
||||
anchorOrigin = { vertical: 'top', horizontal: 'center' },
|
||||
@@ -121,10 +123,11 @@ export function AlertDisplay(props: AlertDisplayProps) {
|
||||
<Typography component="span">
|
||||
{String(firstMessage.message)}
|
||||
{messages.length > 1 && (
|
||||
<em>{` (${messages.length - 1} older ${pluralize(
|
||||
'message',
|
||||
messages.length - 1,
|
||||
)})`}</em>
|
||||
<em>
|
||||
{t('alertDisplay.message', {
|
||||
count: messages.length - 1,
|
||||
})}
|
||||
</em>
|
||||
)}
|
||||
</Typography>
|
||||
</Alert>
|
||||
|
||||
@@ -18,16 +18,18 @@ import { createMocks } from 'react-idle-timer';
|
||||
import { MessageChannel } from 'worker_threads';
|
||||
import { ApiProvider } from '@backstage/core-app-api';
|
||||
import { identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { TestApiRegistry } from '@backstage/test-utils';
|
||||
import { TestApiRegistry, renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
|
||||
import { AutoLogout } from './AutoLogout';
|
||||
import { cleanup, render } from '@testing-library/react';
|
||||
import { cleanup } from '@testing-library/react';
|
||||
|
||||
// Mock the signOut function of identityApiRef
|
||||
const mockSignOut = jest.fn();
|
||||
const mockIdentityApi = { signOut: mockSignOut };
|
||||
|
||||
const mockIdentityApi = {
|
||||
signOut: mockSignOut,
|
||||
getCredentials: jest.fn().mockReturnValue({ token: 'xxx' }),
|
||||
};
|
||||
const apis = TestApiRegistry.from([identityApiRef, mockIdentityApi]);
|
||||
|
||||
describe('AutoLogout', () => {
|
||||
@@ -44,27 +46,29 @@ describe('AutoLogout', () => {
|
||||
});
|
||||
|
||||
it('should throw error if idleTimeoutMinutes is smaller than promptBeforeSeconds', async () => {
|
||||
expect(() =>
|
||||
render(
|
||||
<ApiProvider apis={apis}>
|
||||
<AutoLogout
|
||||
enabled
|
||||
idleTimeoutMinutes={0.5}
|
||||
promptBeforeIdleSeconds={120}
|
||||
/>
|
||||
</ApiProvider>,
|
||||
),
|
||||
).toThrow();
|
||||
await expect(
|
||||
async () =>
|
||||
await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<AutoLogout
|
||||
enabled
|
||||
idleTimeoutMinutes={0.5}
|
||||
promptBeforeIdleSeconds={120}
|
||||
/>
|
||||
</ApiProvider>,
|
||||
),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error if idleTimeoutMinutes is smaller than 30 seconds', async () => {
|
||||
expect(() =>
|
||||
render(
|
||||
<ApiProvider apis={apis}>
|
||||
<AutoLogout enabled idleTimeoutMinutes={0.49} />
|
||||
<div>Test Child</div>
|
||||
</ApiProvider>,
|
||||
),
|
||||
).toThrow();
|
||||
await expect(
|
||||
async () =>
|
||||
await renderInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<AutoLogout enabled idleTimeoutMinutes={0.49} />
|
||||
<div>Test Child</div>
|
||||
</ApiProvider>,
|
||||
),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,6 +22,8 @@ import DialogContentText from '@material-ui/core/DialogContentText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import React, { useEffect } from 'react';
|
||||
import { IIdleTimer } from 'react-idle-timer';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
export interface StillTherePromptProps {
|
||||
idleTimer: IIdleTimer;
|
||||
@@ -41,6 +43,7 @@ export const StillTherePrompt = (props: StillTherePromptProps) => {
|
||||
remainingTime,
|
||||
setRemainingTime,
|
||||
} = props;
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
@@ -65,7 +68,7 @@ export const StillTherePrompt = (props: StillTherePromptProps) => {
|
||||
|
||||
return (
|
||||
<Dialog open={open} data-testid="inactivity-prompt-dialog">
|
||||
<DialogTitle>Logging out due to inactivity</DialogTitle>
|
||||
<DialogTitle>{t('autoLogout.stillTherePrompt.title')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
You are about to be disconnected in{' '}
|
||||
@@ -82,7 +85,7 @@ export const StillTherePrompt = (props: StillTherePromptProps) => {
|
||||
variant="contained"
|
||||
size="small"
|
||||
>
|
||||
Yes! Don't log me out
|
||||
{t('autoLogout.stillTherePrompt.buttonText')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
@@ -20,6 +20,8 @@ import Tooltip from '@material-ui/core/Tooltip';
|
||||
import CopyIcon from '@material-ui/icons/FileCopy';
|
||||
import React, { MouseEventHandler, useEffect, useState } from 'react';
|
||||
import useCopyToClipboard from 'react-use/lib/useCopyToClipboard';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
/**
|
||||
* Properties for {@link CopyTextButton}
|
||||
@@ -78,10 +80,11 @@ export interface CopyTextButtonProps {
|
||||
* ```
|
||||
*/
|
||||
export function CopyTextButton(props: CopyTextButtonProps) {
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
const {
|
||||
text,
|
||||
tooltipDelay = 1000,
|
||||
tooltipText = 'Text copied to clipboard',
|
||||
tooltipText = t('copyTextButton.tooltipText'),
|
||||
'aria-label': ariaLabel = 'Copy text',
|
||||
} = props;
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
@@ -23,6 +23,8 @@ import React from 'react';
|
||||
import { CodeSnippet } from '../CodeSnippet';
|
||||
import { Link } from '../Link';
|
||||
import { EmptyState } from './EmptyState';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
|
||||
const COMPONENT_YAML_TEMPLATE = `apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
@@ -76,7 +78,7 @@ function generateComponentYaml(annotations: string[]) {
|
||||
return COMPONENT_YAML_TEMPLATE.replace(ANNOTATION_YAML, annotationYaml);
|
||||
}
|
||||
|
||||
function generateDescription(annotations: string[]) {
|
||||
function useGenerateDescription(annotations: string[]) {
|
||||
const isSingular = annotations.length <= 1;
|
||||
return (
|
||||
<>
|
||||
@@ -106,17 +108,17 @@ export function MissingAnnotationEmptyState(props: Props) {
|
||||
readMoreUrl ||
|
||||
'https://backstage.io/docs/features/software-catalog/well-known-annotations';
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
return (
|
||||
<EmptyState
|
||||
missing="field"
|
||||
title="Missing Annotation"
|
||||
description={generateDescription(annotations)}
|
||||
title={t('emptyState.missingAnnotation.title')}
|
||||
description={useGenerateDescription(annotations)}
|
||||
action={
|
||||
<>
|
||||
<Typography variant="body1">
|
||||
Add the annotation to your component YAML as shown in the
|
||||
highlighted example below:
|
||||
{t('emptyState.missingAnnotation.actionTitle')}
|
||||
</Typography>
|
||||
<Box className={classes.code}>
|
||||
<CodeSnippet
|
||||
@@ -128,7 +130,7 @@ export function MissingAnnotationEmptyState(props: Props) {
|
||||
/>
|
||||
</Box>
|
||||
<Button color="primary" component={Link} to={url}>
|
||||
Read more
|
||||
{t('emptyState.missingAnnotation.readMore')}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import MaterialLink, {
|
||||
LinkProps as MaterialLinkProps,
|
||||
@@ -29,6 +30,7 @@ import {
|
||||
LinkProps as RouterLinkProps,
|
||||
Route,
|
||||
} from 'react-router-dom';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
|
||||
export function isReactRouterBeta(): boolean {
|
||||
const [obj] = createRoutesFromChildren(<Route index element={<div />} />);
|
||||
@@ -161,6 +163,7 @@ export const Link = React.forwardRef<any, LinkProps>(
|
||||
({ onClick, noTrack, ...props }, ref) => {
|
||||
const classes = useStyles();
|
||||
const analytics = useAnalytics();
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
// Adding the base path to URLs breaks react-router v6 stable, so we only
|
||||
// do it for beta. The react router version won't change at runtime so it is
|
||||
@@ -199,7 +202,7 @@ export const Link = React.forwardRef<any, LinkProps>(
|
||||
>
|
||||
{props.children}
|
||||
<Typography component="span" className={classes.visuallyHidden}>
|
||||
, Opens in a new window
|
||||
{`, ${t('link.openNewWindow')}`}
|
||||
</Typography>
|
||||
</MaterialLink>
|
||||
) : (
|
||||
|
||||
@@ -23,6 +23,8 @@ import Button from '@material-ui/core/Button';
|
||||
import React, { useState } from 'react';
|
||||
import { isError } from '@backstage/errors';
|
||||
import { PendingOAuthRequest } from '@backstage/core-plugin-api';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
export type LoginRequestListItemClassKey = 'root';
|
||||
|
||||
@@ -44,6 +46,7 @@ type RowProps = {
|
||||
const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => {
|
||||
const classes = useItemStyles();
|
||||
const [error, setError] = useState<string>();
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const handleContinue = async () => {
|
||||
setBusy(true);
|
||||
@@ -68,7 +71,7 @@ const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => {
|
||||
secondary={error && <Typography color="error">{error}</Typography>}
|
||||
/>
|
||||
<Button color="primary" variant="contained" onClick={handleContinue}>
|
||||
Log in
|
||||
{t('oauthRequestDialog.login')}
|
||||
</Button>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
@@ -30,6 +30,8 @@ import {
|
||||
oauthRequestApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
export type OAuthRequestDialogClassKey =
|
||||
| 'dialog'
|
||||
@@ -63,6 +65,7 @@ export function OAuthRequestDialog(_props: {}) {
|
||||
const [busy, setBusy] = useState(false);
|
||||
const oauthRequestApi = useApi(oauthRequestApiRef);
|
||||
const configApi = useApi(configApiRef);
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const authRedirect =
|
||||
configApi.getOptionalBoolean('enableExperimentalRedirectFlow') ?? false;
|
||||
@@ -94,12 +97,10 @@ export function OAuthRequestDialog(_props: {}) {
|
||||
variant="h1"
|
||||
variantMapping={{ h1: 'span' }}
|
||||
>
|
||||
Login Required
|
||||
{t('oauthRequestDialog.title')}
|
||||
</Typography>
|
||||
{authRedirect ? (
|
||||
<Typography>
|
||||
This will trigger a http redirect to OAuth Login.
|
||||
</Typography>
|
||||
<Typography>{t('oauthRequestDialog.authRedirectTitle')}</Typography>
|
||||
) : null}
|
||||
</DialogTitle>
|
||||
|
||||
@@ -118,7 +119,9 @@ export function OAuthRequestDialog(_props: {}) {
|
||||
</main>
|
||||
|
||||
<DialogActions classes={{ root: classes.actionButtons }}>
|
||||
<Button onClick={handleRejectAll}>Reject All</Button>
|
||||
<Button onClick={handleRejectAll}>
|
||||
{t('oauthRequestDialog.rejectAll')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -20,6 +20,8 @@ import React, { PropsWithChildren, ReactNode, useContext } from 'react';
|
||||
|
||||
import { VerticalStepperContext } from './SimpleStepper';
|
||||
import { StepActions } from './types';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
export type SimpleStepperFooterClassKey = 'root';
|
||||
|
||||
@@ -55,9 +57,12 @@ interface BackBtnProps extends CommonBtnProps {
|
||||
disabled?: boolean;
|
||||
stepIndex: number;
|
||||
}
|
||||
export const RestartBtn = ({ text, handleClick }: RestartBtnProps) => (
|
||||
<Button onClick={handleClick}>{text || 'Reset'}</Button>
|
||||
);
|
||||
export const RestartBtn = ({ text, handleClick }: RestartBtnProps) => {
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
return (
|
||||
<Button onClick={handleClick}>{text || t('simpleStepper.reset')}</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const NextBtn = ({
|
||||
text,
|
||||
@@ -65,39 +70,48 @@ const NextBtn = ({
|
||||
disabled,
|
||||
last,
|
||||
stepIndex,
|
||||
}: NextBtnProps) => (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
data-testid={`nextButton-${stepIndex}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{text || (last ? 'Finish' : 'Next')}
|
||||
</Button>
|
||||
);
|
||||
}: NextBtnProps) => {
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
return (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
data-testid={`nextButton-${stepIndex}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{text || (last ? t('simpleStepper.finish') : t('simpleStepper.next'))}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const SkipBtn = ({ text, handleClick, disabled, stepIndex }: SkipBtnProps) => (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
data-testid={`skipButton-${stepIndex}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{text || 'Skip'}
|
||||
</Button>
|
||||
);
|
||||
const SkipBtn = ({ text, handleClick, disabled, stepIndex }: SkipBtnProps) => {
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
return (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
disabled={disabled}
|
||||
data-testid={`skipButton-${stepIndex}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{text || t('simpleStepper.skip')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const BackBtn = ({ text, handleClick, disabled, stepIndex }: BackBtnProps) => (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
data-testid={`backButton-${stepIndex}`}
|
||||
disabled={disabled}
|
||||
>
|
||||
{text || 'Back'}
|
||||
</Button>
|
||||
);
|
||||
const BackBtn = ({ text, handleClick, disabled, stepIndex }: BackBtnProps) => {
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
data-testid={`backButton-${stepIndex}`}
|
||||
disabled={disabled}
|
||||
>
|
||||
{text || t('simpleStepper.back')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export type SimpleStepperFooterProps = {
|
||||
actions?: StepActions;
|
||||
|
||||
@@ -31,6 +31,8 @@ import React, { MouseEventHandler, useState } from 'react';
|
||||
import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks';
|
||||
import { HelpIcon } from '../../icons';
|
||||
import { Link } from '../Link';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
type SupportButtonProps = {
|
||||
title?: string;
|
||||
@@ -85,6 +87,7 @@ const SupportListItem = ({ item }: { item: SupportItem }) => {
|
||||
};
|
||||
|
||||
export function SupportButton(props: SupportButtonProps) {
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
const { title, items, children } = props;
|
||||
const { items: configItems } = useSupportConfig();
|
||||
|
||||
@@ -125,7 +128,7 @@ export function SupportButton(props: SupportButtonProps) {
|
||||
onClick={onClickHandler}
|
||||
startIcon={<HelpIcon />}
|
||||
>
|
||||
Support
|
||||
{t('supportButton.title')}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
@@ -171,7 +174,7 @@ export function SupportButton(props: SupportButtonProps) {
|
||||
onClick={popoverCloseHandler}
|
||||
aria-label="Close"
|
||||
>
|
||||
Close
|
||||
{t('supportButton.close')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Popover>
|
||||
|
||||
@@ -21,6 +21,8 @@ import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Select } from '../Select';
|
||||
import { SelectProps } from '../Select/Select';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
export type TableFiltersClassKey = 'root' | 'value' | 'heder' | 'filters';
|
||||
|
||||
@@ -76,6 +78,7 @@ export const Filters = (props: Props) => {
|
||||
const classes = useFilterStyles();
|
||||
|
||||
const { onChangeFilters } = props;
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const [selectedFilters, setSelectedFilters] = useState<SelectedFilters>({
|
||||
...props.selectedFilters,
|
||||
@@ -96,9 +99,9 @@ export const Filters = (props: Props) => {
|
||||
return (
|
||||
<Box className={classes.root}>
|
||||
<Box className={classes.header}>
|
||||
<Box className={classes.value}>Filters</Box>
|
||||
<Box className={classes.value}>{t('table.filter.title')}</Box>
|
||||
<Button color="primary" onClick={handleClick}>
|
||||
Clear all
|
||||
{t('table.filter.clearAll')}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box className={classes.filters}>
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
|
||||
import { useApiHolder, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { coreComponentsTranslationRef } from '../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
export type SupportItemLink = {
|
||||
url: string;
|
||||
@@ -32,30 +34,34 @@ export type SupportConfig = {
|
||||
items: SupportItem[];
|
||||
};
|
||||
|
||||
const DEFAULT_SUPPORT_CONFIG: SupportConfig = {
|
||||
url: 'https://github.com/backstage/backstage/issues',
|
||||
items: [
|
||||
{
|
||||
title: 'Support Not Configured',
|
||||
icon: 'warning',
|
||||
links: [
|
||||
{
|
||||
// TODO: Update to dedicated support page on backstage.io/docs
|
||||
title: 'Add `app.support` config key',
|
||||
url: 'https://github.com/backstage/backstage/blob/master/app-config.yaml',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
const useDefaultSupportConfig = () => {
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
return {
|
||||
url: 'https://github.com/backstage/backstage/issues',
|
||||
items: [
|
||||
{
|
||||
title: t('supportConfig.default.title'),
|
||||
icon: 'warning',
|
||||
links: [
|
||||
{
|
||||
// TODO: Update to dedicated support page on backstage.io/docs
|
||||
title: t('supportConfig.default.linkTitle'),
|
||||
url: 'https://github.com/backstage/backstage/blob/master/app-config.yaml',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
export function useSupportConfig(): SupportConfig {
|
||||
const apiHolder = useApiHolder();
|
||||
const config = apiHolder.get(configApiRef);
|
||||
const supportConfig = config?.getOptionalConfig('app.support');
|
||||
const defaultSupportConfig = useDefaultSupportConfig();
|
||||
|
||||
if (!supportConfig) {
|
||||
return DEFAULT_SUPPORT_CONFIG;
|
||||
return defaultSupportConfig;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -18,6 +18,8 @@ import Typography from '@material-ui/core/Typography';
|
||||
import React, { ComponentClass, Component, ErrorInfo } from 'react';
|
||||
import { LinkButton } from '../../components/LinkButton';
|
||||
import { ErrorPanel } from '../../components/ErrorPanel';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
type SlackChannel = {
|
||||
name: string;
|
||||
@@ -37,14 +39,21 @@ type State = {
|
||||
|
||||
const SlackLink = (props: { slackChannel?: string | SlackChannel }) => {
|
||||
const { slackChannel } = props;
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
if (!slackChannel) {
|
||||
return null;
|
||||
} else if (typeof slackChannel === 'string') {
|
||||
return <Typography>Please contact {slackChannel} for help.</Typography>;
|
||||
return (
|
||||
<Typography>{t('errorBoundary.title', { slackChannel })}</Typography>
|
||||
);
|
||||
} else if (!slackChannel.href) {
|
||||
return (
|
||||
<Typography>Please contact {slackChannel.name} for help.</Typography>
|
||||
<Typography>
|
||||
{t('errorBoundary.title', {
|
||||
slackChannel: slackChannel.name,
|
||||
})}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import { Link } from '../../components/Link';
|
||||
import { useSupportConfig } from '../../hooks';
|
||||
import { MicDrop } from './MicDrop';
|
||||
import { StackDetails } from './StackDetails';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
interface IErrorPageProps {
|
||||
status?: string;
|
||||
@@ -64,10 +66,17 @@ const useStyles = makeStyles(
|
||||
*
|
||||
*/
|
||||
export function ErrorPage(props: IErrorPageProps) {
|
||||
const { status, statusMessage, additionalInfo, supportUrl, stack } = props;
|
||||
const {
|
||||
status = '',
|
||||
statusMessage,
|
||||
additionalInfo,
|
||||
supportUrl,
|
||||
stack,
|
||||
} = props;
|
||||
const classes = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const support = useSupportConfig();
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
return (
|
||||
<Grid container className={classes.container}>
|
||||
@@ -77,17 +86,17 @@ export function ErrorPage(props: IErrorPageProps) {
|
||||
variant="body1"
|
||||
className={classes.subtitle}
|
||||
>
|
||||
ERROR {status}: {statusMessage}
|
||||
{t('errorPage.subtitle', { status, statusMessage })}
|
||||
</Typography>
|
||||
<Typography variant="body1" className={classes.subtitle}>
|
||||
{additionalInfo}
|
||||
</Typography>
|
||||
<Typography variant="h2" className={classes.title}>
|
||||
Looks like someone dropped the mic!
|
||||
{t('errorPage.title')}
|
||||
</Typography>
|
||||
<Typography variant="h6" className={classes.title}>
|
||||
<Link to="#" data-testid="go-back-link" onClick={() => navigate(-1)}>
|
||||
Go back
|
||||
{t('errorPage.goBack')}
|
||||
</Link>
|
||||
... or please{' '}
|
||||
<Link to={supportUrl || support.url}>contact support</Link> if you
|
||||
|
||||
@@ -19,6 +19,8 @@ import { useState } from 'react';
|
||||
import { Link } from '../../components/Link';
|
||||
import { CodeSnippet } from '../../components';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
|
||||
interface IStackDetailsProps {
|
||||
stack: string;
|
||||
@@ -46,6 +48,7 @@ const useStyles = makeStyles(
|
||||
export function StackDetails(props: IStackDetailsProps) {
|
||||
const { stack } = props;
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const [detailsOpen, setDetailsOpen] = useState<boolean>(false);
|
||||
|
||||
@@ -53,7 +56,7 @@ export function StackDetails(props: IStackDetailsProps) {
|
||||
return (
|
||||
<Typography variant="h6" className={classes.title}>
|
||||
<Link to="#" onClick={() => setDetailsOpen(true)}>
|
||||
Show more details
|
||||
{t('errorPage.showMoreDetails')}
|
||||
</Link>
|
||||
</Typography>
|
||||
);
|
||||
@@ -63,7 +66,7 @@ export function StackDetails(props: IStackDetailsProps) {
|
||||
<>
|
||||
<Typography variant="h6" className={classes.title}>
|
||||
<Link to="#" onClick={() => setDetailsOpen(false)}>
|
||||
Show less details
|
||||
{t('errorPage.showLessDetails')}
|
||||
</Link>
|
||||
</Typography>
|
||||
<CodeSnippet
|
||||
|
||||
@@ -24,6 +24,8 @@ import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
import { ErrorPanel } from '../../components/ErrorPanel';
|
||||
import { Progress } from '../../components/Progress';
|
||||
import { ProxiedSignInIdentity } from './ProxiedSignInIdentity';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
/**
|
||||
* Props for {@link ProxiedSignInPage}.
|
||||
@@ -61,6 +63,7 @@ export type ProxiedSignInPageProps = SignInPageProps & {
|
||||
*/
|
||||
export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => {
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const [{ status, error }, { execute }] = useAsync(async () => {
|
||||
const identity = new ProxiedSignInIdentity({
|
||||
@@ -79,12 +82,7 @@ export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => {
|
||||
if (status === 'loading') {
|
||||
return <Progress />;
|
||||
} else if (error) {
|
||||
return (
|
||||
<ErrorPanel
|
||||
title="You do not appear to be signed in. Please try reloading the browser page."
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
return <ErrorPanel title={t('proxiedSignInPage.title')} error={error} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -34,6 +34,8 @@ import { MobileSidebar } from './MobileSidebar';
|
||||
import { useContent } from './Page';
|
||||
import { SidebarOpenStateProvider } from './SidebarOpenStateContext';
|
||||
import { useSidebarPinState } from './SidebarPinStateContext';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
|
||||
/** @public */
|
||||
export type SidebarClassKey = 'drawer' | 'drawerOpen';
|
||||
@@ -250,6 +252,7 @@ function A11ySkipSidebar() {
|
||||
const { sidebarConfig } = useContext(SidebarConfigContext);
|
||||
const { focusContent, contentRef } = useContent();
|
||||
const classes = useStyles({ sidebarConfig });
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
if (!contentRef?.current) {
|
||||
return null;
|
||||
@@ -260,7 +263,7 @@ function A11ySkipSidebar() {
|
||||
variant="contained"
|
||||
className={classnames(classes.visuallyHidden)}
|
||||
>
|
||||
Skip to content
|
||||
{t('skipToContent')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ import { Page } from '../Page';
|
||||
import { getSignInProviders, useSignInProviders } from './providers';
|
||||
import { GridItem, useStyles } from './styles';
|
||||
import { IdentityProviders, SignInProviderConfig } from './types';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
type MultiSignInPageProps = SignInPageProps & {
|
||||
providers: IdentityProviders;
|
||||
@@ -95,6 +97,7 @@ export const SingleSignInPage = ({
|
||||
const classes = useStyles();
|
||||
const authApi = useApi(provider.apiRef);
|
||||
const configApi = useApi(configApiRef);
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const [error, setError] = useState<Error>();
|
||||
|
||||
@@ -174,7 +177,7 @@ export const SingleSignInPage = ({
|
||||
login({ showPopup: true });
|
||||
}}
|
||||
>
|
||||
Sign In
|
||||
{t('signIn.title')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -28,6 +28,8 @@ import { useApi, errorApiRef } from '@backstage/core-plugin-api';
|
||||
import { GridItem } from './styles';
|
||||
import { ForwardedError } from '@backstage/errors';
|
||||
import { UserIdentity } from './UserIdentity';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
const Component: ProviderComponent = ({
|
||||
config,
|
||||
@@ -38,6 +40,7 @@ const Component: ProviderComponent = ({
|
||||
const { apiRef, title, message } = config as SignInProviderConfig;
|
||||
const authApi = useApi(apiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
@@ -63,7 +66,7 @@ const Component: ProviderComponent = ({
|
||||
);
|
||||
} catch (error) {
|
||||
onSignInFailure();
|
||||
errorApi.post(new ForwardedError('Login failed', error));
|
||||
errorApi.post(new ForwardedError(t('signIn.loginFailed'), error));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,7 +77,7 @@ const Component: ProviderComponent = ({
|
||||
title={title}
|
||||
actions={
|
||||
<Button color="primary" variant="outlined" onClick={handleLogin}>
|
||||
Sign In
|
||||
{t('signIn.title')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -27,6 +27,8 @@ import { InfoCard } from '../InfoCard/InfoCard';
|
||||
import { ProviderComponent, ProviderLoader, SignInProvider } from './types';
|
||||
import { GridItem } from './styles';
|
||||
import { UserIdentity } from './UserIdentity';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
// accept base64url format according to RFC7515 (https://tools.ietf.org/html/rfc7515#section-3)
|
||||
const ID_TOKEN_REGEX = /^[a-z0-9_\-]+\.[a-z0-9_\-]+\.[a-z0-9_\-]+$/i;
|
||||
@@ -44,6 +46,9 @@ const useFormStyles = makeStyles(
|
||||
alignSelf: 'center',
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
subTitle: {
|
||||
whiteSpace: 'pre-line',
|
||||
},
|
||||
}),
|
||||
{ name: 'BackstageCustomProvider' },
|
||||
);
|
||||
@@ -63,6 +68,7 @@ const asInputRef = (renderResult: UseFormRegisterReturn) => {
|
||||
|
||||
const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => {
|
||||
const classes = useFormStyles();
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
const { register, handleSubmit, formState } = useForm<Data>({
|
||||
mode: 'onChange',
|
||||
});
|
||||
@@ -84,18 +90,16 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => {
|
||||
|
||||
return (
|
||||
<GridItem>
|
||||
<InfoCard title="Custom User" variant="fullHeight">
|
||||
<Typography variant="body1">
|
||||
Enter your own User ID and credentials.
|
||||
<br />
|
||||
This selection will not be stored.
|
||||
<InfoCard title={t('signIn.customProvider.title')} variant="fullHeight">
|
||||
<Typography variant="body1" className={classes.subTitle}>
|
||||
{t('signIn.customProvider.subtitle')}
|
||||
</Typography>
|
||||
|
||||
<form className={classes.form} onSubmit={handleSubmit(handleResult)}>
|
||||
<FormControl>
|
||||
<TextField
|
||||
{...asInputRef(register('userId', { required: true }))}
|
||||
label="User ID"
|
||||
label={t('signIn.customProvider.userId')}
|
||||
margin="normal"
|
||||
error={Boolean(errors.userId)}
|
||||
/>
|
||||
@@ -111,10 +115,10 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => {
|
||||
validate: token =>
|
||||
!token ||
|
||||
ID_TOKEN_REGEX.test(token) ||
|
||||
'Token is not a valid OpenID Connect JWT Token',
|
||||
t('signIn.customProvider.tokenInvalid'),
|
||||
}),
|
||||
)}
|
||||
label="ID Token (optional)"
|
||||
label={t('signIn.customProvider.idToken')}
|
||||
margin="normal"
|
||||
autoComplete="off"
|
||||
error={Boolean(errors.idToken)}
|
||||
@@ -130,7 +134,7 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => {
|
||||
className={classes.button}
|
||||
disabled={!formState?.isDirty || !isEmpty(errors)}
|
||||
>
|
||||
Continue
|
||||
{t('signIn.customProvider.continue')}
|
||||
</Button>
|
||||
</form>
|
||||
</InfoCard>
|
||||
|
||||
@@ -25,6 +25,8 @@ import { discoveryApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { GuestUserIdentity } from './GuestUserIdentity';
|
||||
import useLocalStorage from 'react-use/lib/useLocalStorage';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { coreComponentsTranslationRef } from '../../translation';
|
||||
import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
|
||||
|
||||
const getIdentity = async (identity: ProxiedSignInIdentity) => {
|
||||
try {
|
||||
@@ -48,6 +50,7 @@ const Component: ProviderComponent = ({
|
||||
}) => {
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
const [_, setUseLegacyGuestToken] = useLocalStorage('enableLegacyGuestToken');
|
||||
const { t } = useTranslationRef(coreComponentsTranslationRef);
|
||||
|
||||
const handle = async () => {
|
||||
onSignInStarted();
|
||||
@@ -85,11 +88,13 @@ const Component: ProviderComponent = ({
|
||||
variant="fullHeight"
|
||||
actions={
|
||||
<Button color="primary" variant="outlined" onClick={handle}>
|
||||
Enter
|
||||
{t('signIn.guestProvider.enter')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Typography variant="body1">Sign in as a Guest.</Typography>
|
||||
<Typography variant="body1" style={{ whiteSpace: 'pre-line' }}>
|
||||
{t('signIn.guestProvider.subtitle')}
|
||||
</Typography>
|
||||
</InfoCard>
|
||||
</GridItem>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2023 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 coreComponentsTranslationRef = createTranslationRef({
|
||||
id: 'core-components',
|
||||
messages: {
|
||||
signIn: {
|
||||
title: 'Sign In',
|
||||
loginFailed: 'Login failed',
|
||||
customProvider: {
|
||||
title: 'Custom User',
|
||||
subtitle:
|
||||
'Enter your own User ID and credentials.\n This selection will not be stored.',
|
||||
userId: 'User ID',
|
||||
tokenInvalid: 'Token is not a valid OpenID Connect JWT Token',
|
||||
continue: 'Continue',
|
||||
idToken: 'ID Token (optional)',
|
||||
},
|
||||
guestProvider: {
|
||||
title: 'Guest',
|
||||
subtitle:
|
||||
'Enter as a Guest User.\n You will not have a verified identity, meaning some features might be unavailable.',
|
||||
enter: 'Enter',
|
||||
},
|
||||
},
|
||||
skipToContent: 'Skip to content',
|
||||
copyTextButton: {
|
||||
tooltipText: 'Text copied to clipboard',
|
||||
},
|
||||
simpleStepper: {
|
||||
reset: 'Reset',
|
||||
finish: 'Finish',
|
||||
next: 'Next',
|
||||
skip: 'Skip',
|
||||
back: 'Back',
|
||||
},
|
||||
errorPage: {
|
||||
subtitle: 'ERROR {{status}}: {{statusMessage}}',
|
||||
title: 'Looks like someone dropped the mic!',
|
||||
goBack: 'Go back',
|
||||
showMoreDetails: 'Show more details',
|
||||
showLessDetails: 'Show less details',
|
||||
},
|
||||
emptyState: {
|
||||
missingAnnotation: {
|
||||
title: 'Missing Annotation',
|
||||
actionTitle:
|
||||
'Add the annotation to your component YAML as shown in the highlighted example below:',
|
||||
readMore: 'Read more',
|
||||
},
|
||||
},
|
||||
supportConfig: {
|
||||
default: {
|
||||
title: 'Support Not Configured',
|
||||
linkTitle: 'Add `app.support` config key',
|
||||
},
|
||||
},
|
||||
errorBoundary: {
|
||||
title: 'Please contact {{slackChannel}} for help.',
|
||||
},
|
||||
oauthRequestDialog: {
|
||||
title: 'Login Required',
|
||||
authRedirectTitle: 'This will trigger a http redirect to OAuth Login.',
|
||||
login: 'Log in',
|
||||
rejectAll: 'Reject All',
|
||||
},
|
||||
link: {
|
||||
openNewWindow: 'Opens in a new window',
|
||||
},
|
||||
supportButton: {
|
||||
title: 'Support',
|
||||
close: 'Close',
|
||||
},
|
||||
table: {
|
||||
filter: {
|
||||
title: 'Filters',
|
||||
clearAll: 'Clear all',
|
||||
},
|
||||
},
|
||||
alertDisplay: {
|
||||
message_one: '({{ count }} older message)',
|
||||
message_other: '({{ count }} older messages)',
|
||||
},
|
||||
autoLogout: {
|
||||
stillTherePrompt: {
|
||||
title: 'Logging out due to inactivity',
|
||||
buttonText: "Yes! Don't log me out",
|
||||
},
|
||||
},
|
||||
proxiedSignInPage: {
|
||||
title:
|
||||
'You do not appear to be signed in. Please try reloading the browser page.',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { renderWithEffects, TestApiProvider } from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { BadgesApi, badgesApiRef } from '../api';
|
||||
import { EntityBadgesDialog } from './EntityBadgesDialog';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
@@ -43,7 +43,7 @@ describe('EntityBadgesDialog', () => {
|
||||
kind: 'MockKind',
|
||||
} as Entity;
|
||||
|
||||
const rendered = await renderWithEffects(
|
||||
const rendered = await renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[badgesApiRef, mockApi],
|
||||
|
||||
+3
-3
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { BitriseBuildsComponent } from './BitriseBuildsComponent';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
let entityValue: {
|
||||
entity: { metadata: { annotations?: { [key: string]: string } } };
|
||||
@@ -48,10 +48,10 @@ jest.mock('../BitriseBuildsTableComponent', () => ({
|
||||
describe('BitriseArtifactsComponent', () => {
|
||||
entityValue = { entity: { metadata: {} } };
|
||||
|
||||
const renderComponent = () => render(<BitriseBuildsComponent />);
|
||||
const renderComponent = () => renderInTestApp(<BitriseBuildsComponent />);
|
||||
|
||||
it('should display an empty state if an app annotation is missing', async () => {
|
||||
const rendered = renderComponent();
|
||||
const rendered = await renderComponent();
|
||||
|
||||
expect(await rendered.findByText('Missing Annotation')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/core-app-api": "workspace:^",
|
||||
"@backstage/dev-utils": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
|
||||
@@ -21,6 +21,10 @@ import { Features } from './Features';
|
||||
import { mockCalverProject } from '../test-helpers/test-helpers';
|
||||
import { TEST_IDS } from '../test-helpers/test-ids';
|
||||
import { mockApiClient } from '../test-helpers/mock-api-client';
|
||||
import { MockErrorApi, TestApiProvider } from '@backstage/test-utils';
|
||||
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { MockTranslationApi } from '@backstage/test-utils/alpha';
|
||||
import { errorApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
jest.mock('@backstage/core-plugin-api', () => ({
|
||||
...jest.requireActual('@backstage/core-plugin-api'),
|
||||
@@ -35,18 +39,26 @@ jest.mock('../contexts/ProjectContext', () => ({
|
||||
describe('Features', () => {
|
||||
it('should omit features omitted via configuration', async () => {
|
||||
const { getByTestId } = render(
|
||||
<Features
|
||||
features={{
|
||||
info: { omit: false },
|
||||
createRc: { omit: true },
|
||||
promoteRc: { omit: true },
|
||||
patch: { omit: true },
|
||||
custom: {
|
||||
// shouldn't trigger "missing key" warning in console
|
||||
factory: () => [<div>Custom 1</div>, <div>Custom 2</div>],
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[translationApiRef, MockTranslationApi.create()],
|
||||
[errorApiRef, new MockErrorApi()],
|
||||
]}
|
||||
>
|
||||
<Features
|
||||
features={{
|
||||
info: { omit: false },
|
||||
createRc: { omit: true },
|
||||
promoteRc: { omit: true },
|
||||
patch: { omit: true },
|
||||
custom: {
|
||||
// shouldn't trigger "missing key" warning in console
|
||||
factory: () => [<div>Custom 1</div>, <div>Custom 2</div>],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
,
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => getByTestId(TEST_IDS.info.info));
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import {
|
||||
mockCalverProject,
|
||||
mockReleaseBranch,
|
||||
mockReleaseCandidateCalver,
|
||||
} from '../../test-helpers/test-helpers';
|
||||
import { Info } from './Info';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
jest.mock('../../contexts/ProjectContext', () => ({
|
||||
useProjectContext: () => ({
|
||||
@@ -32,7 +31,7 @@ jest.mock('../../contexts/ProjectContext', () => ({
|
||||
|
||||
describe('Info', () => {
|
||||
it('should return early if no latestRelease exists', async () => {
|
||||
const { findByText } = render(
|
||||
const { findByText } = await renderInTestApp(
|
||||
<Info
|
||||
latestRelease={mockReleaseCandidateCalver}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, waitFor, screen } from '@testing-library/react';
|
||||
import { waitFor, screen, render } from '@testing-library/react';
|
||||
|
||||
import {
|
||||
mockBumpedTag,
|
||||
@@ -29,6 +29,10 @@ import {
|
||||
import { mockApiClient } from '../../test-helpers/mock-api-client';
|
||||
import { PatchBody } from './PatchBody';
|
||||
import { TEST_IDS } from '../../test-helpers/test-ids';
|
||||
import { MockErrorApi, TestApiProvider } from '@backstage/test-utils';
|
||||
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { MockTranslationApi } from '@backstage/test-utils/alpha';
|
||||
import { errorApiRef } from '@backstage/core-plugin-api';
|
||||
|
||||
jest.mock('@backstage/core-plugin-api', () => ({
|
||||
...jest.requireActual('@backstage/core-plugin-api'),
|
||||
@@ -56,13 +60,21 @@ describe('PatchBody', () => {
|
||||
});
|
||||
|
||||
const { getByTestId } = render(
|
||||
<PatchBody
|
||||
bumpedTag={mockBumpedTag}
|
||||
latestRelease={mockReleaseCandidateCalver}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
tagParts={mockTagParts}
|
||||
ctaMessage={mockCtaMessage}
|
||||
/>,
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[translationApiRef, MockTranslationApi.create()],
|
||||
[errorApiRef, new MockErrorApi()],
|
||||
]}
|
||||
>
|
||||
<PatchBody
|
||||
bumpedTag={mockBumpedTag}
|
||||
latestRelease={mockReleaseCandidateCalver}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
tagParts={mockTagParts}
|
||||
ctaMessage={mockCtaMessage}
|
||||
/>
|
||||
,
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(getByTestId(TEST_IDS.patch.loading)).toBeInTheDocument();
|
||||
@@ -74,13 +86,20 @@ describe('PatchBody', () => {
|
||||
|
||||
it('should render not-prerelease description', async () => {
|
||||
const { getByTestId } = render(
|
||||
<PatchBody
|
||||
latestRelease={mockReleaseVersionCalver}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
bumpedTag={mockBumpedTag}
|
||||
tagParts={mockTagParts}
|
||||
ctaMessage={mockCtaMessage}
|
||||
/>,
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[translationApiRef, MockTranslationApi.create()],
|
||||
[errorApiRef, new MockErrorApi()],
|
||||
]}
|
||||
>
|
||||
<PatchBody
|
||||
latestRelease={mockReleaseVersionCalver}
|
||||
releaseBranch={mockReleaseBranch}
|
||||
bumpedTag={mockBumpedTag}
|
||||
tagParts={mockTagParts}
|
||||
ctaMessage={mockCtaMessage}
|
||||
/>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(getByTestId(TEST_IDS.patch.loading)).toBeInTheDocument();
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Entity } from '@backstage/catalog-model';
|
||||
import { ConfigReader } from '@backstage/core-app-api';
|
||||
import { ConfigApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { renderWithEffects, TestApiProvider } from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { GoCdBuildsComponent } from './GoCdBuildsComponent';
|
||||
import { gocdApiRef } from '../../plugin';
|
||||
import { GoCdApi } from '../../api/gocdApi';
|
||||
@@ -41,7 +41,7 @@ describe('GoCdArtifactsComponent', () => {
|
||||
};
|
||||
|
||||
const renderComponent = () =>
|
||||
renderWithEffects(
|
||||
renderInTestApp(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[gocdApiRef, mockApiClient],
|
||||
|
||||
@@ -16,19 +16,18 @@
|
||||
|
||||
import React from 'react';
|
||||
import { VisitList } from './VisitList';
|
||||
import { render } from '@testing-library/react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('<VisitList/>', () => {
|
||||
it('renders with mandatory parameters', async () => {
|
||||
const { getByText } = await render(
|
||||
const { getByText } = await renderInTestApp(
|
||||
<VisitList title="My title" detailType="time-ago" />,
|
||||
);
|
||||
expect(getByText('My title')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders skeleton when loading is true', async () => {
|
||||
const { container } = await render(
|
||||
const { container } = await renderInTestApp(
|
||||
<VisitList title="My title" detailType="time-ago" loading />,
|
||||
);
|
||||
expect(container.querySelectorAll('li')).toHaveLength(8);
|
||||
@@ -36,7 +35,7 @@ describe('<VisitList/>', () => {
|
||||
});
|
||||
|
||||
it('renders specified amount of items', async () => {
|
||||
const { container } = await render(
|
||||
const { container } = await renderInTestApp(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
@@ -49,7 +48,7 @@ describe('<VisitList/>', () => {
|
||||
});
|
||||
|
||||
it('renders some items hidden', async () => {
|
||||
const { container } = await render(
|
||||
const { container } = await renderInTestApp(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
@@ -63,7 +62,7 @@ describe('<VisitList/>', () => {
|
||||
});
|
||||
|
||||
it('renders all items when not collapsed', async () => {
|
||||
const { container } = await render(
|
||||
const { container } = await renderInTestApp(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
@@ -78,23 +77,20 @@ describe('<VisitList/>', () => {
|
||||
});
|
||||
|
||||
it('renders visit with time-ago', async () => {
|
||||
const { container, getByText } = await render(
|
||||
<BrowserRouter>
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
,
|
||||
</BrowserRouter>,
|
||||
const { container, getByText } = await renderInTestApp(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="time-ago"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
expect(container.querySelectorAll('li')).toHaveLength(1);
|
||||
expect(getByText('Explore Backstage')).toBeInTheDocument();
|
||||
@@ -102,23 +98,20 @@ describe('<VisitList/>', () => {
|
||||
});
|
||||
|
||||
it('renders visit with hits', async () => {
|
||||
const { container, getByText } = await render(
|
||||
<BrowserRouter>
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="hits"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
,
|
||||
</BrowserRouter>,
|
||||
const { container, getByText } = await renderInTestApp(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="hits"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
expect(container.querySelectorAll('li')).toHaveLength(1);
|
||||
expect(getByText('Explore Backstage')).toBeInTheDocument();
|
||||
@@ -126,23 +119,20 @@ describe('<VisitList/>', () => {
|
||||
});
|
||||
|
||||
it('renders text warning about few items', async () => {
|
||||
const { getByText } = await render(
|
||||
<BrowserRouter>
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="hits"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
,
|
||||
</BrowserRouter>,
|
||||
const { getByText } = await renderInTestApp(
|
||||
<VisitList
|
||||
title="My title"
|
||||
detailType="hits"
|
||||
visits={[
|
||||
{
|
||||
id: 'explore',
|
||||
name: 'Explore Backstage',
|
||||
pathname: '/explore',
|
||||
hits: 35,
|
||||
timestamp: Date.now() - 86400_000,
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
getByText('The more pages you visit, the more pages will appear here.'),
|
||||
@@ -150,10 +140,8 @@ describe('<VisitList/>', () => {
|
||||
});
|
||||
|
||||
it('renders text warning about no items', async () => {
|
||||
const { getByText } = await render(
|
||||
<BrowserRouter>
|
||||
<VisitList title="My title" detailType="hits" visits={[]} />,
|
||||
</BrowserRouter>,
|
||||
const { getByText } = await renderInTestApp(
|
||||
<VisitList title="My title" detailType="hits" visits={[]} />,
|
||||
);
|
||||
expect(getByText('There are no visits to show yet.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import { FixDialog } from './FixDialog';
|
||||
import { Pod } from 'kubernetes-models/v1/Pod';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
jest.mock('../Events', () => ({
|
||||
Events: () => {
|
||||
@@ -33,8 +32,8 @@ jest.mock('../PodLogs', () => ({
|
||||
}));
|
||||
|
||||
describe('FixDialog', () => {
|
||||
it('docs link should render', () => {
|
||||
const { getByText } = render(
|
||||
it('docs link should render', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<FixDialog
|
||||
open
|
||||
clusterName="some-cluster"
|
||||
@@ -74,8 +73,8 @@ describe('FixDialog', () => {
|
||||
expect(getByText('fix1')).toBeInTheDocument();
|
||||
expect(getByText('fix2')).toBeInTheDocument();
|
||||
});
|
||||
it('events button should render', () => {
|
||||
const { getByText } = render(
|
||||
it('events button should render', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<FixDialog
|
||||
open
|
||||
clusterName="some-cluster"
|
||||
@@ -115,8 +114,8 @@ describe('FixDialog', () => {
|
||||
expect(getByText('fix1')).toBeInTheDocument();
|
||||
expect(getByText('fix2')).toBeInTheDocument();
|
||||
});
|
||||
it('Logs button should render', () => {
|
||||
const { getByText } = render(
|
||||
it('Logs button should render', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<FixDialog
|
||||
open
|
||||
clusterName="some-cluster"
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { TechDocsSearchResultListItem } from './TechDocsSearchResultListItem';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
// Using canvas to render text..
|
||||
jest.mock('react-text-truncate', () => {
|
||||
@@ -46,7 +46,7 @@ const validResultWithTitle = {
|
||||
|
||||
describe('TechDocsSearchResultListItem test', () => {
|
||||
it('should render search doc passed in', async () => {
|
||||
const { findByText } = render(
|
||||
const { findByText } = await renderInTestApp(
|
||||
<TechDocsSearchResultListItem result={validResult} />,
|
||||
);
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('TechDocsSearchResultListItem test', () => {
|
||||
});
|
||||
|
||||
it('should use title if defined', async () => {
|
||||
const { findByText } = render(
|
||||
const { findByText } = await renderInTestApp(
|
||||
<TechDocsSearchResultListItem
|
||||
result={validResult}
|
||||
title="Count Dookumentation"
|
||||
@@ -77,7 +77,7 @@ describe('TechDocsSearchResultListItem test', () => {
|
||||
});
|
||||
|
||||
it('should use entity title if defined', async () => {
|
||||
const { findByText } = render(
|
||||
const { findByText } = await renderInTestApp(
|
||||
<TechDocsSearchResultListItem result={validResultWithTitle} />,
|
||||
);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { renderWithEffects, TestApiProvider } from '@backstage/test-utils';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { TodoApi, todoApiRef } from '../../api';
|
||||
import { TodoList } from './TodoList';
|
||||
@@ -43,7 +43,7 @@ describe('TodoList', () => {
|
||||
kind: 'MockKind',
|
||||
} as Entity;
|
||||
|
||||
const rendered = await renderWithEffects(
|
||||
const rendered = await renderInTestApp(
|
||||
<TestApiProvider apis={[[todoApiRef, mockApi]]}>
|
||||
<EntityProvider entity={mockEntity}>
|
||||
<TodoList />
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
|
||||
import React from 'react';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import {
|
||||
renderInTestApp,
|
||||
setupRequestMockHandlers,
|
||||
} from '@backstage/test-utils';
|
||||
import { ComponentEntity } from '@backstage/catalog-model';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { EntityVaultCard } from './EntityVaultCard';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
|
||||
@@ -40,7 +43,7 @@ describe('EntityVaultCard', () => {
|
||||
};
|
||||
|
||||
it('should render missing entity annotation', async () => {
|
||||
const rendered = render(
|
||||
const rendered = await renderInTestApp(
|
||||
<EntityProvider entity={entityAnnotationMissing}>
|
||||
<EntityVaultCard />
|
||||
</EntityProvider>,
|
||||
|
||||
@@ -24,6 +24,9 @@ import {
|
||||
featureFlagsApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { translationApiRef } from '@backstage/core-plugin-api/alpha';
|
||||
import { MockTranslationApi } from '@backstage/test-utils/alpha';
|
||||
|
||||
const configApi = new ConfigReader({});
|
||||
const featureFlagsApi = new LocalStorageFeatureFlags();
|
||||
const alertApi = new AlertApiForwarder();
|
||||
@@ -55,6 +58,7 @@ const oktaAuthApi = OktaAuth.create({
|
||||
basePath: '/auth/',
|
||||
oauthRequestApi,
|
||||
});
|
||||
const translationApi = MockTranslationApi.create();
|
||||
|
||||
export const apis = [
|
||||
[configApiRef, configApi],
|
||||
@@ -67,4 +71,5 @@ export const apis = [
|
||||
[githubAuthApiRef, githubAuthApi],
|
||||
[gitlabAuthApiRef, gitlabAuthApi],
|
||||
[oktaAuthApiRef, oktaAuthApi],
|
||||
[translationApiRef, translationApi],
|
||||
];
|
||||
|
||||
@@ -6722,6 +6722,7 @@ __metadata:
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/integration": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@material-ui/core": ^4.12.2
|
||||
"@material-ui/icons": ^4.9.1
|
||||
"@material-ui/lab": 4.0.0-alpha.61
|
||||
|
||||
Reference in New Issue
Block a user