diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2024-11-05 18:07:01 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-11-13 20:05:48 +0000 |
commit | 29168ab9565bcc29a752a58efb3c09848f7b2b03 (patch) | |
tree | 9c1435e35e724a829fb5a5856ba4049d5e3d1887 /server/sonar-web/src/main/js | |
parent | b2a3c8a65a92fdac25042a05cd37c43fa18c67c3 (diff) | |
download | sonarqube-29168ab9565bcc29a752a58efb3c09848f7b2b03.tar.gz sonarqube-29168ab9565bcc29a752a58efb3c09848f7b2b03.zip |
SONAR-23596 New branding
SONAR-23597 SONAR-23598 SONAR-23595
Diffstat (limited to 'server/sonar-web/src/main/js')
54 files changed, 879 insertions, 195 deletions
diff --git a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx index faa3e3c4fa3..65e122d5618 100644 --- a/server/sonar-web/src/main/js/app/components/AdminContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/AdminContainer.tsx @@ -26,7 +26,8 @@ import { getSettingsNavigation } from '../../api/navigation'; import { getPendingPlugins } from '../../api/plugins'; import { getSystemStatus, waitSystemUPStatus } from '../../api/system'; import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization'; -import { translate, translateWithParameters } from '../../helpers/l10n'; +import { translate } from '../../helpers/l10n'; +import { getIntl } from '../../helpers/l10nBundle'; import { AdminPagesContext } from '../../types/admin'; import { AppState } from '../../types/appstate'; import { PendingPluginResult } from '../../types/plugins'; @@ -46,6 +47,7 @@ interface State { } export class AdminContainer extends React.PureComponent<AdminContainerProps, State> { + intl = getIntl(); mounted = false; portalAnchor: Element | null = null; state: State = { @@ -129,9 +131,9 @@ export class AdminContainer extends React.PureComponent<AdminContainerProps, Sta <> <Helmet defer={false} - titleTemplate={translateWithParameters( - 'page_title.template.with_category', - translate('layout.settings'), + titleTemplate={this.intl.formatMessage( + { id: 'page_title.template.with_category' }, + { page: translate('layout.settings') }, )} /> {this.portalAnchor && diff --git a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx index 34913e58aff..31fb9b35a42 100644 --- a/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ComponentContainer.tsx @@ -22,6 +22,7 @@ import { differenceBy } from 'lodash'; import * as React from 'react'; import { createPortal } from 'react-dom'; import { Helmet } from 'react-helmet-async'; +import { useIntl } from 'react-intl'; import { Outlet } from 'react-router-dom'; import { CenteredLayout, Spinner } from '~design-system'; import { useLocation, useRouter } from '~sonar-aligned/components/hoc/withRouter'; @@ -31,7 +32,6 @@ import { validateProjectAlmBinding } from '../../api/alm-settings'; import { getTasksForComponent } from '../../api/ce'; import { getComponentData } from '../../api/components'; import { getComponentNavigation } from '../../api/navigation'; -import { translateWithParameters } from '../../helpers/l10n'; import { HttpStatus } from '../../helpers/request'; import { getPortfolioUrl, getProjectUrl, getPullRequestUrl } from '../../helpers/urls'; import { useCurrentBranchQuery } from '../../queries/branch'; @@ -63,6 +63,8 @@ function ComponentContainer({ hasFeature }: Readonly<WithAvailableFeaturesProps> } = useLocation(); const router = useRouter(); + const intl = useIntl(); + const [component, setComponent] = React.useState<Component>(); const [currentTask, setCurrentTask] = React.useState<Task>(); const [tasksInProgress, setTasksInProgress] = React.useState<Task[]>(); @@ -285,9 +287,9 @@ function ComponentContainer({ hasFeature }: Readonly<WithAvailableFeaturesProps> <div> <Helmet defer={false} - titleTemplate={translateWithParameters( - 'page_title.template.with_instance', - component?.name ?? '', + titleTemplate={intl.formatMessage( + { id: 'page_title.template.with_instance' }, + { project: component?.name ?? '' }, )} /> {component && diff --git a/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx b/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx index cce2c4419d6..106ca9b1439 100644 --- a/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx +++ b/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx @@ -33,6 +33,7 @@ import { Title, } from '~design-system'; import { Image } from '~sonar-aligned/components/common/Image'; +import { SonarQubeConnectionIllustration } from '../../components/branding/SonarQubeConnectionIllustration'; import { whenLoggedIn } from '../../components/hoc/whenLoggedIn'; import { translate, translateWithParameters } from '../../helpers/l10n'; import { generateSonarLintUserToken, portIsValid, sendUserToken } from '../../helpers/sonarlint'; @@ -88,13 +89,9 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { {status === Status.request && ( <> <Title>{translate('sonarlint-connection.request.title')}</Title> - <Image - alt="sonarlint-connection-request" - className="sw-my-4" - src="/images/SonarLint-connection-request.png" - /> + <SonarQubeConnectionIllustration className="sw-my-4" connected={false} /> <p className="sw-my-4"> - {translateWithParameters('sonarlint-connection.request.description', ideName)} + <FormattedMessage id="sonarlint-connection.request.description" values={{ ideName }} /> </p> <p className="sw-mb-10">{translate('sonarlint-connection.request.description2')}</p> @@ -110,7 +107,7 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { {status === Status.tokenError && ( <> - <Image alt="sonarlint-token-error" className="sw-my-4 sw-pt-2" src="/images/cross.svg" /> + <Image aria-hidden className="sw-my-4 sw-pt-2" src="/images/cross.svg" /> <Title>{translate('sonarlint-connection.token-error.title')}</Title> <p className="sw-my-4">{translate('sonarlint-connection.token-error.description')}</p> <p className="sw-mb-4"> @@ -131,11 +128,7 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { {status === Status.tokenCreated && newToken && ( <> - <Image - alt="sonarlint-connection-error" - className="sw-my-4 sw-pt-2" - src="/images/check.svg" - /> + <Image aria-hidden className="sw-my-4 sw-pt-2" src="/images/check.svg" /> <Title>{translate('sonarlint-connection.connection-error.title')}</Title> <p className="sw-my-6"> {translate('sonarlint-connection.connection-error.description')} @@ -167,11 +160,7 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) { {status === Status.tokenSent && newToken && ( <> <Title>{translate('sonarlint-connection.success.title')}</Title> - <Image - alt="sonarlint-connection-success" - className="sw-mb-4" - src="/images/SonarLint-connection-ok.png" - /> + <SonarQubeConnectionIllustration className="sw-mb-4" connected /> <p className="sw-my-4"> {translateWithParameters('sonarlint-connection.success.description', newToken.name)} </p> diff --git a/server/sonar-web/src/main/js/app/components/__tests__/AdminContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/AdminContainer-test.tsx index 54ed12766d7..8dc51f4ca7c 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/AdminContainer-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/AdminContainer-test.tsx @@ -29,6 +29,14 @@ import { AdminPagesContext } from '../../../types/admin'; import { AdminContainer, AdminContainerProps } from '../AdminContainer'; import AdminContext from '../AdminContext'; +jest.mock('../../../helpers/l10nBundle', () => { + const bundle = jest.requireActual('../../../helpers/l10nBundle'); + return { + ...bundle, + getIntl: () => ({ formatMessage: jest.fn() }), + }; +}); + jest.mock('../../../api/navigation', () => ({ getSettingsNavigation: jest .fn() diff --git a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx index 70e84d95c97..b9976eb3e21 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx +++ b/server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx @@ -35,15 +35,15 @@ afterEach(() => { }); it('should render the logged-in information', async () => { - renderGlobalFooter(); + renderGlobalFooter({}, { edition: EditionKey.community }); expect(ui.databaseWarningMessage.query()).not.toBeInTheDocument(); expect(ui.footerListItems.getAll()).toHaveLength(7); expect(byText('Community Edition').get()).toBeInTheDocument(); - expect(ui.versionLabel('4.2').get()).toBeInTheDocument(); - expect(await ui.ltaDocumentationLinkActive.find()).toBeInTheDocument(); + expect(await ui.versionLabel('4.2').find()).toBeInTheDocument(); + expect(ui.ltaDocumentationLinkActive.query()).not.toBeInTheDocument(); expect(ui.apiLink.get()).toBeInTheDocument(); }); @@ -85,7 +85,7 @@ it('should not render missing logged-in information', () => { }); it('should not render the logged-in information', () => { - renderGlobalFooter({ hideLoggedInInfo: true }); + renderGlobalFooter({ hideLoggedInInfo: true }, { edition: EditionKey.community }); expect(ui.databaseWarningMessage.query()).not.toBeInTheDocument(); @@ -109,7 +109,7 @@ function renderGlobalFooter( return renderComponent(<GlobalFooter {...props} />, '/', { appState: mockAppState({ productionDatabase: true, - edition: EditionKey.community, + edition: EditionKey.developer, version: '4.2', ...appStateOverride, }), diff --git a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx index c1a7473aa79..be82875356e 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx @@ -156,7 +156,6 @@ function renderInProgressBanner(completedCount: number, total: number) { <span> <FormattedMessage id="indexation.admin_link" - defaultMessage={translate('indexation.admin_link')} values={{ link: renderBackgroundTasksPageLink(false, translate('background_tasks.page')), }} diff --git a/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx b/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx index d29d1223ec4..7b06d3ddcd5 100644 --- a/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx +++ b/server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx @@ -24,7 +24,6 @@ import { CenteredLayout, FlagMessage, Link } from '~design-system'; import withIndexationContext, { WithIndexationContextProps, } from '../../../components/hoc/withIndexationContext'; -import { translate } from '../../../helpers/l10n'; export class PageUnavailableDueToIndexation extends React.PureComponent<WithIndexationContextProps> { componentDidUpdate() { @@ -41,17 +40,14 @@ export class PageUnavailableDueToIndexation extends React.PureComponent<WithInde <CenteredLayout className="sw-flex sw-justify-around"> <FlagMessage className="sw-mt-32" variant="info"> <span className="sw-w-[290px]"> - {translate('indexation.page_unavailable.description')} + <FormattedMessage id="indexation.page_unavailable.description" /> <span className="sw-ml-1"> <FormattedMessage - defaultMessage={translate( - 'indexation.page_unavailable.description.additional_information', - )} id="indexation.page_unavailable.description.additional_information" values={{ link: ( <Link to="https://docs.sonarsource.com/sonarqube/latest/instance-administration/reindexing/"> - {translate('learn_more')} + <FormattedMessage id="learn_more" /> </Link> ), }} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx index 3f72585ddc7..062673766cf 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx @@ -19,11 +19,12 @@ */ import { Link } from '@sonarsource/echoes-react'; +import { useIntl } from 'react-intl'; import { HelperHintIcon } from '~design-system'; import DocHelpTooltip from '~sonar-aligned/components/controls/DocHelpTooltip'; import HelpTooltip from '~sonar-aligned/components/controls/HelpTooltip'; import { DocLink } from '../../../../../helpers/doc-links'; -import { translate, translateWithParameters } from '../../../../../helpers/l10n'; +import { translate } from '../../../../../helpers/l10n'; import { getApplicationAdminUrl } from '../../../../../helpers/urls'; import { useProjectBindingQuery } from '../../../../../queries/devops-integration'; import { AlmKeys } from '../../../../../types/alm-settings'; @@ -48,6 +49,8 @@ export default function BranchHelpTooltip({ const { data: projectBinding } = useProjectBindingQuery(component.key); const isGitLab = projectBinding != null && projectBinding.alm === AlmKeys.GitLab; + const intl = useIntl(); + if (isApplication) { if (!hasManyBranches && canAdminComponent) { return ( @@ -72,9 +75,11 @@ export default function BranchHelpTooltip({ <DocHelpTooltip content={ projectBinding != null - ? translateWithParameters( - `branch_like_navigation.no_branch_support.content_x.${isGitLab ? 'mr' : 'pr'}`, - translate('alm', projectBinding.alm), + ? intl.formatMessage( + { + id: `branch_like_navigation.no_branch_support.content_x.${isGitLab ? 'mr' : 'pr'}`, + }, + { alm: translate('alm', projectBinding.alm) }, ) : translate('branch_like_navigation.no_branch_support.content') } diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx index 22ce2083a08..094f55130f1 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx @@ -19,8 +19,8 @@ */ import { DropdownMenu, DropdownMenuAlign } from '@sonarsource/echoes-react'; +import { FormattedMessage } from 'react-intl'; import { MainMenuItem } from '~design-system'; -import { translate } from '../../../../helpers/l10n'; import { AppState } from '../../../../types/appstate'; import { Extension } from '../../../../types/types'; import withAppStateContext from '../../app-state/withAppStateContext'; @@ -48,7 +48,7 @@ function GlobalNavMore({ appState: { globalPages = [] } }: Readonly<{ appState: > <MainMenuItem> <a aria-haspopup="menu" href="#" id="global-navigation-more" role="button"> - {translate('more')} + <FormattedMessage id="more" /> </a> </MainMenuItem> </DropdownMenu.Root> diff --git a/server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx b/server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx index dc200ed30e2..5a642219a17 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx @@ -18,9 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { LogoSize } from '@sonarsource/echoes-react'; import * as React from 'react'; -import { MainAppBar, SonarQubeLogo } from '~design-system'; +import { MainAppBar } from '~design-system'; import { Image } from '~sonar-aligned/components/common/Image'; +import { SonarQubeProductLogo } from '../../../../components/branding/SonarQubeProductLogo'; import { translate } from '../../../../helpers/l10n'; import { GlobalSettingKeys } from '../../../../types/settings'; import { AppStateContext } from '../../app-state/AppStateContext'; @@ -41,7 +43,7 @@ function LogoWithAriaText() { {customLogoUrl ? ( <Image alt={title} src={customLogoUrl} width={customLogoWidth} /> ) : ( - <SonarQubeLogo /> + <SonarQubeProductLogo hasText size={LogoSize.Large} /> )} </div> ); diff --git a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx index 9e828b956e3..8bf0ca1a0f9 100644 --- a/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx +++ b/server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx @@ -19,11 +19,11 @@ */ import styled from '@emotion/styled'; -import { Button } from '@sonarsource/echoes-react'; +import { Button, Theme, ThemeProvider } from '@sonarsource/echoes-react'; import * as React from 'react'; import { ButtonPrimary, themeBorder, themeColor } from '~design-system'; -import { Image } from '~sonar-aligned/components/common/Image'; import { dismissNotice } from '../../../api/users'; +import { SonarQubeIDEPromotionIllustration } from '../../../components/branding/SonarQubeIDEPromotionIllustration'; import { translate } from '../../../helpers/l10n'; import { NoticeType, isLoggedIn } from '../../../types/users'; import { CurrentUserContextInterface } from '../current-user/CurrentUserContext'; @@ -48,9 +48,11 @@ export function PromotionNotification(props: CurrentUserContextInterface) { return ( <PromotionNotificationWrapper className="it__promotion_notification sw-z-global-popup sw-rounded-1 sw-flex sw-items-center sw-px-4"> - <div className="sw-mr-2"> - <Image alt="SonarQube + SonarLint" height={80} src="/images/sq-sl.svg" /> - </div> + <ThemeProvider theme={Theme.dark}> + <div className="sw-mr-2"> + <SonarQubeIDEPromotionIllustration /> + </div> + </ThemeProvider> <PromotionNotificationContent className="sw-flex-1 sw-px-2 sw-py-4"> <span className="sw-typo-semibold">{translate('promotion.sonarlint.title')}</span> <p className="sw-mt-2">{translate('promotion.sonarlint.content')}</p> diff --git a/server/sonar-web/src/main/js/app/index.ts b/server/sonar-web/src/main/js/app/index.ts index 8ad4712c238..e1b81ebe273 100644 --- a/server/sonar-web/src/main/js/app/index.ts +++ b/server/sonar-web/src/main/js/app/index.ts @@ -52,10 +52,17 @@ async function initApplication() { }, ); - const [l10nBundle, currentUser, appState, availableFeatures] = await Promise.all([ - loadL10nBundle(), + const appState = isMainApp() + ? await getGlobalNavigation().catch((error) => { + // eslint-disable-next-line no-console + console.error(error); + return undefined; + }) + : undefined; + + const [l10nBundle, currentUser, availableFeatures] = await Promise.all([ + loadL10nBundle(appState), isMainApp() ? getCurrentUser() : undefined, - isMainApp() ? getGlobalNavigation() : undefined, isMainApp() ? getAvailableFeatures() : undefined, ]).catch((error) => { // eslint-disable-next-line no-console diff --git a/server/sonar-web/src/main/js/apps/account/Account.tsx b/server/sonar-web/src/main/js/apps/account/Account.tsx index 8f16428327c..8c7b227834c 100644 --- a/server/sonar-web/src/main/js/apps/account/Account.tsx +++ b/server/sonar-web/src/main/js/apps/account/Account.tsx @@ -21,11 +21,12 @@ import * as React from 'react'; import { createPortal } from 'react-dom'; import { Helmet } from 'react-helmet-async'; +import { useIntl } from 'react-intl'; import { Outlet } from 'react-router-dom'; import { LargeCenteredLayout, PageContentFontWrapper, TopBar } from '~design-system'; import A11ySkipTarget from '~sonar-aligned/components/a11y/A11ySkipTarget'; import { useCurrentLoginUser } from '../../app/components/current-user/CurrentUserContext'; -import { translate, translateWithParameters } from '../../helpers/l10n'; +import { translate } from '../../helpers/l10n'; import Nav from './components/Nav'; import UserCard from './components/UserCard'; @@ -33,6 +34,8 @@ export default function Account() { const currentUser = useCurrentLoginUser(); const [portalAnchor, setPortalAnchor] = React.useState<Element | null>(null); + const intl = useIntl(); + // Set portal anchor on mount React.useEffect(() => { setPortalAnchor(document.getElementById('component-nav-portal')); @@ -61,9 +64,9 @@ export default function Account() { <Helmet defaultTitle={title} defer={false} - titleTemplate={translateWithParameters( - 'page_title.template.with_category', - translate('my_account.page'), + titleTemplate={intl.formatMessage( + { id: 'page_title.template.with_category' }, + { page: translate('my_account.page') }, )} /> diff --git a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx index 889e943b41f..daf9032ca96 100644 --- a/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx @@ -53,6 +53,14 @@ jest.mock('../../../../helpers/dates', () => { }; }); +jest.mock('../../../../helpers/l10nBundle', () => { + const bundle = jest.requireActual('../../../../helpers/l10nBundle'); + return { + ...bundle, + getIntl: () => ({ formatMessage: jest.fn(({ id }) => `${id}`) }), + }; +}); + const ui = { pageTitle: byRole('heading', { name: 'audit_logs.page' }), downloadButton: byRole('link', { name: 'download_verb' }), diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts index 37916c840b0..d59b743fc31 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts @@ -36,6 +36,15 @@ import { getPageObjects, renderCodingRulesApp } from '../utils-tests'; const rulesHandler = new CodingRulesServiceMock(); const settingsHandler = new SettingsServiceMock(); + +jest.mock('../../../helpers/l10nBundle', () => { + const bundle = jest.requireActual('../../../helpers/l10nBundle'); + return { + ...bundle, + getIntl: () => ({ formatMessage: jest.fn() }), + }; +}); + afterEach(() => { settingsHandler.reset(); rulesHandler.reset(); @@ -53,7 +62,7 @@ describe('rendering', () => { expect(ui.ruleCleanCodeAttribute(CleanCodeAttribute.Clear).get()).toBeInTheDocument(); // 1 In Rule details + 1 in facet expect(ui.ruleSoftwareQuality(SoftwareQuality.Maintainability).getAll()).toHaveLength(2); - expect(document.title).toEqual('page_title.template.with_category.coding_rules.page'); + expect(document.title).toEqual('coding_rule.page.Java.Awsome java rule'); expect(screen.getByText('Why')).toBeInTheDocument(); expect(screen.getByText('Because')).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts index a68adfaaf8b..7e61a2f7aa9 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts @@ -38,6 +38,14 @@ import { getPageObjects, renderCodingRulesApp } from '../utils-tests'; const rulesHandler = new CodingRulesServiceMock(); const settingsHandler = new SettingsServiceMock(); +jest.mock('../../../helpers/l10nBundle', () => { + const bundle = jest.requireActual('../../../helpers/l10nBundle'); + return { + ...bundle, + getIntl: () => ({ formatMessage: jest.fn() }), + }; +}); + afterEach(() => { rulesHandler.reset(); settingsHandler.reset(); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts index 0deaa28759c..73cbba1cfcc 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts +++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts @@ -28,6 +28,14 @@ import { getPageObjects, renderCodingRulesApp } from '../utils-tests'; const rulesHandler = new CodingRulesServiceMock(); const settingsHandler = new SettingsServiceMock(); +jest.mock('../../../helpers/l10nBundle', () => { + const bundle = jest.requireActual('../../../helpers/l10nBundle'); + return { + ...bundle, + getIntl: () => ({ formatMessage: jest.fn() }), + }; +}); + afterEach(() => { rulesHandler.reset(); settingsHandler.reset(); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx index 1625138526e..77c670e76be 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx @@ -45,6 +45,7 @@ import { DocLink } from '../../../helpers/doc-links'; import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers'; import { KeyboardKeys } from '../../../helpers/keycodes'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { getIntl } from '../../../helpers/l10nBundle'; import { SecurityStandard } from '../../../types/security'; import { SettingsKey } from '../../../types/settings'; import { Dict, Paging, Rule, RuleActivation } from '../../../types/types'; @@ -103,6 +104,7 @@ interface State { const RULE_LIST_HEADER_HEIGHT = 68; export class CodingRulesApp extends React.PureComponent<Props, State> { + intl = getIntl(); mounted = false; constructor(props: Props) { @@ -574,9 +576,9 @@ export class CodingRulesApp extends React.PureComponent<Props, State> { <Helmet defer={false} title={translateWithParameters('coding_rule.page', openRule.langName, openRule.name)} - titleTemplate={translateWithParameters( - 'page_title.template.with_category', - translate('coding_rules.page'), + titleTemplate={this.intl.formatMessage( + { id: 'page_title.template.with_category' }, + { page: translate('coding_rules.page') }, )} /> ) : ( diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx index 139b9a7fbdb..2afc4120eb6 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx @@ -60,13 +60,7 @@ const ui = { }), }; -const original = window.location; - beforeAll(() => { - Object.defineProperty(window, 'location', { - configurable: true, - value: { replace: jest.fn() }, - }); almIntegrationHandler = new AlmIntegrationsServiceMock(); dopTranslationHandler = new DopTranslationServiceMock(); newCodePeriodHandler = new NewCodeDefinitionServiceMock(); @@ -78,9 +72,6 @@ beforeEach(() => { dopTranslationHandler.reset(); newCodePeriodHandler.reset(); }); -afterAll(() => { - Object.defineProperty(window, 'location', { configurable: true, value: original }); -}); it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { const user = userEvent.setup(); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx index d3ee2e65328..2bee7a314df 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx @@ -55,13 +55,8 @@ const ui = { }), instanceSelector: byLabelText(/alm.configuration.selector.label/), }; -const original = window.location; beforeAll(() => { - Object.defineProperty(window, 'location', { - configurable: true, - value: { replace: jest.fn() }, - }); almIntegrationHandler = new AlmIntegrationsServiceMock(); dopTranslationHandler = new DopTranslationServiceMock(); newCodePeriodHandler = new NewCodeDefinitionServiceMock(); @@ -74,10 +69,6 @@ beforeEach(() => { newCodePeriodHandler.reset(); }); -afterAll(() => { - Object.defineProperty(window, 'location', { configurable: true, value: original }); -}); - it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { const user = userEvent.setup(); renderCreateProject(); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx index ca52dfa93b4..20417f5162a 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx @@ -83,13 +83,7 @@ const ui = { globalSettingRadio: byRole('radio', { name: 'new_code_definition.global_setting' }), }; -const original = window.location; - beforeAll(() => { - Object.defineProperty(window, 'location', { - configurable: true, - value: { replace: jest.fn() }, - }); almIntegrationHandler = new AlmIntegrationsServiceMock(); dopTranslationHandler = new DopTranslationServiceMock(); newCodePeriodHandler = new NewCodeDefinitionServiceMock(); @@ -102,10 +96,6 @@ beforeEach(() => { newCodePeriodHandler.reset(); }); -afterAll(() => { - Object.defineProperty(window, 'location', { configurable: true, value: original }); -}); - it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { const user = userEvent.setup(); renderCreateProject(); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx index ce4a24d6a91..a608af96767 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx @@ -77,10 +77,6 @@ const ui = { }; beforeAll(() => { - Object.defineProperty(window, 'location', { - configurable: true, - value: { replace: jest.fn() }, - }); almIntegrationHandler = new AlmIntegrationsServiceMock(); almSettingsHandler = new AlmSettingsServiceMock(); componentsHandler = new ComponentsServiceMock(); diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx index 051bdfb50ea..abf661e987b 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx @@ -130,7 +130,7 @@ describe('issues app', () => { // Are rule headers present? expect(screen.getByRole('heading', { level: 1, name: 'Fix that' })).toBeInTheDocument(); - expect(screen.getByRole('link', { name: 'advancedRuleId' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /^advancedRuleId/ })).toBeInTheDocument(); // Select the "why is this an issue" tab and check its content await user.click( @@ -182,7 +182,7 @@ describe('issues app', () => { // Are rule headers present? expect(screen.getByRole('heading', { level: 1, name: 'Fix this' })).toBeInTheDocument(); - expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /^simpleRuleId/ })).toBeInTheDocument(); // Select the "why is this an issue tab" and check its content await user.click( @@ -195,7 +195,7 @@ describe('issues app', () => { // Are rule headers present? expect(screen.getByRole('heading', { level: 1, name: 'Issue on file' })).toBeInTheDocument(); - expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: /^simpleRuleId/ })).toBeInTheDocument(); // The "Where is the issue" tab should be selected by default. Check its content expect(screen.getAllByRole('button', { name: 'Issue on file' })).toHaveLength(2); // there will be 2 buttons one in concise issue and other in code viewer diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueDetails.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueDetails.tsx index b6f788ab3d0..0179cd64c9a 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueDetails.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueDetails.tsx @@ -21,6 +21,7 @@ import styled from '@emotion/styled'; import { Spinner } from '@sonarsource/echoes-react'; import { Helmet } from 'react-helmet-async'; +import { useIntl } from 'react-intl'; import { FlagMessage, LargeCenteredLayout, @@ -33,7 +34,7 @@ import ScreenPositionHelper from '../../../components/common/ScreenPositionHelpe import { IssueSuggestionCodeTab } from '../../../components/rules/IssueSuggestionCodeTab'; import IssueTabViewer from '../../../components/rules/IssueTabViewer'; import { fillBranchLike } from '../../../helpers/branch-like'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { translate } from '../../../helpers/l10n'; import { useRuleDetailsQuery } from '../../../queries/rules'; import A11ySkipTarget from '../../../sonar-aligned/components/a11y/A11ySkipTarget'; import { isPortfolioLike } from '../../../sonar-aligned/helpers/component'; @@ -82,6 +83,8 @@ export default function IssueDetails({ const { data: ruleData, isLoading: isLoadingRule } = useRuleDetailsQuery({ key: openIssue.rule }); const openRuleDetails = ruleData?.rule; + const intl = useIntl(); + const warning = !canBrowseAllChildProjects && isPortfolioLike(qualifier) && ( <FlagMessage className="it__portfolio_warning sw-flex" @@ -100,9 +103,9 @@ export default function IssueDetails({ <Helmet defer={false} title={openIssue.message} - titleTemplate={translateWithParameters( - 'page_title.template.with_category', - translate('issues.page'), + titleTemplate={intl.formatMessage( + { id: 'page_title.template.with_category' }, + { page: translate('issues.page') }, )} /> <h1 className="sw-sr-only">{translate('issues.page')}</h1> diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx index 99d2cccca84..cc01e0de5f0 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx @@ -165,7 +165,7 @@ export default class IssueHeader extends React.PureComponent<Props, State> { {isExternal ? ( <span>({key})</span> ) : ( - <Link to={getRuleUrl(key)} target="_blank"> + <Link to={getRuleUrl(key)} shouldOpenInNewTab> {key} </Link> )} diff --git a/server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx b/server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx index 155bbc8878b..4a2111cf2fe 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx @@ -20,13 +20,11 @@ import { FormattedMessage } from 'react-intl'; import { formatMeasure } from '~sonar-aligned/helpers/measures'; -import { translate } from '../../../helpers/l10n'; export default function TotalEffort({ effort }: { effort: number }) { return ( <div className="sw-inline-block"> <FormattedMessage - defaultMessage={translate('issue.x_effort')} id="issue.x_effort" values={{ 0: <strong>{formatMeasure(effort, 'WORK_DUR')}</strong> }} /> diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchListRow.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchListRow.tsx index d14f7713ba4..c644d5e774c 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchListRow.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/BranchListRow.tsx @@ -26,6 +26,7 @@ import { IconMoreVertical, Tooltip, } from '@sonarsource/echoes-react'; +import { useIntl } from 'react-intl'; import { ActionCell, Badge, @@ -97,6 +98,8 @@ function referenceBranchDoesNotExist( export default function BranchListRow(props: BranchListRowProps) { const { branch, existingBranches, inheritedSetting } = props; + const intl = useIntl(); + let settingWarning: string | undefined; if (branchInheritsItselfAsReference(branch, inheritedSetting)) { settingWarning = translateWithParameters( @@ -104,9 +107,11 @@ export default function BranchListRow(props: BranchListRowProps) { branch.name, ); } else if (referenceBranchDoesNotExist(branch, existingBranches)) { - settingWarning = translateWithParameters( - 'baseline.reference_branch.does_not_exist', - branch.newCodePeriod?.value ?? '', + settingWarning = intl.formatMessage( + { + id: 'baseline.reference_branch.does_not_exist', + }, + { branch: branch.newCodePeriod?.value ?? '' }, ); } diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx index 10d64eb50c1..1b2456a3c11 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx @@ -18,12 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { FormattedMessage } from 'react-intl'; import { MenuPlacement, OptionProps, components } from 'react-select'; import { Badge, FlagErrorIcon, FormField, InputSelect, SelectionCard } from '~design-system'; import Tooltip from '../../../components/controls/Tooltip'; import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils'; import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { translate } from '../../../helpers/l10n'; import { NewCodeDefinitionType } from '../../../types/new-code-definition'; export interface BaselineSettingReferenceBranchProps { @@ -60,10 +61,12 @@ function renderBranchOption(props: OptionProps<BranchOption, false>) { <components.Option {...props}> {option.isInvalid ? ( <Tooltip - content={translateWithParameters( - 'baseline.reference_branch.does_not_exist', - option.value, - )} + content={ + <FormattedMessage + id="baseline.reference_branch.does_not_exist" + values={{ branch: option.value }} + /> + } > <span> {option.value} <FlagErrorIcon className="sw-ml-2" /> diff --git a/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx b/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx index 8758e89fc38..e2a7bc5e9a8 100644 --- a/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx @@ -394,6 +394,7 @@ function getPageObjects() { branchNCDsBanner: byText(/new_code_definition.auto_update.branch.message/), dismissButton: byLabelText('dismiss'), baselineSpecificAnalysisDate: byText(/January 10, 2018/), + missingReferenceBranchWarning: byText('baseline.reference_branch.does_not_exist'), }; async function appIsLoaded() { diff --git a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx index 9ea67fc2774..93f608d26cd 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx @@ -164,7 +164,6 @@ function renderFirstLine( <span className="sw-typo-semibold" title={formattedAnalysisDate}> <FormattedMessage id="projects.last_analysis_on_x" - defaultMessage={translate('projects.last_analysis_on_x')} values={{ date: <DateFromNow className="sw-typo-default" date={analysisDate} />, }} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx index ad5d7b1510f..0a1b3363e22 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx @@ -22,6 +22,7 @@ import { withTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useCallback, useEffect } from 'react'; import { Helmet } from 'react-helmet-async'; +import { useIntl } from 'react-intl'; import { useNavigate, useParams } from 'react-router-dom'; import { Card, @@ -36,7 +37,7 @@ import { import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import '../../../components/search-navigator.css'; import { DocLink } from '../../../helpers/doc-links'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { translate } from '../../../helpers/l10n'; import { getQualityGateUrl } from '../../../helpers/urls'; import { useQualityGatesQuery } from '../../../queries/quality-gates'; import { QualityGate } from '../../../types/types'; @@ -47,6 +48,7 @@ import ListHeader from './ListHeader'; export default function App() { const { data, isLoading } = useQualityGatesQuery(); + const intl = useIntl(); const { name } = useParams(); const navigate = useNavigate(); const { @@ -79,9 +81,9 @@ export default function App() { <PageContentFontWrapper className="sw-typo-default"> <Helmet defer={false} - titleTemplate={translateWithParameters( - 'page_title.template.with_category', - translate('quality_gates.page'), + titleTemplate={intl.formatMessage( + { id: 'page_title.template.with_category' }, + { page: translate('quality_gates.page') }, )} /> <div className="sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12 sw-w-full"> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx index f8c72d29f8d..a47c1873b6c 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx @@ -20,9 +20,10 @@ import * as React from 'react'; import { Helmet } from 'react-helmet-async'; +import { useIntl } from 'react-intl'; import { Outlet, useSearchParams } from 'react-router-dom'; import { useLocation } from '~sonar-aligned/components/hoc/withRouter'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { translate } from '../../../helpers/l10n'; import ProfileHeader from '../details/ProfileHeader'; import { useQualityProfilesContext } from '../qualityProfilesContext'; import ProfileNotFound from './ProfileNotFound'; @@ -36,6 +37,8 @@ export default function ProfileContainer() { const context = useQualityProfilesContext(); const { profiles } = context; + const intl = useIntl(); + // try to find a quality profile with the given key // if managed to find one, redirect to a new version // otherwise show not found page @@ -63,9 +66,9 @@ export default function ProfileContainer() { <Helmet defer={false} title={profile.name} - titleTemplate={translateWithParameters( - 'page_title.template.with_category', - translate('quality_profiles.page'), + titleTemplate={intl.formatMessage( + { id: 'page_title.template.with_category' }, + { page: translate('quality_profiles.page') }, )} /> <ProfileHeader diff --git a/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx b/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx index e44730a8428..6b1220bcf68 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/Login.tsx @@ -19,8 +19,9 @@ */ import styled from '@emotion/styled'; -import { Spinner } from '@sonarsource/echoes-react'; +import { LogoSize, LogoSonar, Spinner } from '@sonarsource/echoes-react'; import { Helmet } from 'react-helmet-async'; +import { FormattedMessage } from 'react-intl'; import { Card, FlagMessage, @@ -31,8 +32,8 @@ import { themeBorder, themeColor, } from '~design-system'; -import { Image } from '~sonar-aligned/components/common/Image'; import { Location } from '~sonar-aligned/types/router'; +import { SonarQubeProductLogo } from '../../../components/branding/SonarQubeProductLogo'; import { translate } from '../../../helpers/l10n'; import { getReturnUrl } from '../../../helpers/urls'; import { IdentityProvider } from '../../../types/types'; @@ -53,13 +54,15 @@ export default function Login(props: Readonly<LoginProps>) { const displayError = Boolean(location.query.authorizationError); return ( - <div className="sw-flex sw-flex-col sw-items-center" id="login_form"> + <div className="sw-flex sw-flex-col sw-items-center sw-pt-32" id="login_form"> <Helmet defer={false} title={translate('login.page')} /> - <Image alt="" className="sw-mt-32" src="/images/sonar-logo-horizontal.png" /> + <LogoSonar hasText size={LogoSize.Large} /> <Card className="sw-my-14 sw-p-0 sw-w-abs-350"> <PageContentFontWrapper className="sw-typo-lg sw-flex sw-flex-col sw-items-center sw-py-8 sw-px-4"> - <Image alt="" className="sw-mb-6" src="/images/embed-doc/sq-icon.svg" width={28} /> - <Title className="sw-mb-6">{translate('login.login_to_sonarqube')}</Title> + <SonarQubeProductLogo size={LogoSize.Small} /> + <Title className="sw-my-6 sw-text-center"> + <FormattedMessage id="login.login_to_sonarqube" /> + </Title> <Spinner isLoading={loading}> <> {displayError && ( diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts index 530c8a9baa3..6e53ac929b9 100644 --- a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts @@ -40,6 +40,14 @@ jest.mock('../../../helpers/l10n', () => ({ hasMessage: jest.fn(), })); +jest.mock('../../../helpers/l10nBundle', () => { + const bundle = jest.requireActual('../../../helpers/l10nBundle'); + return { + ...bundle, + getIntl: () => ({ formatMessage: jest.fn(({ id }) => `${id}`) }), + }; +}); + const fields = [ { key: 'foo', type: 'STRING' } as SettingFieldDefinition, { key: 'bar', type: 'SINGLE_SELECT_LIST' } as SettingFieldDefinition, diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx index f15e9e0128d..bd30797fb90 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx @@ -23,8 +23,10 @@ import userEvent from '@testing-library/user-event'; import { first } from 'lodash'; import { byRole, byText } from '~sonar-aligned/helpers/testSelector'; import SystemServiceMock from '../../../../api/mocks/SystemServiceMock'; +import { mockAppState } from '../../../../helpers/testMocks'; import { renderAppRoutes } from '../../../../helpers/testReactTestingUtils'; import { AppState } from '../../../../types/appstate'; +import { EditionKey } from '../../../../types/editions'; import routes from '../../routes'; import { LogsLevels } from '../../utils'; @@ -125,7 +127,9 @@ describe('System Info Cluster', () => { }); function renderSystemApp(appState?: AppState) { - return renderAppRoutes('system', routes, { appState }); + return renderAppRoutes('system', routes, { + appState: mockAppState({ edition: EditionKey.developer, ...appState }), + }); } function getPageObjects() { diff --git a/server/sonar-web/src/main/js/components/branding/SonarQubeConnectionIllustration.tsx b/server/sonar-web/src/main/js/components/branding/SonarQubeConnectionIllustration.tsx new file mode 100644 index 00000000000..11c7b8308be --- /dev/null +++ b/server/sonar-web/src/main/js/components/branding/SonarQubeConnectionIllustration.tsx @@ -0,0 +1,199 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { useAppState } from '../../app/components/app-state/withAppStateContext'; +import { EditionKey } from '../../types/editions'; + +interface Props { + className?: string; + connected: boolean; +} + +/** + * This component switches between the Community and Server product versions' logo, + * and between the connected and requested versions of the illustration + */ +export function SonarQubeConnectionIllustration(props: Props) { + const { edition } = useAppState(); + + return edition === EditionKey.community ? ( + <CommunityIllustration {...props} /> + ) : ( + <ServerIllustration {...props} /> + ); +} + +function CommunityIllustration({ connected, ...imageProps }: Props) { + return connected ? ( + <svg + aria-hidden + width="179" + height="98" + viewBox="0 0 179 98" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...imageProps} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M72.005 26h-50.01c-8.836 0-16 7.163-16 16v40c0 8.837 7.164 16 16 16h135c8.837 0 16-7.163 16-16V42c0-8.837-7.163-16-16-16h-57.4l-2 2h59.4c7.732 0 14 6.268 14 14v40c0 7.732-6.268 14-14 14h-135c-7.732 0-14-6.268-14-14V42c0-7.732 6.268-14 14-14h52.01l-2-2Z" + fill="var(--echoes-color-icon-success)" + /> + <path + d="M66.545 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM46.355 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM32.045 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M90.316 63.612h5.232v1.632h-5.232V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <path + d="M155.444 63.552A1.55 1.55 0 0 1 153.892 62c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.551-1.551A1.55 1.55 0 0 1 132.995 38c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.551 1.552ZM132.995 86c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 112.099 62c0 11.524 9.372 20.897 20.896 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 132.995 86Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M147.582 63.552A1.55 1.55 0 0 1 146.03 62c0-7.19-5.845-13.035-13.035-13.035a1.55 1.55 0 0 1-1.551-1.551 1.55 1.55 0 0 1 1.551-1.552c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.551 1.552ZM132.995 78.138c-8.896 0-16.138-7.242-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 119.961 62c0 7.19 5.845 13.034 13.034 13.034a1.55 1.55 0 0 1 1.552 1.552 1.55 1.55 0 0 1-1.552 1.552ZM133.016 70.286a8.254 8.254 0 0 1-5.855-2.42c-3.228-3.228-3.228-8.483 0-11.721a8.232 8.232 0 0 1 5.855-2.431 8.19 8.19 0 0 1 5.855 2.43c.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.386 0-2.69.537-3.662 1.52a5.189 5.189 0 0 0 0 7.324 5.188 5.188 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.254 8.254 0 0 1-5.855 2.42v.011Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M110.658 4.837a2.861 2.861 0 0 1 0 4.044L87.804 31.736a2.861 2.861 0 0 1-4.044 0L72.332 20.308a2.861 2.861 0 0 1 0-4.044 2.861 2.861 0 0 1 4.044 0l9.41 9.401 20.837-20.828a2.86 2.86 0 0 1 4.044 0h-.009Z" + fill="var(--echoes-color-icon-success)" + /> + </svg> + ) : ( + <svg + aria-hidden + width="179" + height="98" + viewBox="0 0 179 98" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...imageProps} + > + <path + d="M35.076 27H80.745L64.703 97H16C7.716 97 1 90.284 1 82V42c0-8.284 6.716-15 15-15h19.076Z" + stroke="var(--echoes-color-border-weak)" + strokeWidth="2" + /> + <path + d="M56.55 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M90.32 63.612h5.233v1.632H90.32V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <path + d="M143.924 97H98.255l16.042-70H163c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15h-19.076Z" + stroke="var(--echoes-color-border-weak)" + strokeWidth="2" + /> + <path + d="M165.448 62.552A1.55 1.55 0 0 1 163.897 61c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 143 37c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.552 1.552ZM143 85c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 122.103 61c0 11.524 9.373 20.897 20.897 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 143 85Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M157.586 62.552A1.55 1.55 0 0 1 156.034 61c0-7.19-5.844-13.035-13.034-13.035a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 143 44.862c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.552 1.552ZM143 77.138c-8.897 0-16.138-7.242-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 129.966 61c0 7.19 5.844 13.034 13.034 13.034a1.55 1.55 0 0 1 1.552 1.552A1.55 1.55 0 0 1 143 77.138ZM143.021 69.286a8.252 8.252 0 0 1-5.855-2.42c-3.228-3.228-3.228-8.483 0-11.721a8.232 8.232 0 0 1 5.855-2.431c2.213 0 4.293.858 5.855 2.43.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.387 0-2.69.537-3.662 1.52a5.187 5.187 0 0 0 0 7.324 5.188 5.188 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.256 8.256 0 0 1-5.855 2.42v.011Z" + fill="var(--echoes-logos-colors-brand)" + /> + </svg> + ); +} + +function ServerIllustration({ connected, ...imageProps }: Props) { + return connected ? ( + <svg + aria-hidden + width="179" + height="98" + viewBox="0 0 179 98" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...imageProps} + > + <path + fillRule="evenodd" + clipRule="evenodd" + d="M72.005 26h-50.01c-8.836 0-16 7.163-16 16v40c0 8.837 7.164 16 16 16h135c8.837 0 16-7.163 16-16V42c0-8.837-7.163-16-16-16h-57.4l-2 2h59.4c7.732 0 14 6.268 14 14v40c0 7.732-6.268 14-14 14h-135c-7.732 0-14-6.268-14-14V42c0-7.732 6.268-14 14-14h52.01l-2-2Z" + fill="var(--echoes-color-icon-success)" + /> + <path + d="M66.545 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM46.355 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM32.045 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M90.316 63.612h5.232v1.632h-5.232V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <g clipPath="url(#sqciok)" fill="var(--echoes-logos-colors-brand)"> + <path d="M155.268 63.54a1.54 1.54 0 0 1-1.539-1.54c0-11.433-9.301-20.733-20.734-20.733a1.54 1.54 0 0 1 0-3.079c13.131 0 23.814 10.682 23.814 23.812 0 .85-.69 1.54-1.54 1.54h-.001ZM132.994 85.812c-13.129 0-23.811-10.681-23.811-23.812a1.54 1.54 0 0 1 3.079 0c0 11.433 9.3 20.733 20.732 20.733a1.54 1.54 0 1 1 0 3.079ZM139.677 63.54c-.85 0-1.54-.69-1.54-1.54a5.147 5.147 0 0 0-5.142-5.141 1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.539 1.54h-.001ZM132.994 70.222c-4.532 0-8.221-3.688-8.221-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 1 1 0 3.079h.001Z" /> + <path d="M147.472 63.54a1.54 1.54 0 0 1-1.539-1.54c0-7.133-5.804-12.937-12.939-12.937a1.54 1.54 0 0 1 0-3.078c8.833 0 16.018 7.185 16.018 16.016 0 .85-.69 1.54-1.54 1.54v-.002ZM132.994 78.017c-8.831 0-16.016-7.185-16.016-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 1 1 0 3.08h.001Z" /> + </g> + <path + d="M110.658 4.837a2.861 2.861 0 0 1 0 4.044L87.804 31.736a2.861 2.861 0 0 1-4.044 0L72.332 20.308a2.861 2.861 0 0 1 0-4.044 2.861 2.861 0 0 1 4.044 0l9.41 9.401 20.837-20.828a2.86 2.86 0 0 1 4.044 0h-.009Z" + fill="var(--echoes-color-icon-success)" + /> + <defs> + <clipPath id="sqciok"> + <path fill="#fff" transform="translate(108.995 38)" d="M0 0h48v48H0z" /> + </clipPath> + </defs> + </svg> + ) : ( + <svg + aria-hidden + width="179" + height="98" + viewBox="0 0 179 98" + fill="none" + xmlns="http://www.w3.org/2000/svg" + {...imageProps} + > + <path + d="M35.076 27H80.745L64.703 97H16C7.716 97 1 90.284 1 82V42c0-8.284 6.716-15 15-15h19.076Z" + stroke="var(--echoes-color-border-weak)" + strokeWidth="2" + /> + <path + d="M56.55 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M90.32 63.612h5.233v1.632H90.32V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <path + d="M143.924 97H98.255l16.042-70H163c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15h-19.076Z" + stroke="var(--echoes-color-border-weak)" + strokeWidth="2" + /> + <g clipPath="url(#sqcireq)" fill="var(--echoes-logos-colors-brand)"> + <path d="M165.273 63.54a1.54 1.54 0 0 1-1.539-1.54c0-11.433-9.301-20.733-20.734-20.733a1.54 1.54 0 0 1 0-3.079c13.131 0 23.813 10.682 23.813 23.812 0 .85-.69 1.54-1.539 1.54h-.001ZM142.999 85.812c-13.13 0-23.811-10.681-23.811-23.812a1.54 1.54 0 1 1 3.078 0c0 11.433 9.3 20.733 20.733 20.733a1.54 1.54 0 0 1 0 3.079ZM149.682 63.54a1.54 1.54 0 0 1-1.54-1.54A5.147 5.147 0 0 0 143 56.859a1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.54 1.54ZM142.999 70.222c-4.533 0-8.221-3.688-8.221-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 0 1 0 3.079h.001Z" /> + <path d="M157.477 63.54c-.85 0-1.54-.69-1.54-1.54 0-7.133-5.804-12.937-12.938-12.937a1.54 1.54 0 0 1 0-3.078c8.832 0 16.017 7.185 16.017 16.016 0 .85-.69 1.54-1.539 1.54v-.002ZM142.999 78.017c-8.831 0-16.016-7.185-16.016-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 0 1 0 3.08h.001Z" /> + </g> + <defs> + <clipPath id="sqcireq"> + <path fill="#fff" transform="translate(119 38)" d="M0 0h48v48H0z" /> + </clipPath> + </defs> + </svg> + ); +} diff --git a/server/sonar-web/src/main/js/components/branding/SonarQubeIDEPromotionIllustration.tsx b/server/sonar-web/src/main/js/components/branding/SonarQubeIDEPromotionIllustration.tsx new file mode 100644 index 00000000000..3e4ea0bc9db --- /dev/null +++ b/server/sonar-web/src/main/js/components/branding/SonarQubeIDEPromotionIllustration.tsx @@ -0,0 +1,107 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { useAppState } from '../../app/components/app-state/withAppStateContext'; +import { EditionKey } from '../../types/editions'; + +interface Props { + className?: string; +} +/** + * This component switches between the Community and Server product versions' logo + */ +export function SonarQubeIDEPromotionIllustration({ className }: Props) { + const { edition } = useAppState(); + + return edition === EditionKey.community ? ( + <svg + aria-hidden + height="120" + viewBox="0 0 82 173" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className} + > + <path + d="M46.924 71H1.255L17.297 1H66c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15H46.924Z" + stroke="var(--echoes-color-border-weak)" + strokeWidth="2" + /> + <path + d="M68.448 36.552A1.55 1.55 0 0 1 66.897 35c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 46 11c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.552 1.552ZM46 59c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 25.103 35c0 11.524 9.373 20.897 20.897 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 46 59Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M60.586 36.552A1.55 1.55 0 0 1 59.035 35c0-7.19-5.845-13.035-13.035-13.035a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 46 18.862c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.552 1.552ZM46 51.138c-8.897 0-16.138-7.241-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 32.965 35c0 7.19 5.845 13.035 13.035 13.035a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 46 51.138ZM46.02 43.286a8.255 8.255 0 0 1-5.855-2.42c-3.227-3.228-3.227-8.483 0-11.721a8.233 8.233 0 0 1 5.856-2.431 8.19 8.19 0 0 1 5.855 2.43c.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.386 0-2.69.537-3.662 1.52a5.189 5.189 0 0 0 0 7.324 5.189 5.189 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.255 8.255 0 0 1-5.855 2.42v.011Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M41.82 88.112h5.233v1.632H41.82V95h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <path + d="M35.076 102H80.745l-16.042 70H16c-8.284 0-15-6.716-15-15v-40c0-8.284 6.716-15 15-15h19.076Z" + stroke="var(--echoes-color-border-weak)" + strokeWidth="2" + /> + <path + d="M56.55 139.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 158.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 123.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + </svg> + ) : ( + <svg + aria-hidden + height="120" + viewBox="0 0 82 173" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className} + > + <path + d="M46.924 71H1.255L17.297 1H66c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15H46.924Z" + stroke="var(--echoes-color-border-weak)" + strokeWidth="2" + /> + <g clipPath="url(#sqidepi)" fill="var(--echoes-logos-colors-brand)"> + <path d="M68.273 37.54c-.85 0-1.54-.69-1.54-1.54 0-11.433-9.3-20.733-20.733-20.733a1.54 1.54 0 0 1 0-3.079c13.13 0 23.813 10.682 23.813 23.812 0 .85-.69 1.54-1.539 1.54ZM46 59.812C32.87 59.812 22.186 49.13 22.186 36a1.54 1.54 0 0 1 3.08 0c0 11.433 9.3 20.733 20.732 20.733a1.54 1.54 0 0 1 0 3.079ZM52.682 37.54c-.85 0-1.54-.69-1.54-1.54A5.147 5.147 0 0 0 46 30.859a1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.54 1.54ZM46 44.222c-4.534 0-8.222-3.688-8.222-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 0 1 0 3.079H46Z" /> + <path d="M60.477 37.54c-.85 0-1.54-.69-1.54-1.54 0-7.133-5.804-12.937-12.938-12.937a1.54 1.54 0 0 1 0-3.078c8.832 0 16.017 7.185 16.017 16.016 0 .85-.69 1.54-1.54 1.54v-.002ZM46 52.017c-8.832 0-16.017-7.185-16.017-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 0 1 0 3.08H46Z" /> + </g> + <path + d="M41.82 88.112h5.233v1.632H41.82V95h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <path + d="M35.076 102H80.745l-16.042 70H16c-8.284 0-15-6.716-15-15v-40c0-8.284 6.716-15 15-15h19.076Z" + stroke="var(--echoes-color-border-weak)" + strokeWidth="2" + /> + <path + d="M56.55 139.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 158.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 123.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <defs> + <clipPath id="sqidepi"> + <path fill="#fff" transform="translate(22 12)" d="M0 0h48v48H0z" /> + </clipPath> + </defs> + </svg> + ); +} diff --git a/server/sonar-web/src/main/js/components/branding/SonarQubeProductLogo.tsx b/server/sonar-web/src/main/js/components/branding/SonarQubeProductLogo.tsx new file mode 100644 index 00000000000..a9db4c2aa96 --- /dev/null +++ b/server/sonar-web/src/main/js/components/branding/SonarQubeProductLogo.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { LogoSonarQubeCommunity, LogoSonarQubeServer } from '@sonarsource/echoes-react'; +import * as React from 'react'; +import { useAppState } from '../../app/components/app-state/withAppStateContext'; +import { EditionKey } from '../../types/editions'; + +type Props = React.ComponentProps<typeof LogoSonarQubeServer>; + +/** + * This component switches between the Community and Server product versions' logo + */ +export function SonarQubeProductLogo(props: Props) { + const { edition } = useAppState(); + + const OfficialLogo = + edition === EditionKey.community ? LogoSonarQubeCommunity : LogoSonarQubeServer; + + return <OfficialLogo {...props} />; +} diff --git a/server/sonar-web/src/main/js/components/branding/__tests__/SonarQubeConnectionIllustration-test.tsx b/server/sonar-web/src/main/js/components/branding/__tests__/SonarQubeConnectionIllustration-test.tsx new file mode 100644 index 00000000000..b95f33683f1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/branding/__tests__/SonarQubeConnectionIllustration-test.tsx @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { mockAppState } from '../../../helpers/testMocks'; +import { renderComponent } from '../../../helpers/testReactTestingUtils'; +import { EditionKey } from '../../../types/editions'; +import { SonarQubeConnectionIllustration } from '../SonarQubeConnectionIllustration'; + +it.each([ + [EditionKey.community, true], + [EditionKey.community, false], + [EditionKey.enterprise, true], + [EditionKey.enterprise, false], +])('should render %s edition (variant connected %s) correctly', (edition, connected) => { + const { container } = renderComponent( + <SonarQubeConnectionIllustration connected={connected} />, + '', + { appState: mockAppState({ edition }) }, + ); + + expect(container).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/branding/__tests__/__snapshots__/SonarQubeConnectionIllustration-test.tsx.snap b/server/sonar-web/src/main/js/components/branding/__tests__/__snapshots__/SonarQubeConnectionIllustration-test.tsx.snap new file mode 100644 index 00000000000..400a3b1350d --- /dev/null +++ b/server/sonar-web/src/main/js/components/branding/__tests__/__snapshots__/SonarQubeConnectionIllustration-test.tsx.snap @@ -0,0 +1,189 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render community edition (variant connected false) correctly 1`] = ` +<div> + <svg + aria-hidden="true" + fill="none" + height="98" + viewBox="0 0 179 98" + width="179" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M35.076 27H80.745L64.703 97H16C7.716 97 1 90.284 1 82V42c0-8.284 6.716-15 15-15h19.076Z" + stroke="var(--echoes-color-border-weak)" + stroke-width="2" + /> + <path + d="M56.55 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M90.32 63.612h5.233v1.632H90.32V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <path + d="M143.924 97H98.255l16.042-70H163c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15h-19.076Z" + stroke="var(--echoes-color-border-weak)" + stroke-width="2" + /> + <path + d="M165.448 62.552A1.55 1.55 0 0 1 163.897 61c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 143 37c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.552 1.552ZM143 85c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 122.103 61c0 11.524 9.373 20.897 20.897 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 143 85Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M157.586 62.552A1.55 1.55 0 0 1 156.034 61c0-7.19-5.844-13.035-13.034-13.035a1.55 1.55 0 0 1-1.552-1.551A1.55 1.55 0 0 1 143 44.862c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.552 1.552ZM143 77.138c-8.897 0-16.138-7.242-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 129.966 61c0 7.19 5.844 13.034 13.034 13.034a1.55 1.55 0 0 1 1.552 1.552A1.55 1.55 0 0 1 143 77.138ZM143.021 69.286a8.252 8.252 0 0 1-5.855-2.42c-3.228-3.228-3.228-8.483 0-11.721a8.232 8.232 0 0 1 5.855-2.431c2.213 0 4.293.858 5.855 2.43.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.387 0-2.69.537-3.662 1.52a5.187 5.187 0 0 0 0 7.324 5.188 5.188 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.256 8.256 0 0 1-5.855 2.42v.011Z" + fill="var(--echoes-logos-colors-brand)" + /> + </svg> +</div> +`; + +exports[`should render community edition (variant connected true) correctly 1`] = ` +<div> + <svg + aria-hidden="true" + fill="none" + height="98" + viewBox="0 0 179 98" + width="179" + xmlns="http://www.w3.org/2000/svg" + > + <path + clip-rule="evenodd" + d="M72.005 26h-50.01c-8.836 0-16 7.163-16 16v40c0 8.837 7.164 16 16 16h135c8.837 0 16-7.163 16-16V42c0-8.837-7.163-16-16-16h-57.4l-2 2h59.4c7.732 0 14 6.268 14 14v40c0 7.732-6.268 14-14 14h-135c-7.732 0-14-6.268-14-14V42c0-7.732 6.268-14 14-14h52.01l-2-2Z" + fill="var(--echoes-color-icon-success)" + fill-rule="evenodd" + /> + <path + d="M66.545 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM46.355 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM32.045 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M90.316 63.612h5.232v1.632h-5.232V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <path + d="M155.444 63.552A1.55 1.55 0 0 1 153.892 62c0-11.524-9.373-20.897-20.897-20.897a1.55 1.55 0 0 1-1.551-1.551A1.55 1.55 0 0 1 132.995 38c13.231 0 24 10.769 24 24a1.55 1.55 0 0 1-1.551 1.552ZM132.995 86c-13.231 0-24-10.769-24-24a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 112.099 62c0 11.524 9.372 20.897 20.896 20.897a1.55 1.55 0 0 1 1.552 1.551A1.55 1.55 0 0 1 132.995 86Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M147.582 63.552A1.55 1.55 0 0 1 146.03 62c0-7.19-5.845-13.035-13.035-13.035a1.55 1.55 0 0 1-1.551-1.551 1.55 1.55 0 0 1 1.551-1.552c8.897 0 16.138 7.241 16.138 16.138a1.55 1.55 0 0 1-1.551 1.552ZM132.995 78.138c-8.896 0-16.138-7.242-16.138-16.138a1.55 1.55 0 0 1 1.552-1.552A1.55 1.55 0 0 1 119.961 62c0 7.19 5.845 13.034 13.034 13.034a1.55 1.55 0 0 1 1.552 1.552 1.55 1.55 0 0 1-1.552 1.552ZM133.016 70.286a8.254 8.254 0 0 1-5.855-2.42c-3.228-3.228-3.228-8.483 0-11.721a8.232 8.232 0 0 1 5.855-2.431 8.19 8.19 0 0 1 5.855 2.43c.61.611.61 1.594 0 2.194-.61.61-1.593.61-2.193 0a5.133 5.133 0 0 0-3.662-1.52c-1.386 0-2.69.537-3.662 1.52a5.189 5.189 0 0 0 0 7.324 5.188 5.188 0 0 0 7.324 0c.61-.61 1.593-.61 2.193 0 .6.61.61 1.593 0 2.193a8.254 8.254 0 0 1-5.855 2.42v.011Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M110.658 4.837a2.861 2.861 0 0 1 0 4.044L87.804 31.736a2.861 2.861 0 0 1-4.044 0L72.332 20.308a2.861 2.861 0 0 1 0-4.044 2.861 2.861 0 0 1 4.044 0l9.41 9.401 20.837-20.828a2.86 2.86 0 0 1 4.044 0h-.009Z" + fill="var(--echoes-color-icon-success)" + /> + </svg> +</div> +`; + +exports[`should render enterprise edition (variant connected false) correctly 1`] = ` +<div> + <svg + aria-hidden="true" + fill="none" + height="98" + viewBox="0 0 179 98" + width="179" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M35.076 27H80.745L64.703 97H16C7.716 97 1 90.284 1 82V42c0-8.284 6.716-15 15-15h19.076Z" + stroke="var(--echoes-color-border-weak)" + stroke-width="2" + /> + <path + d="M56.55 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM36.36 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM22.05 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M90.32 63.612h5.233v1.632H90.32V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <path + d="M143.924 97H98.255l16.042-70H163c8.284 0 15 6.716 15 15v40c0 8.284-6.716 15-15 15h-19.076Z" + stroke="var(--echoes-color-border-weak)" + stroke-width="2" + /> + <g + clip-path="url(#sqcireq)" + fill="var(--echoes-logos-colors-brand)" + > + <path + d="M165.273 63.54a1.54 1.54 0 0 1-1.539-1.54c0-11.433-9.301-20.733-20.734-20.733a1.54 1.54 0 0 1 0-3.079c13.131 0 23.813 10.682 23.813 23.812 0 .85-.69 1.54-1.539 1.54h-.001ZM142.999 85.812c-13.13 0-23.811-10.681-23.811-23.812a1.54 1.54 0 1 1 3.078 0c0 11.433 9.3 20.733 20.733 20.733a1.54 1.54 0 0 1 0 3.079ZM149.682 63.54a1.54 1.54 0 0 1-1.54-1.54A5.147 5.147 0 0 0 143 56.859a1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.54 1.54ZM142.999 70.222c-4.533 0-8.221-3.688-8.221-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 0 1 0 3.079h.001Z" + /> + <path + d="M157.477 63.54c-.85 0-1.54-.69-1.54-1.54 0-7.133-5.804-12.937-12.938-12.937a1.54 1.54 0 0 1 0-3.078c8.832 0 16.017 7.185 16.017 16.016 0 .85-.69 1.54-1.539 1.54v-.002ZM142.999 78.017c-8.831 0-16.016-7.185-16.016-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 0 1 0 3.08h.001Z" + /> + </g> + <defs> + <clippath + id="sqcireq" + > + <path + d="M0 0h48v48H0z" + fill="#fff" + transform="translate(119 38)" + /> + </clippath> + </defs> + </svg> +</div> +`; + +exports[`should render enterprise edition (variant connected true) correctly 1`] = ` +<div> + <svg + aria-hidden="true" + fill="none" + height="98" + viewBox="0 0 179 98" + width="179" + xmlns="http://www.w3.org/2000/svg" + > + <path + clip-rule="evenodd" + d="M72.005 26h-50.01c-8.836 0-16 7.163-16 16v40c0 8.837 7.164 16 16 16h135c8.837 0 16-7.163 16-16V42c0-8.837-7.163-16-16-16h-57.4l-2 2h59.4c7.732 0 14 6.268 14 14v40c0 7.732-6.268 14-14 14h-135c-7.732 0-14-6.268-14-14V42c0-7.732 6.268-14 14-14h52.01l-2-2Z" + fill="var(--echoes-color-icon-success)" + fill-rule="evenodd" + /> + <path + d="M66.545 64.34c0 1.08-1.23 1.71-2.1 1.08-.96-.72-1.56-1.86-2.04-2.76-.66-1.26-1.14-2.01-1.74-2.01-.6 0-1.05.75-1.74 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.74-2.01-.63 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.66-1.26-1.11-2.01-1.71-2.01-.6 0-1.05.75-1.71 2.01-.78 1.47-1.86 3.48-4.14 3.48-2.28 0-3.36-2.01-4.14-3.48-.48-.87-.84-1.53-1.2-1.83-.3-.24-.51-.57-.51-.96v-.21c0-1.08 1.23-1.71 2.1-1.08.96.72 1.56 1.86 2.04 2.76.66 1.26 1.11 2.01 1.71 2.01.6 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.71-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.66 1.26 1.11 2.01 1.74 2.01.63 0 1.05-.75 1.74-2.01.78-1.47 1.86-3.48 4.14-3.48 2.28 0 3.36 2.01 4.14 3.48.48.87.84 1.53 1.23 1.83.3.24.51.57.51.96v.21h-.03ZM46.355 83.18c-4.95 0-9.66-1.71-13.47-4.83-.6-.51-.66-1.44-.12-2.01.51-.54 1.32-.57 1.86-.12 3.42 2.85 7.71 4.32 12.21 4.2 4.5-.12 8.7-1.83 11.97-4.83.54-.48 1.35-.48 1.86 0 .57.54.57 1.47 0 2.01-3.75 3.48-8.61 5.43-13.77 5.58h-.57.03ZM32.045 48.41c-.57-.54-.57-1.47 0-2.01 3.87-3.57 8.79-5.43 13.77-5.55 4.98-.12 9.96 1.47 14.04 4.83.6.51.66 1.44.12 2.01-.51.51-1.32.57-1.86.12-3.51-2.88-7.8-4.29-12.09-4.2-4.29.09-8.67 1.71-12.09 4.83-.54.48-1.35.48-1.86 0l-.03-.03Z" + fill="var(--echoes-logos-colors-brand)" + /> + <path + d="M90.316 63.612h5.232v1.632h-5.232V70.5h-1.632v-5.256h-5.232v-1.632h5.232v-5.256h1.632v5.256Z" + fill="var(--echoes-color-text-subdued)" + /> + <g + clip-path="url(#sqciok)" + fill="var(--echoes-logos-colors-brand)" + > + <path + d="M155.268 63.54a1.54 1.54 0 0 1-1.539-1.54c0-11.433-9.301-20.733-20.734-20.733a1.54 1.54 0 0 1 0-3.079c13.131 0 23.814 10.682 23.814 23.812 0 .85-.69 1.54-1.54 1.54h-.001ZM132.994 85.812c-13.129 0-23.811-10.681-23.811-23.812a1.54 1.54 0 0 1 3.079 0c0 11.433 9.3 20.733 20.732 20.733a1.54 1.54 0 1 1 0 3.079ZM139.677 63.54c-.85 0-1.54-.69-1.54-1.54a5.147 5.147 0 0 0-5.142-5.141 1.54 1.54 0 0 1 0-3.079c4.534 0 8.222 3.688 8.222 8.22 0 .851-.69 1.54-1.539 1.54h-.001ZM132.994 70.222c-4.532 0-8.221-3.688-8.221-8.222a1.54 1.54 0 0 1 3.079 0 5.147 5.147 0 0 0 5.141 5.142 1.54 1.54 0 1 1 0 3.079h.001Z" + /> + <path + d="M147.472 63.54a1.54 1.54 0 0 1-1.539-1.54c0-7.133-5.804-12.937-12.939-12.937a1.54 1.54 0 0 1 0-3.078c8.833 0 16.018 7.185 16.018 16.016 0 .85-.69 1.54-1.54 1.54v-.002ZM132.994 78.017c-8.831 0-16.016-7.185-16.016-16.017a1.54 1.54 0 0 1 3.079 0c0 7.134 5.804 12.938 12.936 12.938a1.54 1.54 0 1 1 0 3.08h.001Z" + /> + </g> + <path + d="M110.658 4.837a2.861 2.861 0 0 1 0 4.044L87.804 31.736a2.861 2.861 0 0 1-4.044 0L72.332 20.308a2.861 2.861 0 0 1 0-4.044 2.861 2.861 0 0 1 4.044 0l9.41 9.401 20.837-20.828a2.86 2.86 0 0 1 4.044 0h-.009Z" + fill="var(--echoes-color-icon-success)" + /> + <defs> + <clippath + id="sqciok" + > + <path + d="M0 0h48v48H0z" + fill="#fff" + transform="translate(108.995 38)" + /> + </clippath> + </defs> + </svg> +</div> +`; diff --git a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx index 5bffded6ae8..9824cec5a8f 100644 --- a/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx +++ b/server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx @@ -20,41 +20,12 @@ import { DropdownMenu } from '@sonarsource/echoes-react'; import * as React from 'react'; -import { Image } from '~sonar-aligned/components/common/Image'; import { DocLink } from '../../helpers/doc-links'; import { translate } from '../../helpers/l10n'; import { SuggestionLink } from '../../types/types'; import { DocItemLink } from './DocItemLink'; import { SuggestionsContext } from './SuggestionsContext'; -function IconLink({ - icon = 'embed-doc/sq-icon.svg', - link, - text, -}: { - icon?: string; - link: string; - text: string; -}) { - return ( - <DropdownMenu.ItemLink - prefix={ - <Image - alt={text} - aria-hidden - className="sw-mr-2" - height="18" - src={`/images/${icon}`} - width="18" - /> - } - to={link} - > - {text} - </DropdownMenu.ItemLink> - ); -} - function Suggestions({ suggestions }: Readonly<{ suggestions: SuggestionLink[] }>) { return ( <> @@ -103,21 +74,15 @@ export function EmbedDocsPopup() { <DropdownMenu.GroupLabel>{translate('docs.stay_connected')}</DropdownMenu.GroupLabel> - <IconLink - link="https://www.sonarsource.com/products/sonarqube/whats-new/?referrer=sonarqube" - text={translate('docs.news')} - /> - - <IconLink - link="https://www.sonarsource.com/products/sonarqube/roadmap/?referrer=sonarqube" - text={translate('docs.roadmap')} - /> - - <IconLink - icon="embed-doc/x-icon-black.svg" - link="https://twitter.com/SonarQube" - text="X @SonarQube" - /> + <DropdownMenu.ItemLink to="https://www.sonarsource.com/products/sonarqube/whats-new/?referrer=sonarqube"> + {translate('docs.news')} + </DropdownMenu.ItemLink> + + <DropdownMenu.ItemLink to="https://www.sonarsource.com/products/sonarqube/roadmap/?referrer=sonarqube"> + {translate('docs.roadmap')} + </DropdownMenu.ItemLink> + + <DropdownMenu.ItemLink to="https://twitter.com/SonarQube">X @SonarQube</DropdownMenu.ItemLink> </> ); } diff --git a/server/sonar-web/src/main/js/components/intl/TranslatedMessage.tsx b/server/sonar-web/src/main/js/components/intl/TranslatedMessage.tsx new file mode 100644 index 00000000000..497103386cd --- /dev/null +++ b/server/sonar-web/src/main/js/components/intl/TranslatedMessage.tsx @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import { ComponentProps } from 'react'; +import { FormattedMessage } from 'react-intl'; + +type Props = ComponentProps<typeof FormattedMessage>; + +export function TranslatedMessage(props: Props) { + return <FormattedMessage {...props} />; +} diff --git a/server/sonar-web/src/main/js/components/shared/AppVersionStatus.tsx b/server/sonar-web/src/main/js/components/shared/AppVersionStatus.tsx index 8b9452fe2ff..bd481c9c061 100644 --- a/server/sonar-web/src/main/js/components/shared/AppVersionStatus.tsx +++ b/server/sonar-web/src/main/js/components/shared/AppVersionStatus.tsx @@ -27,10 +27,11 @@ import { useDocUrl } from '../../helpers/docs'; import { getInstanceVersionNumber } from '../../helpers/strings'; import { isCurrentVersionEOLActive } from '../../helpers/system'; import { useSystemUpgrades } from '../../queries/system'; +import { EditionKey } from '../../types/editions'; export default function AppVersionStatus() { const { data } = useSystemUpgrades(); - const { version, versionEOL } = useAppState(); + const { edition, version, versionEOL } = useAppState(); const isActiveVersion = useMemo(() => { if (data?.installedVersionActive !== undefined) { @@ -47,7 +48,7 @@ export default function AppVersionStatus() { { id: `footer.version` }, { version: getInstanceVersionNumber(version), - status: ( + status: edition && edition !== EditionKey.community && ( <LinkStandalone className="sw-ml-1" highlight={LinkHighlight.CurrentColor} to={docUrl}> <FormattedMessage id={`footer.version.status.${isActiveVersion ? 'active' : 'inactive'}`} diff --git a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/RepositoryVariables.tsx b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/RepositoryVariables.tsx index 4a08ed78697..75e3731f4cf 100644 --- a/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/RepositoryVariables.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/RepositoryVariables.tsx @@ -59,7 +59,7 @@ export default function RepositoryVariables(props: RepositoryVariablesProps) { almBinding, projectBinding, )}/admin/addon/admin/pipelines/repository-variables`} - target="_blank" + shouldOpenInNewTab > {translate('onboarding.tutorial.with.bitbucket_pipelines.variables.intro.link')} </LinkStandalone> diff --git a/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx b/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx index eaf7bdf367d..a4606e48efd 100644 --- a/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx @@ -65,7 +65,7 @@ export default function GithubCFamilyExampleRepositories( height={20} src="/images/alm/github.svg" /> - <LinkStandalone target="_blank" to={link}> + <LinkStandalone shouldOpenInNewTab to={link}> sonarsource-cfamily-examples </LinkStandalone> </div> diff --git a/server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx b/server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx index acb6dfef1db..38f0f198ee8 100644 --- a/server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx +++ b/server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx @@ -59,7 +59,7 @@ export default function SecretStep(props: SecretStepProps) { almBinding && projectBinding ? ( <LinkStandalone to={`${buildGithubLink(almBinding, projectBinding)}/settings/secrets`} - target="_blank" + shouldOpenInNewTab > {translate('onboarding.tutorial.with.github_action.secret.intro.link')} </LinkStandalone> diff --git a/server/sonar-web/src/main/js/components/tutorials/test-utils.ts b/server/sonar-web/src/main/js/components/tutorials/test-utils.ts index aee023dcbeb..f46255e766c 100644 --- a/server/sonar-web/src/main/js/components/tutorials/test-utils.ts +++ b/server/sonar-web/src/main/js/components/tutorials/test-utils.ts @@ -71,9 +71,11 @@ export function getCommonNodes(ci: TutorialModes) { expiresInSelect: byRole('combobox', { name: '' }), tokenValue: byText('generatedtoken2'), linkToRepo: byRole('link', { - name: `onboarding.tutorial.with.${CI_TRANSLATE_MAP[ci]}.${ - ci === TutorialModes.GitHubActions ? 'secret' : 'variables' - }.intro.link`, + name: new RegExp( + `onboarding.tutorial.with.${CI_TRANSLATE_MAP[ci]}.${ + ci === TutorialModes.GitHubActions ? 'secret' : 'variables' + }.intro.link`, + ), }), allSetSentence: byText('onboarding.tutorial.ci_outro.done'), }; diff --git a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx index f6ee51f0630..d3f89677405 100644 --- a/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx +++ b/server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx @@ -50,7 +50,7 @@ export default function SystemUpgradeButton(props: Readonly<Props>) { <Link className="sw-ml-2" to="https://www.sonarsource.com/products/sonarqube/downloads/?referrer=sonarqube" - target="_blank" + shouldOpenInNewTab > {translate('learn_more')} </Link> diff --git a/server/sonar-web/src/main/js/helpers/__tests__/l10n-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/l10n-test.ts index 39d66bb80f8..e1d3df12fce 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/l10n-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/l10n-test.ts @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { IntlShape } from 'react-intl'; import { Dict } from '../../types/types'; import { getLocalizedCategoryMetricName, @@ -30,18 +31,30 @@ import { translate, translateWithParameters, } from '../l10n'; -import { getMessages } from '../l10nBundle'; +import { getIntl, getMessages } from '../l10nBundle'; const MSG = 'my_message'; jest.unmock('../l10n'); -jest.mock('../l10nBundle', () => ({ - getMessages: jest.fn().mockReturnValue({}), -})); +jest.mock('../l10nBundle', () => { + const bundle = jest.requireActual('../l10nBundle'); + return { + ...bundle, + getIntl: jest.fn().mockReturnValue({ formatMessage: jest.fn(({ id }) => `${id}`) }), + getMessages: jest.fn().mockReturnValue({}), + }; +}); + +const resetMessages = (messages: Dict<string>) => { + jest.mocked(getMessages).mockReturnValue(messages); -const resetMessages = (messages: Dict<string>) => - (getMessages as jest.Mock).mockReturnValue(messages); + jest.mocked(getIntl).mockReturnValue({ + formatMessage: jest.fn(({ id }) => { + return id ? (messages[id] ?? id) : `${id}`; + }), + } as unknown as IntlShape); +}; beforeEach(() => { resetMessages({}); @@ -82,6 +95,13 @@ describe('translate', () => { expect(translate('random', 'key')).toBe('random.key'); expect(translate('composite.random', 'key')).toBe('composite.random.key'); }); + + it('should fall back to the old system when intl is undefined', () => { + jest.mocked(getIntl).mockReturnValueOnce(undefined as unknown as IntlShape); + resetMessages({ exists: 'this exists' }); + + expect(translate('exists')).toBe('this exists'); + }); }); describe('translateWithParameters', () => { diff --git a/server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts index 5ac785375b5..930f244e8a9 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts @@ -20,6 +20,7 @@ import { fetchL10nBundle } from '../../api/l10n'; import { loadL10nBundle } from '../l10nBundle'; +import { mockAppState } from '../testMocks'; beforeEach(() => { jest.clearAllMocks(); @@ -33,9 +34,11 @@ jest.mock('../../api/l10n', () => ({ }), })); +const APP_STATE = mockAppState({}); + describe('#loadL10nBundle', () => { it('should fetch bundle without any timestamp', async () => { - await loadL10nBundle(); + await loadL10nBundle(APP_STATE); expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: undefined }); }); @@ -44,7 +47,7 @@ describe('#loadL10nBundle', () => { const cachedBundle = { timestamp: 'timestamp', locale: 'fr', messages: { cache: 'cache' } }; (window as unknown as any).sonarQubeL10nBundle = cachedBundle; - await loadL10nBundle(); + await loadL10nBundle(APP_STATE); expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: undefined }); }); @@ -53,7 +56,7 @@ describe('#loadL10nBundle', () => { const cachedBundle = { timestamp: 'timestamp', locale: 'de', messages: { cache: 'cache' } }; (window as unknown as any).sonarQubeL10nBundle = cachedBundle; - await loadL10nBundle(); + await loadL10nBundle(APP_STATE); expect(fetchL10nBundle).toHaveBeenCalledWith({ locale: 'de', ts: cachedBundle.timestamp }); }); @@ -63,7 +66,7 @@ describe('#loadL10nBundle', () => { (fetchL10nBundle as jest.Mock).mockRejectedValueOnce({ status: 304 }); (window as unknown as any).sonarQubeL10nBundle = cachedBundle; - const bundle = await loadL10nBundle(); + const bundle = await loadL10nBundle(APP_STATE); expect(bundle).toEqual( expect.objectContaining({ locale: cachedBundle.locale, messages: cachedBundle.messages }), diff --git a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts index 09527fbb946..4c8516d50fb 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts @@ -28,6 +28,14 @@ import { import { mockQualityGateStatusCondition } from '../mocks/quality-gates'; import { mockMeasure, mockMeasureEnhanced, mockMetric } from '../testMocks'; +jest.mock('../l10nBundle', () => { + const bundle = jest.requireActual('../l10nBundle'); + return { + ...bundle, + getIntl: () => ({ formatMessage: jest.fn(({ id }) => `${id}`) }), + }; +}); + describe('enhanceConditionWithMeasure', () => { it('should correctly map enhance conditions with measure data', () => { const measures = [ diff --git a/server/sonar-web/src/main/js/helpers/l10n.ts b/server/sonar-web/src/main/js/helpers/l10n.ts index 920a5a6c577..4594e4ae636 100644 --- a/server/sonar-web/src/main/js/helpers/l10n.ts +++ b/server/sonar-web/src/main/js/helpers/l10n.ts @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getMessages } from './l10nBundle'; +import { getIntl, getMessages } from './l10nBundle'; export function hasMessage(...keys: string[]): boolean { const messageKey = keys.join('.'); @@ -35,9 +35,14 @@ export function translate(...keys: string[]): string { console.error(`No message for: ${messageKey}`); } - return l10nMessages[messageKey] || messageKey; + const intl = getIntl(); + // fallback to old if in extension + return intl ? intl.formatMessage({ id: messageKey }) : l10nMessages[messageKey]; } +/** + * @param messageKey @deprecated Use react-intl instead + */ export function translateWithParameters( messageKey: string, ...parameters: Array<string | number> diff --git a/server/sonar-web/src/main/js/helpers/l10nBundle.ts b/server/sonar-web/src/main/js/helpers/l10nBundle.ts index 4fc589c47f8..d34b1f479bb 100644 --- a/server/sonar-web/src/main/js/helpers/l10nBundle.ts +++ b/server/sonar-web/src/main/js/helpers/l10nBundle.ts @@ -20,9 +20,12 @@ import { IntlShape, createIntl, createIntlCache } from 'react-intl'; import { fetchL10nBundle } from '../api/l10n'; +import { AppState } from '../types/appstate'; +import { EditionKey } from '../types/editions'; import { L10nBundle, L10nBundleRequestParams } from '../types/l10nBundle'; import { Dict } from '../types/types'; import { toISO8601WithOffsetString } from './dates'; +import { isDefined } from './types'; const DEFAULT_LOCALE = 'en'; const DEFAULT_MESSAGES: Dict<string> = { @@ -48,7 +51,7 @@ export function getCurrentL10nBundle() { return getL10nBundleFromCache(); } -export async function loadL10nBundle() { +export async function loadL10nBundle(appState: AppState | undefined) { const browserLocale = getPreferredLanguage(); const cachedBundle = getL10nBundleFromCache(); @@ -91,6 +94,17 @@ export async function loadL10nBundle() { { locale: effectiveLocale, messages, + + /* + * This sets a default value for translations, so devs do not need to pass the {productName} + * value to every instance of FormattedMessage. + * It is a bit of a hack, abusing this config item that is normally for tag replacement only, + * hence the ts-expect-error tag + */ + defaultRichTextElements: { + // @ts-expect-error + productName: getProductName(appState), + }, }, cache, ); @@ -109,3 +123,13 @@ function getL10nBundleFromCache(): L10nBundle { function persistL10nBundleInCache(bundle: L10nBundle) { (window as unknown as any).sonarQubeL10nBundle = bundle; } + +function getProductName(appState?: AppState) { + if (isDefined(appState?.edition)) { + return appState?.edition === EditionKey.community + ? 'SonarQube Community Build' + : 'SonarQube Server'; + } + + return 'SonarQube'; +} diff --git a/server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts b/server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts index 223898cb1d1..f5ca0dfd212 100644 --- a/server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts +++ b/server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts @@ -18,8 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { IntlShape } from 'react-intl'; import { MetricType } from '~sonar-aligned/types/metrics'; -import { getMessages } from '../../../helpers/l10nBundle'; +import { getIntl, getMessages } from '../../../helpers/l10nBundle'; import { Dict } from '../../../types/types'; import { formatMeasure } from '../measures'; @@ -33,11 +34,19 @@ jest.unmock('../../../helpers/l10n'); jest.mock('../../../helpers/l10nBundle', () => ({ getCurrentLocale: jest.fn().mockReturnValue('us'), getMessages: jest.fn().mockReturnValue({}), + getIntl: jest.fn().mockReturnValue({ formatMessage: jest.fn(({ id }) => `${id}`) }), })); -const resetMessages = (messages: Dict<string>) => +const resetMessages = (messages: Dict<string>) => { jest.mocked(getMessages).mockReturnValue(messages); + jest.mocked(getIntl).mockReturnValue({ + formatMessage: jest.fn(({ id }) => { + return id ? (messages[id] ?? id) : `${id}`; + }), + } as unknown as IntlShape); +}; + beforeAll(() => { resetMessages({ 'work_duration.x_days': '{0}d', |