aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2024-11-05 18:07:01 +0100
committersonartech <sonartech@sonarsource.com>2024-11-13 20:05:48 +0000
commit29168ab9565bcc29a752a58efb3c09848f7b2b03 (patch)
tree9c1435e35e724a829fb5a5856ba4049d5e3d1887 /server/sonar-web/src/main/js
parentb2a3c8a65a92fdac25042a05cd37c43fa18c67c3 (diff)
downloadsonarqube-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')
-rw-r--r--server/sonar-web/src/main/js/app/components/AdminContainer.tsx10
-rw-r--r--server/sonar-web/src/main/js/app/components/ComponentContainer.tsx10
-rw-r--r--server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx23
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/AdminContainer-test.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/GlobalFooter-test.tsx10
-rw-r--r--server/sonar-web/src/main/js/app/components/indexation/IndexationNotificationRenderer.tsx1
-rw-r--r--server/sonar-web/src/main/js/app/components/indexation/PageUnavailableDueToIndexation.tsx8
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx13
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMore.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx6
-rw-r--r--server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx12
-rw-r--r--server/sonar-web/src/main/js/app/index.ts13
-rw-r--r--server/sonar-web/src/main/js/apps/account/Account.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/audit-logs/components/__tests__/AuditApp-it.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRuleDetails-it.ts11
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts8
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/__tests__/CustomRule-it.ts8
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/MonorepoProjectCreate-it.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueDetails.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/TotalEffort.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/BranchListRow.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/NewCodeDefinitionSettingReferenceBranch.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/projectNewCode/components/__tests__/ProjectNewCodeDefinitionApp-it.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/project-card/ProjectCard.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/components/ProfileContainer.tsx11
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Login.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts8
-rw-r--r--server/sonar-web/src/main/js/apps/system/components/__tests__/SystemApp-it.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/branding/SonarQubeConnectionIllustration.tsx199
-rw-r--r--server/sonar-web/src/main/js/components/branding/SonarQubeIDEPromotionIllustration.tsx107
-rw-r--r--server/sonar-web/src/main/js/components/branding/SonarQubeProductLogo.tsx38
-rw-r--r--server/sonar-web/src/main/js/components/branding/__tests__/SonarQubeConnectionIllustration-test.tsx39
-rw-r--r--server/sonar-web/src/main/js/components/branding/__tests__/__snapshots__/SonarQubeConnectionIllustration-test.tsx.snap189
-rw-r--r--server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx53
-rw-r--r--server/sonar-web/src/main/js/components/intl/TranslatedMessage.tsx28
-rw-r--r--server/sonar-web/src/main/js/components/shared/AppVersionStatus.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/bitbucket-pipelines/RepositoryVariables.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/github-action/SecretStep.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/test-utils.ts8
-rw-r--r--server/sonar-web/src/main/js/components/upgrade/SystemUpgradeButton.tsx2
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/l10n-test.ts32
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/l10nBundle-test.ts11
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts8
-rw-r--r--server/sonar-web/src/main/js/helpers/l10n.ts9
-rw-r--r--server/sonar-web/src/main/js/helpers/l10nBundle.ts26
-rw-r--r--server/sonar-web/src/main/js/sonar-aligned/helpers/__tests__/measures-test.ts13
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',