aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>2024-03-15 18:24:42 +0100
committersonartech <sonartech@sonarsource.com>2024-03-18 20:02:30 +0000
commit03600808205c4f6df77713dbd40ee0a6e9c25a48 (patch)
tree8c90b9014c31a1715c5b59e8ef4eab1b53a422e7
parent8c71dbf2097fc53ae83a2d54be5099f5cfbd76f4 (diff)
downloadsonarqube-03600808205c4f6df77713dbd40ee0a6e9c25a48.tar.gz
sonarqube-03600808205c4f6df77713dbd40ee0a6e9c25a48.zip
SONAR-21867 Create Image component with baseUrl
-rw-r--r--server/sonar-web/.eslintrc11
-rw-r--r--server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx26
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx6
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/MainSonarQubeBar.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/promotion-notification/PromotionNotification.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx27
-rw-r--r--server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/sessions/components/Login.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx11
-rw-r--r--server/sonar-web/src/main/js/components/common/Image.tsx42
-rw-r--r--server/sonar-web/src/main/js/components/embed-docs-modal/EmbedDocsPopup.tsx7
-rw-r--r--server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx12
-rw-r--r--server/sonar-web/src/main/js/components/permissions/UserHolder.tsx10
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx48
-rw-r--r--server/sonar-web/src/main/js/components/tutorials/components/GithubCFamilyExampleRepositories.tsx14
24 files changed, 221 insertions, 166 deletions
diff --git a/server/sonar-web/.eslintrc b/server/sonar-web/.eslintrc
index 3634bec594b..e249b4c013b 100644
--- a/server/sonar-web/.eslintrc
+++ b/server/sonar-web/.eslintrc
@@ -9,6 +9,17 @@
"rules": {
"camelcase": "off",
"promise/no-return-wrap": "warn",
+ "react/forbid-elements": [
+ "warn",
+ {
+ "forbid": [
+ {
+ "element": "img",
+ "message": "use <Image> from components/common instead"
+ }
+ ]
+ }
+ ],
"react/jsx-curly-brace-presence": "warn",
"testing-library/render-result-naming-convention": "off",
/* Local rules, defined in ./eslint-local-rules/ */
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 4f8a19c4d40..51dd597b733 100644
--- a/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx
+++ b/server/sonar-web/src/main/js/app/components/SonarLintConnection.tsx
@@ -17,6 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { LinkStandalone } from '@sonarsource/echoes-react';
import {
ButtonPrimary,
Card,
@@ -24,7 +26,6 @@ import {
CheckIcon,
ClipboardButton,
InputField,
- Link,
ListItem,
Note,
OrderedList,
@@ -33,6 +34,7 @@ import {
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { useSearchParams } from 'react-router-dom';
+import { Image } from '../../components/common/Image';
import { whenLoggedIn } from '../../components/hoc/whenLoggedIn';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { generateSonarLintUserToken, portIsValid, sendUserToken } from '../../helpers/sonarlint';
@@ -88,10 +90,9 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
{status === Status.request && (
<>
<Title>{translate('sonarlint-connection.request.title')}</Title>
- <img
- alt=""
+ <Image
+ alt="sonarlint-connection-request"
className="sw-my-4"
- role="presentation"
src="/images/SonarLint-connection-request.png"
/>
<p className="sw-my-4">
@@ -107,7 +108,7 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
{status === Status.tokenError && (
<>
- <img alt="" className="sw-my-4 sw-pt-2" role="presentation" src="/images/cross.svg" />
+ <Image alt="sonarlint-token-error" 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">
@@ -116,9 +117,9 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
defaultMessage={translate('sonarlint-connection.token-error.description2')}
values={{
link: (
- <Link to="/account/security">
+ <LinkStandalone to="/account/security">
{translate('sonarlint-connection.token-error.description2.link')}
- </Link>
+ </LinkStandalone>
),
}}
/>
@@ -128,7 +129,11 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
{status === Status.tokenCreated && newToken && (
<>
- <img alt="" className="sw-my-4 sw-pt-2" role="presentation" src="/images/check.svg" />
+ <Image
+ alt="sonarlint-connection-error"
+ 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')}
@@ -160,10 +165,9 @@ export function SonarLintConnection({ currentUser }: Readonly<Props>) {
{status === Status.tokenSent && newToken && (
<>
<Title>{translate('sonarlint-connection.success.title')}</Title>
- <img
- alt=""
+ <Image
+ alt="sonarlint-connection-success"
className="sw-mb-4"
- role="presentation"
src="/images/SonarLint-connection-ok.png"
/>
<p className="sw-my-4">
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx
index 4e8bbb63971..c94295f147b 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx
@@ -20,9 +20,9 @@
import { LinkStandalone } from '@sonarsource/echoes-react';
import React from 'react';
+import { Image } from '../../../../../components/common/Image';
import { isPullRequest } from '../../../../../helpers/branch-like';
import { translate, translateWithParameters } from '../../../../../helpers/l10n';
-import { getBaseUrl } from '../../../../../helpers/system';
import { isDefined } from '../../../../../helpers/types';
import { AlmKeys } from '../../../../../types/alm-settings';
import { BranchLike } from '../../../../../types/branch-like';
@@ -70,10 +70,10 @@ export default function PRLink({
<LinkStandalone
iconLeft={
almKey !== '' && (
- <img
+ <Image
alt={almKey}
height={16}
- src={`${getBaseUrl()}/images/alm/${almKey}.svg`}
+ src={`/images/alm/${almKey}.svg`}
title={translateWithParameters('branches.see_the_pr_on_x', translate(almKey))}
/>
)
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 6c14bb0f749..05e799562df 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
@@ -17,8 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { MainAppBar, SonarQubeLogo } from 'design-system';
import * as React from 'react';
+import { Image } from '../../../../components/common/Image';
import { translate } from '../../../../helpers/l10n';
import { GlobalSettingKeys } from '../../../../types/settings';
import { AppStateContext } from '../../app-state/AppStateContext';
@@ -35,7 +37,7 @@ function LogoWithAriaText() {
return (
<div aria-label={title} role="img">
{customLogoUrl ? (
- <img alt={title} src={customLogoUrl} width={customLogoWidth} />
+ <Image alt={title} src={customLogoUrl} width={customLogoWidth} />
) : (
<SonarQubeLogo />
)}
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 b47b0ebf588..0256393b450 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
@@ -17,12 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import styled from '@emotion/styled';
import { ButtonPrimary, ButtonSecondary, themeBorder, themeColor } from 'design-system';
import * as React from 'react';
import { dismissNotice } from '../../../api/users';
+import { Image } from '../../../components/common/Image';
import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
import { NoticeType, isLoggedIn } from '../../../types/users';
import { CurrentUserContextInterface } from '../current-user/CurrentUserContext';
import withCurrentUserContext from '../current-user/withCurrentUserContext';
@@ -47,7 +48,7 @@ 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">
- <img alt="SonarQube + SonarLint" height={80} src={`${getBaseUrl()}/images/sq-sl.svg`} />
+ <Image alt="SonarQube + SonarLint" height={80} src="/images/sq-sl.svg" />
</div>
<PromotionNotificationContent className="sw-flex-1 sw-px-2 sw-py-4">
<span className="sw-body-sm-highlight">{translate('promotion.sonarlint.title')}</span>
diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx
index 7c317553268..058bad865d5 100644
--- a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx
+++ b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx
@@ -17,11 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { getTextColor } from 'design-system';
import * as React from 'react';
import { getIdentityProviders } from '../../../api/users';
import { colors } from '../../../app/theme';
-import { getBaseUrl } from '../../../helpers/system';
+import { Image } from '../../../components/common/Image';
import { IdentityProvider } from '../../../types/types';
import { LoggedInUser } from '../../../types/users';
@@ -104,11 +105,11 @@ export default class UserExternalIdentity extends React.PureComponent<
color: getTextColor(identityProvider.backgroundColor, colors.secondFontColor),
}}
>
- <img
+ <Image
alt={identityProvider.name}
className="sw-mr-1"
height="14"
- src={getBaseUrl() + identityProvider.iconPath}
+ src={identityProvider.iconPath}
width="14"
/>
{user.externalIdentity}
diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx
index 07e97291e93..5c91aa611a7 100644
--- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectModeSelection.tsx
@@ -17,22 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
/* eslint-disable react/no-unused-prop-types */
+
+import { LinkStandalone, Spinner } from '@sonarsource/echoes-react';
import {
ButtonSecondary,
GreyCard,
HelperHintIcon,
LightPrimary,
- Spinner,
- StandoutLink,
TextMuted,
Title,
} from 'design-system';
import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import { Image } from '../../../components/common/Image';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
import { getCreateProjectModeLocation } from '../../../helpers/urls';
import { AlmKeys } from '../../../types/alm-settings';
import { AppState } from '../../../types/appstate';
@@ -79,12 +80,10 @@ function renderAlmOption(
const svgFileNameGrey = `${svgFileName}_grey`;
const icon = (
- <img
+ <Image
alt="" // Should be ignored by screen readers
className="sw-h-4 sw-w-4"
- src={`${getBaseUrl()}/images/alm/${
- !disabled && hasConfig ? svgFileName : svgFileNameGrey
- }.svg`}
+ src={`/images/alm/${!disabled && hasConfig ? svgFileName : svgFileNameGrey}.svg`}
/>
);
@@ -92,9 +91,11 @@ function renderAlmOption(
<GreyCard key={alm} className="sw-col-span-4 sw-p-4 sw-flex sw-justify-between sw-items-center">
<div className="sw-items-center sw-flex sw-py-2">
{!disabled && hasConfig ? (
- <StandoutLink icon={icon} to={getCreateProjectModeLocation(mode)}>
- {translate('onboarding.create_project.import_select_method', alm)}
- </StandoutLink>
+ <LinkStandalone iconLeft={icon} to={getCreateProjectModeLocation(mode)}>
+ <span className="sw-ml-2">
+ {translate('onboarding.create_project.import_select_method', alm)}
+ </span>
+ </LinkStandalone>
) : (
<>
{icon}
@@ -106,7 +107,7 @@ function renderAlmOption(
)}
</div>
- <Spinner loading={loadingBindings}>
+ <Spinner isLoading={loadingBindings}>
{!hasConfig &&
(canAdmin ? (
<ButtonSecondary onClick={() => props.onConfigMode(configMode)}>
@@ -167,9 +168,9 @@ export function CreateProjectModeSelection(props: CreateProjectModeSelectionProp
<div className="sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12">
<GreyCard className="sw-col-span-4 sw-p-4 sw-py-6 sw-flex sw-justify-between sw-items-center">
<div>
- <StandoutLink to={getCreateProjectModeLocation(CreateProjectModes.Manual)}>
+ <LinkStandalone to={getCreateProjectModeLocation(CreateProjectModes.Manual)}>
{translate('onboarding.create_project.import_select_method.manual')}
- </StandoutLink>
+ </LinkStandalone>
</div>
</GreyCard>
</div>
diff --git a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx
index a9c4957b5ea..9ddd4b52803 100644
--- a/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx
+++ b/server/sonar-web/src/main/js/apps/groups/components/ListItem.tsx
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import {
ActionsDropdown,
Badge,
@@ -33,8 +34,8 @@ import {
} from 'design-system';
import * as React from 'react';
import { useState } from 'react';
+import { Image } from '../../../components/common/Image';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
import { useGroupMembersCountQuery } from '../../../queries/group-memberships';
import { Group, Provider } from '../../../types/types';
import DeleteGroupForm from './DeleteGroupForm';
@@ -69,11 +70,11 @@ export default function ListItem(props: Readonly<ListItemProps>) {
}
return (
- <img
+ <Image
alt={identityProvider}
className="sw-ml-2 sw-mr-2"
height={16}
- src={`${getBaseUrl()}/images/alm/${identityProvider}.svg`}
+ src={`/images/alm/${identityProvider}.svg`}
/>
);
};
diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
index f845f0fd429..a507e9e1241 100644
--- a/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
+++ b/server/sonar-web/src/main/js/apps/marketplace/components/EditionBox.tsx
@@ -17,9 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { SubHeading, UnorderedList } from 'design-system';
import * as React from 'react';
-import { getBaseUrl } from '../../../helpers/system';
+import { Image } from '../../../components/common/Image';
import { Edition, EditionKey } from '../../../types/editions';
interface Props {
@@ -32,11 +33,11 @@ export default function EditionBox({ edition }: Readonly<Props>) {
return (
<div>
<SubHeading as="h2" id="data-center-edition">
- <img
+ <Image
alt="SonarQube logo"
className="sw-mr-2"
width={16}
- src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`}
+ src="/images/embed-doc/sq-icon.svg"
/>
<span>Data Center Edition</span>
</SubHeading>
@@ -56,11 +57,11 @@ export default function EditionBox({ edition }: Readonly<Props>) {
return (
<div>
<SubHeading as="h2" id="enterprise-edition">
- <img
+ <Image
alt="SonarQube logo"
className="sw-mr-2"
width={16}
- src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`}
+ src="/images/embed-doc/sq-icon.svg"
/>
<span>Enterprise Edition</span>
</SubHeading>
@@ -83,11 +84,11 @@ export default function EditionBox({ edition }: Readonly<Props>) {
return (
<div>
<SubHeading as="h2" id="developer-edition">
- <img
+ <Image
alt="SonarQube logo"
className="sw-mr-2"
width={16}
- src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`}
+ src="/images/embed-doc/sq-icon.svg"
/>
<span>Developer Edition</span>
</SubHeading>
@@ -98,28 +99,18 @@ export default function EditionBox({ edition }: Readonly<Props>) {
<UnorderedList className="sw-ml-8" ticks>
<li>
<span>PR / MR decoration &amp; Quality Gate</span>
- <img
- alt="GitHub"
- className="sw-ml-2"
- src={`${getBaseUrl()}/images/alm/github.svg`}
- width={16}
- />
- <img
- alt="GitLab"
- className="sw-ml-2"
- src={`${getBaseUrl()}/images/alm/gitlab.svg`}
- width={16}
- />
- <img
+ <Image alt="GitHub" className="sw-ml-2" src="/images/alm/github.svg" width={16} />
+ <Image alt="GitLab" className="sw-ml-2" src="/images/alm/gitlab.svg" width={16} />
+ <Image
alt="Azure DevOps"
className="sw-ml-2"
- src={`${getBaseUrl()}/images/alm/azure.svg`}
+ src="/images/alm/azure.svg"
width={16}
/>
- <img
+ <Image
alt="Bitbucket"
className="sw-ml-2"
- src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
+ src="/images/alm/bitbucket.svg"
width={16}
/>
</li>
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx
index cc50083316b..104eaf2ec42 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx
@@ -17,13 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Link, Note, getTabPanelId } from 'design-system';
+
+import { Link } from '@sonarsource/echoes-react';
+import { Note, getTabPanelId } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import DocumentationLink from '../../../components/common/DocumentationLink';
+import { Image } from '../../../components/common/Image';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
import { CodeScope, queryToSearch } from '../../../helpers/urls';
import { Branch } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';
@@ -42,7 +44,10 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp
const isApp = component.qualifier === ComponentQualifier.Application;
const hasBadReferenceBranch =
- !isApp && !!period && !period.date && period.mode === NewCodeDefinitionType.ReferenceBranch;
+ !isApp &&
+ !!period &&
+ period.date === '' &&
+ period.mode === NewCodeDefinitionType.ReferenceBranch;
/*
* If the period is "reference branch"-based, and if there's no date, it means
* that we're not lacking a second analysis, but that we'll never have new code because the
@@ -65,11 +70,11 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp
id={getTabPanelId(CodeScope.New)}
style={{ height: 500 }}
>
- <img
+ <Image
alt="" /* Make screen readers ignore this image; it's purely eye candy. */
className="sw-mr-2"
height={52}
- src={`${getBaseUrl()}/images/source-code.svg`}
+ src="/images/source-code.svg"
/>
<Note as="div" className="sw-ml-4 sw-max-w-abs-500">
<p className="sw-mb-2 sw-mt-4">{translate(badExplanationKey)}</p>
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx
index 622eb382e04..08a16d37679 100644
--- a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx
@@ -17,11 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { ButtonPrimary, FlagMessage, Title } from 'design-system';
import * as React from 'react';
import GitHubSynchronisationWarning from '../../../../app/components/GitHubSynchronisationWarning';
+import { Image } from '../../../../components/common/Image';
import { translate } from '../../../../helpers/l10n';
-import { getBaseUrl } from '../../../../helpers/system';
+import { isDefined } from '../../../../helpers/types';
import { useGithubProvisioningEnabledQuery } from '../../../../queries/identity-provider/github';
import { isApplication, isPortfolioLike, isProject } from '../../../../types/component';
import { Component } from '../../../../types/types';
@@ -69,19 +71,19 @@ export default function PageHeader(props: Props) {
<Title>
{translate('permissions.page')}
{provisionedByGitHub && (
- <img
+ <Image
alt="github"
className="sw-mx-2 sw-align-baseline"
aria-label={translate('project_permission.github_managed')}
height={16}
- src={`${getBaseUrl()}/images/alm/github.svg`}
+ src="/images/alm/github.svg"
/>
)}
</Title>
<div>
<p>{description}</p>
- {visibilityDescription && <p>{visibilityDescription}</p>}
+ {isDefined(visibilityDescription) && <p>{visibilityDescription}</p>}
{provisionedByGitHub && (
<>
<p>{translate('roles.page.description.github')}</p>
diff --git a/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx b/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx
index 57ca2e0521e..d3ae3930556 100644
--- a/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx
+++ b/server/sonar-web/src/main/js/apps/projectInformation/badges/ProjectBadges.tsx
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { Spinner } from '@sonarsource/echoes-react';
import {
BasicSeparator,
@@ -32,6 +33,7 @@ import {
import { isEmpty } from 'lodash';
import * as React from 'react';
import { useState } from 'react';
+import { Image } from '../../../components/common/Image';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { localizeMetric } from '../../../helpers/measures';
@@ -103,7 +105,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) {
onClick={() => handleSelectType(BadgeType.measure)}
selected={BadgeType.measure === selectedType}
image={
- <img
+ <Image
alt={translate('overview.badges', BadgeType.measure, 'alt')}
src={getBadgeUrl(BadgeType.measure, fullBadgeOptions, token)}
/>
@@ -115,7 +117,7 @@ export default function ProjectBadges(props: ProjectBadgesProps) {
onClick={() => handleSelectType(BadgeType.qualityGate)}
selected={BadgeType.qualityGate === selectedType}
image={
- <img
+ <Image
alt={translate('overview.badges', BadgeType.qualityGate, 'alt')}
src={getBadgeUrl(BadgeType.qualityGate, fullBadgeOptions, token)}
width="128px"
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx
index 5d7c9e57d97..85e45472453 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCreationMenuItem.tsx
@@ -17,10 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { ItemLink } from 'design-system';
import * as React from 'react';
+import { Image } from '../../../components/common/Image';
import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
import { queryToSearch } from '../../../helpers/urls';
import { AlmKeys } from '../../../types/alm-settings';
@@ -40,12 +41,7 @@ export default function ProjectCreationMenuItem(props: ProjectCreationMenuItemPr
to={{ pathname: '/projects/create', search: queryToSearch({ mode: alm }) }}
>
{alm !== 'manual' && (
- <img
- alt={alm}
- className="sw-mr-2"
- width={16}
- src={`${getBaseUrl()}/images/alm/${almIcon}.svg`}
- />
+ <Image alt={alm} className="sw-mr-2" width={16} src={`/images/alm/${almIcon}.svg`} />
)}
{translate('my_account.add_project', alm)}
</ItemLink>
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx
index 6e21825714a..8a989fae4b1 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/EmptyHotspotsPage.tsx
@@ -17,11 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { Note } from 'design-system';
import * as React from 'react';
import DocumentationLink from '../../../components/common/DocumentationLink';
+import { Image } from '../../../components/common/Image';
import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
export interface EmptyHotspotsPageProps {
filtered: boolean;
@@ -45,13 +46,11 @@ export default function EmptyHotspotsPage(props: EmptyHotspotsPageProps) {
return (
<div className="sw-items-center sw-justify-center sw-flex-col sw-flex sw-pt-16">
- <img
+ <Image
alt={translate('hotspots.page')}
className="sw-mt-8"
height={100}
- src={`${getBaseUrl()}/images/${
- filtered && !filterByFile ? 'filter-large' : 'hotspot-large'
- }.svg`}
+ src={`/images/${filtered && !filterByFile ? 'filter-large' : 'hotspot-large'}.svg`}
/>
<h1 className="sw-mt-10 sw-body-sm-highlight">
{translate(`hotspots.${translationRoot}.title`)}
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 611756f59ce..ab22b747608 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
@@ -17,22 +17,23 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import styled from '@emotion/styled';
+import { Spinner } from '@sonarsource/echoes-react';
import {
Card,
FlagMessage,
PageContentFontWrapper,
- Spinner,
Title,
themeBorder,
themeColor,
} from 'design-system';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
+import { Image } from '../../../components/common/Image';
import { Location } from '../../../components/hoc/withRouter';
import { translate } from '../../../helpers/l10n';
import { sanitizeUserInput } from '../../../helpers/sanitize';
-import { getBaseUrl } from '../../../helpers/system';
import { getReturnUrl } from '../../../helpers/urls';
import { IdentityProvider } from '../../../types/types';
import LoginForm from './LoginForm';
@@ -54,17 +55,12 @@ export default function Login(props: Readonly<LoginProps>) {
return (
<div className="sw-flex sw-flex-col sw-items-center" id="login_form">
<Helmet defer={false} title={translate('login.page')} />
- <img alt="" className="sw-mt-32" src={`${getBaseUrl()}/images/sonar-logo-horizontal.png`} />
+ <Image alt="" className="sw-mt-32" src="/images/sonar-logo-horizontal.png" />
<Card className="sw-my-14 sw-p-0 sw-w-abs-350">
<PageContentFontWrapper className="sw-body-md sw-flex sw-flex-col sw-items-center sw-py-8 sw-px-4">
- <img
- alt=""
- className="sw-mb-6"
- src={`${getBaseUrl()}/images/embed-doc/sq-icon.svg`}
- width={28}
- />
+ <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>
- <Spinner loading={loading}>
+ <Spinner isLoading={loading}>
<>
{displayError && (
<FlagMessage className="sw-mb-6" variant="error">
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
index a0b21bed4f4..73da55bf78f 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmIntegrationRenderer.tsx
@@ -17,11 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { FlagMessage, Link, SubTitle, ToggleButton } from 'design-system';
+
+import { Link } from '@sonarsource/echoes-react';
+import { FlagMessage, SubTitle, ToggleButton } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
+import { Image } from '../../../../components/common/Image';
import { translate } from '../../../../helpers/l10n';
-import { getBaseUrl } from '../../../../helpers/system';
+import { isDefined } from '../../../../helpers/types';
import { useGetValuesQuery } from '../../../../queries/settings';
import {
AlmKeys,
@@ -56,12 +59,7 @@ const tabs = [
{
label: (
<>
- <img
- alt="github"
- className="sw-mr-2"
- height={16}
- src={`${getBaseUrl()}/images/alm/github.svg`}
- />
+ <Image alt="github" className="sw-mr-2" height={16} src="/images/alm/github.svg" />
{translate('settings.almintegration.tab.github')}
</>
),
@@ -70,12 +68,7 @@ const tabs = [
{
label: (
<>
- <img
- alt="bitbucket"
- className="sw-mr-2"
- height={16}
- src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
- />
+ <Image alt="bitbucket" className="sw-mr-2" height={16} src="/images/alm/bitbucket.svg" />
{translate('settings.almintegration.tab.bitbucket')}
</>
),
@@ -84,12 +77,7 @@ const tabs = [
{
label: (
<>
- <img
- alt="azure"
- className="sw-mr-2"
- height={16}
- src={`${getBaseUrl()}/images/alm/azure.svg`}
- />
+ <Image alt="azure" className="sw-mr-2" height={16} src="/images/alm/azure.svg" />
{translate('settings.almintegration.tab.azure')}
</>
),
@@ -98,12 +86,7 @@ const tabs = [
{
label: (
<>
- <img
- alt="gitlab"
- className="sw-mr-2"
- height={16}
- src={`${getBaseUrl()}/images/alm/gitlab.svg`}
- />
+ <Image alt="gitlab" className="sw-mr-2" height={16} src="/images/alm/gitlab.svg" />
{translate('settings.almintegration.tab.gitlab')}
</>
),
@@ -182,7 +165,7 @@ export default function AlmIntegrationRenderer(props: AlmIntegrationRendererProp
onUpdateDefinitions={props.onUpdateDefinitions}
/>
- {definitionKeyForDeletion && (
+ {isDefined(definitionKeyForDeletion) && (
<DeleteModal
id={definitionKeyForDeletion}
onCancel={props.onCancelDelete}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
index a3eac561c30..5358118d64a 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
@@ -17,16 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { Link } from '@sonarsource/echoes-react';
import classNames from 'classnames';
-import { FlagMessage, Link, SubTitle, ToggleButton, getTabId, getTabPanelId } from 'design-system';
+import { FlagMessage, SubTitle, ToggleButton, getTabId, getTabPanelId } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { useSearchParams } from 'react-router-dom';
import withAvailableFeatures, {
WithAvailableFeaturesProps,
} from '../../../../app/components/available-features/withAvailableFeatures';
+import { Image } from '../../../../components/common/Image';
import { translate } from '../../../../helpers/l10n';
-import { getBaseUrl } from '../../../../helpers/system';
import { searchParamsToQuery } from '../../../../helpers/urls';
import { AlmKeys } from '../../../../types/alm-settings';
import { Feature } from '../../../../types/features';
@@ -54,9 +56,7 @@ export const DOCUMENTATION_LINK_SUFFIXES = {
};
function renderDevOpsIcon(key: string) {
- return (
- <img alt={key} className="sw-mr-2" height={16} src={`${getBaseUrl()}/images/alm/${key}.svg`} />
- );
+ return <Image alt={key} className="sw-mr-2" height={16} src={`/images/alm/${key}.svg`} />;
}
export function Authentication(props: Props & WithAvailableFeaturesProps) {
diff --git a/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx b/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
index ad93135f5aa..ce64e3bae55 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
@@ -21,8 +21,9 @@
import { Badge, Note, getTextColor } from 'design-system';
import * as React from 'react';
import { colors } from '../../../app/theme';
+import { Image } from '../../../components/common/Image';
import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
+import { isDefined } from '../../../helpers/types';
import { IdentityProvider, Provider } from '../../../types/types';
import { RestUserDetailed } from '../../../types/users';
@@ -39,7 +40,9 @@ export default function UserListItemIdentity({ identityProvider, user, managePro
<strong className="it__user-name sw-body-sm-highlight">{user.name}</strong>
<Note className="it__user-login">{user.login}</Note>
</div>
- {user.email && <div className="it__user-email sw-mt-1">{user.email}</div>}
+ {isDefined(user.email) && user.email !== '' && (
+ <div className="it__user-email sw-mt-1">{user.email}</div>
+ )}
{!user.local && user.externalProvider !== 'sonarqube' && (
<ExternalProvider identityProvider={identityProvider} user={user} />
)}
@@ -68,11 +71,11 @@ export function ExternalProvider({ identityProvider, user }: Omit<Props, 'manage
color: getTextColor(identityProvider.backgroundColor, colors.secondFontColor),
}}
>
- <img
+ <Image
alt={identityProvider.name}
className="sw-mr-1"
height="14"
- src={getBaseUrl() + identityProvider.iconPath}
+ src={identityProvider.iconPath}
width="14"
/>
{user.externalLogin}
diff --git a/server/sonar-web/src/main/js/components/common/Image.tsx b/server/sonar-web/src/main/js/components/common/Image.tsx
new file mode 100644
index 00000000000..e15f8df3fcc
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/common/Image.tsx
@@ -0,0 +1,42 @@
+/*
+ * 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 * as React from 'react';
+import { getBaseUrl } from '../../helpers/system';
+
+export function Image(props: Readonly<JSX.IntrinsicElements['img']>) {
+ const { alt, src: source, ...rest } = props;
+
+ const baseUrl = getBaseUrl();
+
+ let src = source;
+
+ if (
+ src !== undefined &&
+ !src.startsWith(baseUrl) &&
+ !src.startsWith('http') &&
+ !src.startsWith('data:')
+ ) {
+ src = `${baseUrl}/${src}`.replace(/(?<!:)\/+/g, '/');
+ }
+
+ // eslint-disable-next-line react/forbid-elements
+ return <img alt={alt} src={src} {...rest} />;
+}
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 3555367e6b6..8f613d698e5 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
@@ -17,11 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { ItemDivider, ItemHeader, ItemLink, OpenNewTabIcon } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
-import { getBaseUrl } from '../../helpers/system';
import { SuggestionLink } from '../../types/types';
+import { Image } from '../common/Image';
import { DocItemLink } from './DocItemLink';
import { SuggestionsContext } from './SuggestionsContext';
@@ -36,12 +37,12 @@ function IconLink({
}) {
return (
<ItemLink to={link}>
- <img
+ <Image
alt={text}
aria-hidden
className="sw-mr-2"
height="18"
- src={`${getBaseUrl()}/images/${icon}`}
+ src={`/images/${icon}`}
width="18"
/>
{text}
diff --git a/server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx b/server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx
index a1a7d38d5ad..fb2d9b3e382 100644
--- a/server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx
+++ b/server/sonar-web/src/main/js/components/permissions/GroupHolder.tsx
@@ -17,13 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { Badge, ContentCell, TableRowInteractive, UserGroupIcon } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { isPermissionDefinitionGroup } from '../../helpers/permissions';
-import { getBaseUrl } from '../../helpers/system';
+import { isDefined } from '../../helpers/types';
import { Permissions } from '../../types/permissions';
import { PermissionDefinitions, PermissionGroup } from '../../types/types';
+import { Image } from '../common/Image';
import PermissionCell from './PermissionCell';
import usePermissionChange from './usePermissionChange';
@@ -63,12 +65,12 @@ export default function GroupHolder(props: Props) {
<strong>{group.name}</strong>
</div>
{disabled && (
- <img
+ <Image
alt="github"
className="sw-ml-2"
aria-label={translate('project_permission.github_managed')}
height={16}
- src={`${getBaseUrl()}/images/alm/github.svg`}
+ src="/images/alm/github.svg"
/>
)}
{group.name === ANYONE && (
@@ -77,7 +79,9 @@ export default function GroupHolder(props: Props) {
</Badge>
)}
</div>
- {description && <div className="sw-mt-2 sw-whitespace-normal">{description}</div>}
+ {isDefined(description) && (
+ <div className="sw-mt-2 sw-whitespace-normal">{description}</div>
+ )}
</div>
</div>
</ContentCell>
diff --git a/server/sonar-web/src/main/js/components/permissions/UserHolder.tsx b/server/sonar-web/src/main/js/components/permissions/UserHolder.tsx
index e07c01b6f87..de523fdd992 100644
--- a/server/sonar-web/src/main/js/components/permissions/UserHolder.tsx
+++ b/server/sonar-web/src/main/js/components/permissions/UserHolder.tsx
@@ -17,12 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { Avatar, ContentCell, Note, TableRowInteractive } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { isPermissionDefinitionGroup } from '../../helpers/permissions';
-import { getBaseUrl } from '../../helpers/system';
+import { isDefined } from '../../helpers/types';
import { PermissionDefinitions, PermissionUser } from '../../types/types';
+import { Image } from '../common/Image';
import PermissionCell from './PermissionCell';
import usePermissionChange from './usePermissionChange';
@@ -90,16 +92,16 @@ export default function UserHolder(props: Props) {
<Note className="sw-ml-2">{user.login}</Note>
</div>
{disabled && (
- <img
+ <Image
alt="github"
className="sw-ml-2"
height={16}
aria-label={translate('project_permission.github_managed')}
- src={`${getBaseUrl()}/images/alm/github.svg`}
+ src="/images/alm/github.svg"
/>
)}
</div>
- {user.email && (
+ {isDefined(user.email) && (
<div className="sw-mt-2 sw-max-w-100 sw-text-ellipsis sw-whitespace-nowrap sw-overflow-hidden">
{user.email}
</div>
diff --git a/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx b/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
index 35dd791d8f4..9a5328edeaa 100644
--- a/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
+++ b/server/sonar-web/src/main/js/components/tutorials/TutorialSelectionRenderer.tsx
@@ -18,15 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { LinkHighlight, LinkStandalone, Spinner } from '@sonarsource/echoes-react';
import {
Breadcrumbs,
FlagMessage,
GreyCard,
- HoverLink,
LightLabel,
LightPrimary,
- Spinner,
- StandoutLink,
SubTitle,
Title,
} from 'design-system';
@@ -34,13 +32,13 @@ import * as React from 'react';
import { AnalysisStatus } from '../../apps/overview/components/AnalysisStatus';
import { isMainBranch } from '../../helpers/branch-like';
import { translate } from '../../helpers/l10n';
-import { getBaseUrl } from '../../helpers/system';
import { getProjectTutorialLocation } from '../../helpers/urls';
import { useBranchesQuery } from '../../queries/branch';
import { AlmKeys, AlmSettingsInstance, ProjectAlmBindingResponse } from '../../types/alm-settings';
import { MainBranch } from '../../types/branch-like';
import { Component } from '../../types/types';
import { LoggedInUser } from '../../types/users';
+import { Image } from '../common/Image';
import AzurePipelinesTutorial from './azure-pipelines/AzurePipelinesTutorial';
import BitbucketPipelinesTutorial from './bitbucket-pipelines/BitbucketPipelinesTutorial';
import GitHubActionTutorial from './github-action/GitHubActionTutorial';
@@ -66,9 +64,11 @@ export interface TutorialSelectionRendererProps {
function renderAlm(mode: TutorialModes, project: string, icon?: React.ReactNode) {
return (
<GreyCard className="sw-col-span-4 sw-p-4">
- <StandoutLink icon={icon} to={getProjectTutorialLocation(project, mode)}>
- {translate('onboarding.tutorial.choose_method', mode)}
- </StandoutLink>
+ <LinkStandalone iconLeft={icon} to={getProjectTutorialLocation(project, mode)}>
+ <span className={icon ? 'sw-ml-2' : ''}>
+ {translate('onboarding.tutorial.choose_method', mode)}
+ </span>
+ </LinkStandalone>
{mode === TutorialModes.Local && (
<LightLabel as="p" className="sw-mt-3">
@@ -157,10 +157,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
renderAlm(
TutorialModes.Jenkins,
component.key,
- <img
+ <Image
alt="" // Should be ignored by screen readers
className="sw-h-4 sw-w-4"
- src={`${getBaseUrl()}/images/tutorials/jenkins.svg`}
+ src="/images/tutorials/jenkins.svg"
/>,
)}
@@ -168,10 +168,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
renderAlm(
TutorialModes.GitHubActions,
component.key,
- <img
+ <Image
alt="" // Should be ignored by screen readers
className="sw-h-4 sw-w-4"
- src={`${getBaseUrl()}/images/tutorials/github-actions.svg`}
+ src="/images/tutorials/github-actions.svg"
/>,
)}
@@ -179,10 +179,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
renderAlm(
TutorialModes.BitbucketPipelines,
component.key,
- <img
+ <Image
alt="" // Should be ignored by screen readers
className="sw-h-4 sw-w-4"
- src={`${getBaseUrl()}/images/alm/bitbucket.svg`}
+ src="/images/alm/bitbucket.svg"
/>,
)}
@@ -190,10 +190,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
renderAlm(
TutorialModes.GitLabCI,
component.key,
- <img
+ <Image
alt="" // Should be ignored by screen readers
className="sw-h-4 sw-w-4"
- src={`${getBaseUrl()}/images/alm/gitlab.svg`}
+ src="/images/alm/gitlab.svg"
/>,
)}
@@ -201,10 +201,10 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
renderAlm(
TutorialModes.AzurePipelines,
component.key,
- <img
+ <Image
alt="" // Should be ignored by screen readers
className="sw-h-4 sw-w-4"
- src={`${getBaseUrl()}/images/tutorials/azure-pipelines.svg`}
+ src="/images/tutorials/azure-pipelines.svg"
/>,
)}
@@ -217,13 +217,19 @@ export default function TutorialSelectionRenderer(props: TutorialSelectionRender
{selectedTutorial && (
<Breadcrumbs className="sw-mb-3">
- <HoverLink to={getProjectTutorialLocation(component.key)}>
+ <LinkStandalone
+ highlight={LinkHighlight.CurrentColor}
+ to={getProjectTutorialLocation(component.key)}
+ >
{translate('onboarding.tutorial.breadcrumbs.home')}
- </HoverLink>
+ </LinkStandalone>
- <HoverLink to={getProjectTutorialLocation(component.key, selectedTutorial)}>
+ <LinkStandalone
+ highlight={LinkHighlight.CurrentColor}
+ to={getProjectTutorialLocation(component.key, selectedTutorial)}
+ >
{translate('onboarding.tutorial.breadcrumbs', selectedTutorial)}
- </HoverLink>
+ </LinkStandalone>
</Breadcrumbs>
)}
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 24c5cf72802..a22c87b5e66 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
@@ -17,11 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { LinkStandalone } from '@sonarsource/echoes-react';
import classNames from 'classnames';
-import { Card, LightLabel, StandoutLink } from 'design-system';
+import { Card, LightLabel } from 'design-system';
import React from 'react';
import { translate } from '../../../helpers/l10n';
-import { getBaseUrl } from '../../../helpers/system';
+import { Image } from '../../common/Image';
import { OSs, TutorialModes } from '../types';
export interface GithubCFamilyExampleRepositoriesProps {
@@ -58,15 +60,15 @@ export default function GithubCFamilyExampleRepositories(
return (
<Card className={classNames('sw-p-4 sw-bg-inherit', className)}>
<div>
- <img
+ <Image
alt="" // Should be ignored by screen readers
className="sw-mr-2"
height={20}
- src={`${getBaseUrl()}/images/alm/github.svg`}
+ src="/images/alm/github.svg"
/>
- <StandoutLink target="_blank" to={link}>
+ <LinkStandalone target="_blank" to={link}>
sonarsource-cfamily-examples
- </StandoutLink>
+ </LinkStandalone>
</div>
<LightLabel as="p" className="sw-mt-4">
{translate('onboarding.tutorial.cfamily.examples_repositories_description')}