* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import React, { HTMLAttributeAnchorTarget } from 'react';
import { TooltipWrapperInner } from './Tooltip';
import { OpenNewTabIcon } from './icons/OpenNewTabIcon';
+/** @deprecated Use LinkProps from Echoes instead.
+ *
+ * Some of the props have changed or been renamed:
+ * - `blurAfterClick` is now `shouldBlurAfterClick`
+ * - ~`disabled`~ doesn't exist anymore, a disabled link is just a regular text
+ * - `forceExternal` is now `isExternal`
+ * - `icon` is now `iconLeft` and can only be used with LinkStandalone
+ * - `preventDefault` is now `shouldPreventDefault`
+ * - `showExternalIcon` is now `hasExternalIcon`
+ * - `stopPropagation` is now `shouldStopPropagation`
+ */
export interface LinkProps extends RouterLinkProps {
blurAfterClick?: boolean;
disabled?: boolean;
color: ${themeColor('linkExternalIcon')};
`;
+/** @deprecated Use either Link or LinkStandalone from Echoes, or react-router-dom's Link instead.
+ */
export const BaseLink = React.forwardRef(BaseLinkWithRef);
const StyledBaseLink = styled(BaseLink)`
`};
`;
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const NakedLink = styled(BaseLink)`
border-bottom: none;
padding-bottom: 1px;
}`};
`;
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const DrilldownLink = styled(StyledBaseLink)`
${tw`sw-heading-lg`}
${tw`sw-tracking-tight`}
DrilldownLink.displayName = 'DrilldownLink';
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const HoverLink = styled(StyledBaseLink)`
text-decoration: none;
`;
HoverLink.displayName = 'HoverLink';
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const LinkBox = styled(StyledBaseLink)`
text-decoration: none;
`;
LinkBox.displayName = 'LinkBox';
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const DiscreetLinkBox = styled(StyledBaseLink)`
text-decoration: none;
`;
LinkBox.displayName = 'DiscreetLinkBox';
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const DiscreetLink = styled(HoverLink)`
--border: ${themeBorder('default', 'linkDiscreet')};
`;
DiscreetLink.displayName = 'DiscreetLink';
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const ContentLink = styled(HoverLink)`
--color: ${themeColor('pageTitle')};
--border: ${themeBorder('default', 'contentLinkBorder')};
`;
ContentLink.displayName = 'ContentLink';
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const StandoutLink = styled(StyledBaseLink)`
${tw`sw-font-semibold`}
${tw`sw-no-underline`}
`;
StandoutLink.displayName = 'StandoutLink';
+/** @deprecated Use either Link or LinkStandalone from Echoes instead.
+ */
export const IssueIndicatorLink = styled(BaseLink)`
color: ${themeColor('codeLineMeta')};
text-decoration: none;
* 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 { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
import {
- DiscreetLink,
FlagMessage,
LAYOUT_VIEWPORT_MIN_WIDTH,
PageContentFontWrapper,
hideLoggedInInfo?: boolean;
}
-export default function GlobalFooter({ hideLoggedInInfo }: GlobalFooterProps) {
+export default function GlobalFooter({ hideLoggedInInfo }: Readonly<GlobalFooterProps>) {
const appState = React.useContext(AppStateContext);
const currentEdition = appState?.edition && getEdition(appState.edition);
<span className="sw-body-md-highlight">
{translate('footer.production_database_warning')}
</span>
+
<br />
+
<InstanceMessage message={translate('footer.production_database_explanation')} />
</p>
</FlagMessage>
<ul className="sw-flex sw-items-center sw-gap-3 sw-ml-4">
{!hideLoggedInInfo && currentEdition && <li>{currentEdition.name}</li>}
+
{!hideLoggedInInfo && appState?.version && (
<li className="sw-code">
{translateWithParameters('footer.version_x', appState.version)}
</li>
)}
+
<li>
- <DiscreetLink to="https://www.gnu.org/licenses/lgpl-3.0.txt">
+ <LinkStandalone
+ highlight={LinkHighlight.CurrentColor}
+ to="https://www.gnu.org/licenses/lgpl-3.0.txt"
+ >
{translate('footer.license')}
- </DiscreetLink>
+ </LinkStandalone>
</li>
+
<li>
- <DiscreetLink to="https://community.sonarsource.com/c/help/sq">
+ <LinkStandalone
+ highlight={LinkHighlight.CurrentColor}
+ to="https://community.sonarsource.com/c/help/sq"
+ >
{translate('footer.community')}
- </DiscreetLink>
+ </LinkStandalone>
</li>
+
<li>
- <DiscreetLink to={docUrl('/')}>{translate('footer.documentation')}</DiscreetLink>
+ <LinkStandalone highlight={LinkHighlight.CurrentColor} to={docUrl('/')}>
+ {translate('footer.documentation')}
+ </LinkStandalone>
</li>
+
<li>
- <DiscreetLink to={docUrl('/instance-administration/plugin-version-matrix/')}>
+ <LinkStandalone
+ highlight={LinkHighlight.CurrentColor}
+ to={docUrl('/instance-administration/plugin-version-matrix/')}
+ >
{translate('footer.plugins')}
- </DiscreetLink>
+ </LinkStandalone>
</li>
+
{!hideLoggedInInfo && (
<li>
- <DiscreetLink to="/web_api">{translate('footer.web_api')}</DiscreetLink>
+ <LinkStandalone highlight={LinkHighlight.CurrentColor} to="/web_api">
+ {translate('footer.web_api')}
+ </LinkStandalone>
</li>
)}
</ul>
}
const StyledFooter = styled.div`
+ background-color: ${themeColor('backgroundSecondary')};
+ border-top: ${themeBorder('default')};
box-sizing: border-box;
min-width: ${LAYOUT_VIEWPORT_MIN_WIDTH}px;
- border-top: ${themeBorder('default')};
- background-color: ${themeColor('backgroundSecondary')};
`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { DiscreetLink } from 'design-system';
+
+import { Link, LinkHighlight } from '@sonarsource/echoes-react';
import * as React from 'react';
import { isOfficial } from '../../helpers/system';
return official ? (
<div>
SonarQube™ technology is powered by{' '}
- <DiscreetLink to="https://www.sonarsource.com">SonarSource SA</DiscreetLink>
+ <Link highlight={LinkHighlight.CurrentColor} to="https://www.sonarsource.com">
+ SonarSource SA
+ </Link>
</div>
) : (
<div>
This application is based on{' '}
- <DiscreetLink
+ <Link
+ highlight={LinkHighlight.CurrentColor}
to="https://www.sonarsource.com/products/sonarqube/?referrer=sonarqube"
title="SonarQube™"
>
SonarQube™
- </DiscreetLink>{' '}
+ </Link>{' '}
but is <strong>not</strong> an official version provided by{' '}
- <DiscreetLink to="https://www.sonarsource.com" title="SonarSource SA">
+ <Link
+ highlight={LinkHighlight.CurrentColor}
+ to="https://www.sonarsource.com"
+ title="SonarSource SA"
+ >
SonarSource SA
- </DiscreetLink>
+ </Link>
.
</div>
);
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import {
- ContentCell,
- Key,
- KeyboardHint,
- Link,
- Modal,
- SubTitle,
- Table,
- TableRow,
-} from 'design-system';
+import { LinkStandalone } from '@sonarsource/echoes-react';
+import { ContentCell, Key, KeyboardHint, Modal, SubTitle, Table, TableRow } from 'design-system';
import * as React from 'react';
import { isInput } from '../../helpers/keyboardEventHelpers';
import { KeyboardKeys } from '../../helpers/keycodes';
return SECTIONS.map((section) => (
<div key={section.subTitle} className="sw-mb-4">
<SubTitle>{translate(section.subTitle)}</SubTitle>
+
<Table columnCount={2} columnWidths={['30%', '70%']}>
{section.rows.map((row) => (
<TableRow key={row.command}>
<ContentCell className="sw-justify-center">
<KeyboardHint command={row.command} title="" />
</ContentCell>
+
<ContentCell>{translate(row.description)}</ContentCell>
</TableRow>
))}
const body = (
<>
- <Link
- to="/account"
+ <LinkStandalone
onClick={() => {
setDisplay(false);
return true;
}}
+ to="/account"
>
{translate('keyboard_shortcuts_modal.disable_link')}
- </Link>
+ </LinkStandalone>
+
<div className="sw-mt-4">{renderSection()}</div>
</>
);
return (
<Modal
+ body={body}
headerTitle={title}
onClose={() => setDisplay(false)}
- body={body}
secondaryButtonLabel={translate('close')}
/>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { HoverLink, TextMuted } from 'design-system';
+
+import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
import * as React from 'react';
import Favorite from '../../../../components/controls/Favorite';
import { getComponentOverviewUrl } from '../../../../helpers/urls';
currentUser: CurrentUser;
}
-export function Breadcrumb(props: BreadcrumbProps) {
+export function Breadcrumb(props: Readonly<BreadcrumbProps>) {
const { component, currentUser } = props;
return (
qualifier={component.qualifier}
/>
)}
- <HoverLink
- blurAfterClick
- className="js-project-link sw-flex"
+
+ <LinkStandalone
+ highlight={LinkHighlight.Subdued}
+ className="js-project-link"
key={breadcrumbElement.name}
+ shouldBlurAfterClick
title={breadcrumbElement.name}
to={getComponentOverviewUrl(breadcrumbElement.key, breadcrumbElement.qualifier)}
>
- <TextMuted text={breadcrumbElement.name} />
- </HoverLink>
+ {breadcrumbElement.name}
+ </LinkStandalone>
+
{isNotLast && <span className="slash-separator sw-mx-2" />}
</div>
);
* 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 'design-system';
+
+import { LinkStandalone } from '@sonarsource/echoes-react';
import React from 'react';
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';
import { Component } from '../../../../../types/types';
function getPRUrlAlmKey(url = '') {
const lowerCaseUrl = url.toLowerCase();
+
if (lowerCaseUrl.includes(AlmKeys.GitHub)) {
return AlmKeys.GitHub;
} else if (lowerCaseUrl.includes(AlmKeys.GitLab)) {
) {
return AlmKeys.Azure;
}
+
return undefined;
}
export default function PRLink({
currentBranchLike,
component,
-}: {
+}: Readonly<{
currentBranchLike: BranchLike;
component: Component;
-}) {
+}>) {
if (!isPullRequest(currentBranchLike)) {
return null;
}
const almKey =
component.alm?.key ||
- (isPullRequest(currentBranchLike) && getPRUrlAlmKey(currentBranchLike.url));
+ (isPullRequest(currentBranchLike) && getPRUrlAlmKey(currentBranchLike.url)) ||
+ '';
+
return (
<>
- {currentBranchLike.url !== undefined && (
- <Link
- icon={
- almKey && (
+ {isDefined(currentBranchLike.url) && (
+ <LinkStandalone
+ iconLeft={
+ almKey !== '' && (
<img
alt={almKey}
height={16}
key={currentBranchLike.key}
to={currentBranchLike.url}
>
- {!almKey && translate('branches.see_the_pr')}
- </Link>
+ {almKey === '' && translate('branches.see_the_pr')}
+ </LinkStandalone>
)}
</>
);
* 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,
FlagMessage,
FormField,
InputField,
- Link,
PageContentFontWrapper,
Spinner,
SubTitle,
import { DEFAULT_ADMIN_PASSWORD } from './constants';
export interface ChangeAdminPasswordAppRendererProps {
- passwordValue: string;
+ canAdmin?: boolean;
+ canSubmit?: boolean;
confirmPasswordValue: string;
+ location: Location;
onConfirmPasswordChange: (password: string) => void;
onPasswordChange: (password: string) => void;
onSubmit: () => void;
- canAdmin?: boolean;
- canSubmit?: boolean;
+ passwordValue: string;
submitting: boolean;
success: boolean;
- location: Location;
}
const PASSWORD_FIELD_ID = 'user-password';
const CONFIRM_PASSWORD_FIELD_ID = 'confirm-user-password';
-export default function ChangeAdminPasswordAppRenderer(props: ChangeAdminPasswordAppRendererProps) {
+export default function ChangeAdminPasswordAppRenderer(
+ props: Readonly<ChangeAdminPasswordAppRendererProps>,
+) {
const {
canAdmin,
canSubmit,
confirmPasswordValue,
- passwordValue,
location,
+ passwordValue,
submitting,
success,
} = props;
return (
<CenteredLayout>
<Helmet defer={false} title={translate('users.change_admin_password.page')} />
+
<PageContentFontWrapper className="sw-body-sm sw-flex sw-flex-col sw-items-center sw-justify-center">
<Card className="sw-mx-auto sw-mt-24 sw-w-abs-600 sw-flex sw-items-stretch sw-flex-col">
{success ? (
<FlagMessage className="sw-my-8" variant="success">
<div>
<p className="sw-mb-2">{translate('users.change_admin_password.form.success')}</p>
+
{/* We must reload because we need a refresh of the /api/navigation/global call. */}
- <Link to={getReturnUrl(location)} reloadDocument>
+ <LinkStandalone to={getReturnUrl(location)} reloadDocument>
{translate('users.change_admin_password.form.continue_to_app')}
- </Link>
+ </LinkStandalone>
</div>
</FlagMessage>
) : (
<>
<Title>{translate('users.change_admin_password.instance_is_at_risk')}</Title>
+
<DarkLabel className="sw-mb-2">
{translate('users.change_admin_password.header')}
</DarkLabel>
+
<p>{translate('users.change_admin_password.description')}</p>
<form
</SubTitle>
<FormField
- label={translate('users.change_admin_password.form.password')}
htmlFor={PASSWORD_FIELD_ID}
+ label={translate('users.change_admin_password.form.password')}
required
>
<InputField
</FormField>
<FormField
- label={translate('users.change_admin_password.form.confirm')}
- htmlFor={CONFIRM_PASSWORD_FIELD_ID}
- required
description={
confirmPasswordValue === passwordValue &&
passwordValue === DEFAULT_ADMIN_PASSWORD && (
</FlagMessage>
)
}
+ htmlFor={CONFIRM_PASSWORD_FIELD_ID}
+ label={translate('users.change_admin_password.form.confirm')}
+ required
>
<InputField
id={CONFIRM_PASSWORD_FIELD_ID}
type="submit"
>
<Spinner className="sw-mr-2" loading={submitting} />
+
{translate('update_verb')}
</ButtonPrimary>
</form>
* 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, BranchIcon, HoverLink, LightLabel, Note, QualifierIcon } from 'design-system';
+
+import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
+import { Badge, BranchIcon, LightLabel, Note, QualifierIcon } from 'design-system';
import * as React from 'react';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
+import { isDefined } from '../../../helpers/types';
import { CodeScope, getComponentOverviewUrl, queryToSearch } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import {
component.qualifier === ComponentQualifier.File ||
component.qualifier === ComponentQualifier.TestFile;
- if (isFile && component.path) {
- return component.path + '\n\n' + component.key;
+ if (isFile && isDefined(component.path)) {
+ return `${component.path}\n\n${component.key}`;
}
return [component.name, component.key, component.branch].filter((s) => !!s).join('\n\n');
branchLike?: BranchLike;
canBrowse?: boolean;
component: ComponentMeasure;
+ newCodeSelected?: boolean;
previous?: ComponentMeasure;
rootComponent: ComponentMeasure;
- unclickable?: boolean;
- newCodeSelected?: boolean;
showIcon?: boolean;
+ unclickable?: boolean;
}
export default function ComponentName({
branchLike,
- component,
- unclickable = false,
- rootComponent,
- previous,
canBrowse = false,
+ component,
newCodeSelected,
+ previous,
+ rootComponent,
showIcon = true,
-}: Props) {
+ unclickable = false,
+}: Readonly<Props>) {
const ariaLabel = unclickable ? translate('code.parent_folder') : undefined;
if (
newCodeSelected,
)}
</div>
+
{component.branch ? (
<div className="sw-truncate sw-ml-2">
<BranchIcon className="sw-mr-1" />
+
<Note>{component.branch}</Note>
</div>
) : (
</span>
);
}
+
return (
<span title={getTooltip(component)} aria-label={ariaLabel}>
{renderNameWithIcon(
) {
const name = renderName(component, previous);
const codeType = newCodeSelected ? CodeScope.New : CodeScope.Overall;
+
if (
!unclickable &&
(isPortfolioLike(component.qualifier) ||
)
? component.branch
: undefined;
+
return (
- <HoverLink
- icon={showIcon && <QualifierIcon className="sw-mr-1" qualifier={component.qualifier} />}
+ <LinkStandalone
+ highlight={LinkHighlight.CurrentColor}
+ iconLeft={showIcon && <QualifierIcon className="sw-mr-2" qualifier={component.qualifier} />}
to={getComponentOverviewUrl(
component.refKey ?? component.key,
component.qualifier,
)}
>
{name}
- </HoverLink>
+ </LinkStandalone>
);
} else if (canBrowse) {
const query = { id: rootComponent.key, ...getBranchLikeQuery(branchLike) };
+
if (component.key !== rootComponent.key) {
Object.assign(query, { selected: component.key });
}
+
return (
- <HoverLink
- icon={showIcon && <QualifierIcon qualifier={component.qualifier} />}
+ <LinkStandalone
+ highlight={LinkHighlight.CurrentColor}
+ iconLeft={showIcon && <QualifierIcon className="sw-mr-2" qualifier={component.qualifier} />}
to={{ pathname: '/code', search: queryToSearch(query) }}
>
{name}
- </HoverLink>
+ </LinkStandalone>
);
}
+
return (
<span>
{showIcon && (
- <QualifierIcon className="sw-mr-1 sw-align-text-bottom" qualifier={component.qualifier} />
+ <QualifierIcon className="sw-mr-2 sw-align-text-bottom" qualifier={component.qualifier} />
)}
+
{name}
</span>
);
component.qualifier === ComponentQualifier.Directory &&
previous &&
previous.qualifier === ComponentQualifier.Directory;
+
const prefix =
- areBothDirs && previous !== undefined
+ areBothDirs && isDefined(previous)
? mostCommonPrefix([component.name + '/', previous.name + '/'])
: '';
+
return prefix ? (
<span>
<LightLabel>{prefix}</LightLabel>
+
<span>{component.name.slice(prefix.length)}</span>
</span>
) : (
* 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, Card, CenteredLayout, Link, Note, Spinner, Title } from 'design-system';
+import { Link, LinkStandalone, Spinner } from '@sonarsource/echoes-react';
+import { ButtonPrimary, Card, CenteredLayout, Note, Title } from 'design-system';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { FormattedMessage } from 'react-intl';
import TimeFormatter from '../../../components/intl/TimeFormatter';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
+import { isDefined } from '../../../helpers/types';
import { getReturnUrl } from '../../../helpers/urls';
interface Props {
componentWillUnmount() {
this.mounted = false;
- if (this.interval !== undefined) {
+
+ if (isDefined(this.interval)) {
window.clearInterval(this.interval);
}
}
fetchStatus = () => {
const request = this.props.setup ? this.fetchMigrationState() : this.fetchSystemStatus();
+
request.catch(() => {
if (this.mounted) {
this.setState({
return (
<>
<Helmet defaultTitle={translate('maintenance.page')} defer={false} />
+
<CenteredLayout className="sw-flex sw-justify-around sw-mt-32" id="bd">
<Card className="sw-body-sm sw-p-10 sw-w-abs-400" id="nonav">
{status === 'OFFLINE' && (
<MaintenanceTitle className="text-danger">
<InstanceMessage message={translate('maintenance.is_offline')} />
</MaintenanceTitle>
+
<MaintenanceText>
{translate('maintenance.sonarqube_is_offline.text')}
</MaintenanceText>
+
<div className="sw-text-center">
- <Link reloadDocument to={`${getBaseUrl()}/`}>
+ <LinkStandalone reloadDocument to={`${getBaseUrl()}/`}>
{translate('maintenance.try_again')}
- </Link>
+ </LinkStandalone>
</div>
</>
)}
<MaintenanceTitle>
<InstanceMessage message={translate('maintenance.is_up')} />
</MaintenanceTitle>
+
<MaintenanceText className="sw-text-center">
{translate('maintenance.all_systems_opetational')}
</MaintenanceText>
+
<div className="sw-text-center">
- <Link to="/">{translate('layout.home')}</Link>
+ <LinkStandalone to="/">{translate('layout.home')}</LinkStandalone>
</div>
</>
)}
<MaintenanceTitle>
<InstanceMessage message={translate('maintenance.is_starting')} />
</MaintenanceTitle>
+
<MaintenanceSpinner>
<Spinner />
</MaintenanceSpinner>
<MaintenanceTitle className="text-danger">
<InstanceMessage message={translate('maintenance.is_down')} />
</MaintenanceTitle>
+
<MaintenanceText>{translate('maintenance.sonarqube_is_down.text')}</MaintenanceText>
+
<MaintenanceText className="sw-text-center">
- <Link reloadDocument to={`${getBaseUrl()}/`}>
+ <LinkStandalone reloadDocument to={`${getBaseUrl()}/`}>
{translate('maintenance.try_again')}
- </Link>
+ </LinkStandalone>
</MaintenanceText>
</>
)}
<MaintenanceTitle>
<InstanceMessage message={translate('maintenance.is_under_maintenance')} />
</MaintenanceTitle>
+
<MaintenanceText>
<FormattedMessage
defaultMessage={translate('maintenance.sonarqube_is_under_maintenance.1')}
id="maintenance.sonarqube_is_under_maintenance.1"
values={{
link: (
- <Link
- to="https://www.sonarlint.org/?referrer=sonarqube-maintenance"
- target="_blank"
- >
+ <Link to="https://www.sonarlint.org/?referrer=sonarqube-maintenance">
{translate('maintenance.sonarqube_is_under_maintenance_link.1')}
</Link>
),
}}
/>
</MaintenanceText>
+
<MaintenanceText>
<FormattedMessage
defaultMessage={translate('maintenance.sonarqube_is_under_maintenance.2')}
<MaintenanceTitle>
{translate('maintenance.database_is_up_to_date')}
</MaintenanceTitle>
+
<div className="sw-text-center">
- <Link to="/">{translate('layout.home')}</Link>
+ <LinkStandalone to="/">{translate('layout.home')}</LinkStandalone>
</div>
</>
)}
{state === 'MIGRATION_REQUIRED' && (
<>
<MaintenanceTitle>{translate('maintenance.upgrade_database')}</MaintenanceTitle>
+
<MaintenanceText>{translate('maintenance.upgrade_database.1')}</MaintenanceText>
+
<MaintenanceText>{translate('maintenance.upgrade_database.2')}</MaintenanceText>
+
<MaintenanceText>{translate('maintenance.upgrade_database.3')}</MaintenanceText>
+
<MaintenanceSpinner>
<ButtonPrimary id="start-migration" onClick={this.handleMigrateClick}>
{translate('maintenance.upgrade')}
<MaintenanceTitle className="text-danger">
{translate('maintenance.migration_not_supported')}
</MaintenanceTitle>
+
<p>{translate('maintenance.migration_not_supported.text')}</p>
</>
)}
{state === 'MIGRATION_RUNNING' && (
<>
<MaintenanceTitle>{translate('maintenance.database_migration')}</MaintenanceTitle>
- {this.state.message !== undefined && (
+
+ {isDefined(this.state.message) && (
<MaintenanceText className="sw-text-center">{this.state.message}</MaintenanceText>
)}
- {this.state.startedAt !== undefined && (
+
+ {isDefined(this.state.startedAt) && (
<MaintenanceText className="sw-text-center">
{translate('background_tasks.table.started')}{' '}
<DateFromNow date={this.state.startedAt} />
</Note>
</MaintenanceText>
)}
+
<MaintenanceSpinner>
<Spinner />
</MaintenanceSpinner>
<MaintenanceTitle className="text-success">
{translate('maintenance.database_is_up_to_date')}
</MaintenanceTitle>
+
<div className="sw-text-center">
- <Link to="/">{translate('layout.home')}</Link>
+ <LinkStandalone to="/">{translate('layout.home')}</LinkStandalone>
</div>
</>
)}
<MaintenanceTitle className="text-danger">
{translate('maintenance.upgrade_failed')}
</MaintenanceTitle>
+
<MaintenanceText>{translate('maintenance.upgrade_failed.text')}</MaintenanceText>
</>
)}
}
const MaintenanceTitle = styled(Title)`
- text-align: center;
margin-bottom: 2.5rem;
+ text-align: center;
`;
const MaintenanceText = styled.p`
* 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, Spinner } from '@sonarsource/echoes-react';
import classNames from 'classnames';
-import { FlagMessage, Link, Spinner } from 'design-system';
+import { FlagMessage } from 'design-system';
import * as React from 'react';
import { useComponent } from '../../../app/components/componentContext/withComponentContext';
import { translate } from '../../../helpers/l10n';
import AnalysisWarningsModal from './AnalysisWarningsModal';
export interface HeaderMetaProps {
- component: Component;
className?: string;
+ component: Component;
}
-export function AnalysisStatus(props: HeaderMetaProps) {
+export function AnalysisStatus(props: Readonly<HeaderMetaProps>) {
const { className, component } = props;
const { currentTask, isPending, isInProgress } = useComponent();
const { data: warnings, isLoading } = useBranchWarningQuery(component);
const [modalIsVisible, setDisplayModal] = React.useState(false);
+
const openModal = React.useCallback(() => {
setDisplayModal(true);
}, [setDisplayModal]);
+
const closeModal = React.useCallback(() => {
setDisplayModal(false);
}, [setDisplayModal]);
if (isInProgress || isPending) {
return (
- <div data-test="analysis-status" className={classNames('sw-flex sw-items-center', className)}>
+ <div className={classNames('sw-flex sw-items-center', className)} data-test="analysis-status">
<Spinner />
+
<span className="sw-ml-1">
{isInProgress
? translate('project_navigation.analysis_status.in_progress')
if (currentTask?.status === TaskStatuses.Failed) {
return (
<>
- <FlagMessage data-test="analysis-status" variant="error" className={className}>
+ <FlagMessage className={className} data-test="analysis-status" variant="error">
<span>{translate('project_navigation.analysis_status.failed')}</span>
- <Link className="sw-ml-1" blurAfterClick onClick={openModal} preventDefault to={{}}>
+
+ {/* TODO: replace the Link below with a lighweight/discreet button component */}
+ {/* when it is available in Echoes */}
+ <Link
+ className="sw-ml-1"
+ onClick={openModal}
+ shouldBlurAfterClick
+ shouldPreventDefault
+ to={{}}
+ >
{translate('project_navigation.analysis_status.details_link')}
</Link>
</FlagMessage>
+
{modalIsVisible && (
<AnalysisErrorModal
component={component}
if (!isLoading && warnings && warnings.length > 0) {
return (
<>
- <FlagMessage data-test="analysis-status" variant="warning" className={className}>
+ <FlagMessage className={className} data-test="analysis-status" variant="warning">
<span>{translate('project_navigation.analysis_status.warnings')}</span>
- <Link className="sw-ml-1" blurAfterClick onClick={openModal} preventDefault to={{}}>
+
+ {/* TODO: replace the Link below with a lighweight/discreet button component */}
+ {/* when it is available in Echoes */}
+ <Link
+ className="sw-ml-1"
+ onClick={openModal}
+ shouldBlurAfterClick
+ shouldPreventDefault
+ to={{}}
+ >
{translate('project_navigation.analysis_status.details_link')}
</Link>
</FlagMessage>
+
{modalIsVisible && (
<AnalysisWarningsModal component={component} onClose={closeModal} warnings={warnings} />
)}
* 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 { Link, LinkStandalone } from '@sonarsource/echoes-react';
import classNames from 'classnames';
import {
Badge,
Note,
QualityGateIndicator,
SeparatorCircleIcon,
- StandoutLink,
SubnavigationFlowSeparator,
Tags,
themeBorder,
import Measure from '../../../../components/measure/Measure';
import { translate, translateWithParameters } from '../../../../helpers/l10n';
import { formatMeasure } from '../../../../helpers/measures';
+import { isDefined } from '../../../../helpers/types';
import { getProjectUrl } from '../../../../helpers/urls';
import { ComponentQualifier } from '../../../../types/component';
import { MetricKey, MetricType } from '../../../../types/metrics';
<>
<div className="sw-flex sw-justify-between sw-items-center ">
<div className="sw-flex sw-items-center ">
- {isFavorite !== undefined && (
+ {isDefined(isFavorite) && (
<Favorite
className="sw-mr-2"
component={key}
)}
<span className="it__project-card-name" title={name}>
- <StandoutLink to={getProjectUrl(key)}>{name}</StandoutLink>
+ <LinkStandalone to={getProjectUrl(key)}>{name}</LinkStandalone>
</span>
{qualifier === ComponentQualifier.Application && (
overlay={
<span>
{translate('qualifier.APP')}
- {measures.projects && (
+ {measures.projects !== '' && (
<span>
{' ‒ '}
{translateWithParameters('x_projects_', measures.projects)}
</span>
</Tooltip>
</div>
- {analysisDate && (
+
+ {isDefined(analysisDate) && analysisDate !== '' && (
<Tooltip overlay={qualityGateLabel}>
<span className="sw-flex sw-items-center">
<QualityGateIndicator
</Tooltip>
)}
</div>
+
<LightLabel as="div" className="sw-flex sw-items-center sw-mt-3">
- {analysisDate && (
+ {isDefined(analysisDate) && analysisDate !== '' && (
<DateTimeFormatter date={analysisDate}>
{(formattedAnalysisDate) => (
<span className="sw-body-sm-highlight" title={formattedAnalysisDate}>
)}
</DateTimeFormatter>
)}
+
{isNewCode
? measures[MetricKey.new_lines] != null && (
<>
<SeparatorCircleIcon className="sw-mx-1" />
+
<div>
<span className="sw-body-sm-highlight sw-mr-1" data-key={MetricKey.new_lines}>
<Measure
value={measures.new_lines}
/>
</span>
+
<span className="sw-body-sm">{translate('metric.new_lines.name')}</span>
</div>
</>
: measures[MetricKey.ncloc] != null && (
<>
<SeparatorCircleIcon className="sw-mx-1" />
+
<div>
<span className="sw-body-sm-highlight sw-mr-1" data-key={MetricKey.ncloc}>
<Measure
value={measures.ncloc}
/>
</span>
+
<span className="sw-body-sm">{translate('metric.ncloc.name')}</span>
</div>
+
<SeparatorCircleIcon className="sw-mx-1" />
+
<span className="sw-body-sm" data-key={MetricKey.ncloc_language_distribution}>
<ProjectCardLanguages distribution={measures.ncloc_language_distribution} />
</span>
</>
)}
+
{tags.length > 0 && (
<>
<SeparatorCircleIcon className="sw-mx-1" />
+
<Tags
className="sw-body-sm"
emptyText={translate('issue.no_tag')}
) {
const { analysisDate, key, leakPeriodDate, measures, qualifier, isScannable } = project;
- if (analysisDate && (!isNewCode || leakPeriodDate)) {
+ if (
+ isDefined(analysisDate) &&
+ analysisDate !== '' &&
+ (!isNewCode || (isDefined(leakPeriodDate) && leakPeriodDate !== ''))
+ ) {
return (
<ProjectCardMeasures
measures={measures}
? translate('projects.no_new_code_period', qualifier)
: translate('projects.not_analyzed', qualifier)}
</Note>
+
{qualifier !== ComponentQualifier.Application &&
- !analysisDate &&
+ (analysisDate === undefined || analysisDate === '') &&
isLoggedIn(currentUser) &&
isScannable && (
- <StandoutLink className="sw-ml-2 sw-body-sm-highlight" to={getProjectUrl(key)}>
+ <Link className="sw-ml-2 sw-body-sm-highlight" to={getProjectUrl(key)}>
{translate('projects.configure_analysis')}
- </StandoutLink>
+ </Link>
)}
</div>
);
}
-export default function ProjectCard(props: Props) {
+export default function ProjectCard(props: Readonly<Props>) {
const { currentUser, type, project } = props;
const isNewCode = type === 'leak';
data-key={project.key}
>
{renderFirstLine(project, props.handleFavorite, isNewCode)}
+
<SubnavigationFlowSeparator className="sw-my-3" />
+
{renderSecondLine(currentUser, project, isNewCode)}
</ProjectCardWrapper>
);
* 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 } from 'design-system';
+
+import { LinkStandalone } from '@sonarsource/echoes-react';
+import { FlagMessage, SubTitle } from 'design-system';
import * as React from 'react';
import { getQualityProfileExporterUrl } from '../../../api/quality-profiles';
import { translate } from '../../../helpers/l10n';
profile: Profile;
}
-export default function ProfileExporters({ exporters, profile }: Props) {
+export default function ProfileExporters({ exporters, profile }: Readonly<Props>) {
const exportersForLanguage = exporters.filter((e) => e.languages.includes(profile.language));
if (exportersForLanguage.length === 0) {
<div>
<SubTitle>{translate('quality_profiles.exporters')}</SubTitle>
</div>
+
<FlagMessage className="sw-mb-4" variant="warning">
{translate('quality_profiles.exporters.deprecated')}
</FlagMessage>
+
<ul className="sw-flex sw-flex-col sw-gap-2">
{exportersForLanguage.map((exporter) => (
<li data-key={exporter.key} key={exporter.key}>
- <Link isExternal showExternalIcon to={getQualityProfileExporterUrl(exporter, profile)}>
+ <LinkStandalone
+ hasExternalIcon
+ isExternal
+ to={getQualityProfileExporterUrl(exporter, profile)}
+ >
{exporter.name}
- </Link>
+ </LinkStandalone>
</li>
))}
</ul>
await user.click(ui.apiScopePet.get());
await user.click(ui.apiSidebarItem.getAt(0));
expect(screen.queryByText('about')).not.toBeInTheDocument();
- await user.click(ui.title.get());
- expect(await screen.findByText('about')).toBeInTheDocument();
});
function renderWebApiApp() {
* 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,
BasicSeparator,
Checkbox,
HelperHintIcon,
InputSearch,
- Link,
SubnavigationAccordion,
SubnavigationItem,
SubnavigationSubheading,
import RestMethodPill from './RestMethodPill';
interface Api {
- name: string;
- method: string;
info: OpenAPIV3.OperationObject<InternalExtension>;
+ method: string;
+ name: string;
}
+
interface Props {
- docInfo: OpenAPIV3.InfoObject;
apisList: Api[];
+ docInfo: OpenAPIV3.InfoObject;
}
const METHOD_ORDER: Dict<number> = {
.filter((api) => showInternal || !api.info['x-internal'])
.reduce<Record<string, Api[]>>((acc, api) => {
const subgroup = api.name.split('/')[1];
+
return {
...acc,
[subgroup]: [...(acc[subgroup] ?? []), api],
return (
<>
- <h1 className="sw-mb-2">
- <Link to="." className="sw-text-[unset] sw-border-none">
- {docInfo.title}
- </Link>
- </h1>
+ <h1 className="sw-mb-2">{docInfo.title}</h1>
<InputSearch
className="sw-w-full"
- placeholder={translate('api_documentation.v2.search')}
onChange={setSearch}
+ placeholder={translate('api_documentation.v2.search')}
value={search}
/>
<Checkbox checked={showInternal} onCheck={() => setShowInternal((prev) => !prev)}>
<span className="sw-ml-2">{translate('api_documentation.show_internal_v2')}</span>
</Checkbox>
+
<HelpTooltip
className="sw-ml-2"
overlay={translate('api_documentation.internal_tooltip_v2')}
{Object.entries(groupedList).map(([group, apis]) => (
<SubnavigationAccordion
+ className="sw-mt-2"
+ header={group}
+ id={`web-api-${group}`}
initExpanded={apis.some(
({ name, method }) => name === activeApi[0] && method === activeApi[1],
)}
- className="sw-mt-2"
- header={group}
key={group}
- id={`web-api-${group}`}
>
{sortBy(apis, (a) => [a.name, METHOD_ORDER[a.method]]).map(
({ method, name, info }, index, sorted) => {
const resourceName = getResourceFromName(name);
+
const previousResourceName =
index > 0 ? getResourceFromName(sorted[index - 1].name) : undefined;
+
const isNewResource = resourceName !== previousResourceName;
return (
<Fragment key={getApiEndpointKey(name, method)}>
{index > 0 && isNewResource && <BasicSeparator />}
+
{(index === 0 || isNewResource) && (
<SubnavigationSubheading>{resourceName}</SubnavigationSubheading>
)}
+
<SubnavigationItem
active={name === activeApi[0] && method === activeApi[1]}
onClick={handleApiClick}
<div className="sw-flex sw-gap-2 sw-w-full sw-justify-between">
<div className="sw-flex sw-gap-2">
<RestMethodPill method={method} />
+
<div>{info.summary ?? name}</div>
</div>
{translate('internal')}
</Badge>
)}
+
{info.deprecated && (
<Badge variant="deleted" className="sw-self-center">
{translate('deprecated')}
* 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 { LinkStandalone } from '@sonarsource/echoes-react';
import {
ClipboardIconButton,
DrilldownLink,
InteractiveIcon,
ItemButton,
ItemLink,
- Link,
MenuIcon,
Note,
PopupPlacement,
import { collapsedDirFromPath, fileFromPath } from '../../helpers/path';
import { omitNil } from '../../helpers/request';
import { getBaseUrl } from '../../helpers/system';
+import { isDefined } from '../../helpers/types';
import {
getBranchLikeUrl,
getCodeUrl,
getComponentIssuesUrl,
getComponentSecurityHotspotsUrl,
} from '../../helpers/urls';
-import { DEFAULT_ISSUES_QUERY } from '../shared/utils';
-
+import type { BranchLike } from '../../types/branch-like';
import { ComponentQualifier } from '../../types/component';
import { IssueType } from '../../types/issues';
import { MetricKey, MetricType } from '../../types/metrics';
-
-import type { BranchLike } from '../../types/branch-like';
import type { Measure, SourceViewerFile } from '../../types/types';
+import { DEFAULT_ISSUES_QUERY } from '../shared/utils';
import type { WorkspaceContextShape } from '../workspace/context';
interface Props {
>
<div className="sw-flex sw-flex-1 sw-flex-col sw-gap-1 sw-mr-5 sw-my-1">
<div className="sw-flex sw-gap-1 sw-items-center">
- <Link icon={<ProjectIcon />} to={getBranchLikeUrl(project, this.props.branchLike)}>
+ <LinkStandalone
+ iconLeft={<ProjectIcon className="sw-mr-2" />}
+ to={getBranchLikeUrl(project, this.props.branchLike)}
+ >
{projectName}
- </Link>
+ </LinkStandalone>
</div>
- <div className="sw-flex sw-gap-1 sw-items-center">
+ <div className="sw-flex sw-gap-2 sw-items-center">
<QualifierIcon qualifier={q} />
{collapsedDirFromPath(path)}
{fileFromPath(path)}
- <span className="sw-ml-1">
+ <span>
<ClipboardIconButton
aria-label={translate('component_viewer.copy_path_to_clipboard')}
copyValue={path}
{showMeasures && (
<div className="sw-flex sw-gap-6 sw-items-center">
- {measures[unitTestsOrLines] && (
+ {isDefined(measures[unitTestsOrLines]) && (
<div className="sw-flex sw-flex-col sw-gap-1">
<Note className="it__source-viewer-header-measure-label sw-body-lg">
{translate(`metric.${unitTestsOrLines}.name`)}
</div>
)}
- {measures.coverage !== undefined && (
+ {isDefined(measures.coverage) && (
<div className="sw-flex sw-flex-col sw-gap-1">
<Note className="it__source-viewer-header-measure-label sw-body-lg">
{translate('metric.coverage.name')}
</div>
)}
- {measures.duplicationDensity !== undefined && (
+ {isDefined(measures.duplicationDensity) && (
<div className="sw-flex sw-flex-col sw-gap-1">
<Note className="it__source-viewer-header-measure-label sw-body-lg">
{translate('duplications')}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { CodeSnippet, Link } from 'design-system';
+
+import { LinkStandalone } from '@sonarsource/echoes-react';
+import { CodeSnippet } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { translate } from '../../helpers/l10n';
export default class FormattingTipsWithLink extends React.PureComponent<Props> {
handleClick(evt: React.SyntheticEvent<HTMLAnchorElement>) {
evt.preventDefault();
+
window.open(
getFormattingHelpUrl(),
'Formatting',
render() {
return (
<div className={this.props.className}>
- <Link onClick={this.handleClick} to="#">
+ <LinkStandalone onClick={this.handleClick} to="#">
{translate('formatting.helplink')}
- </Link>
+ </LinkStandalone>
+
<p className="sw-mt-2">
<FormattedMessage
id="formatting.example.link"
example: (
<>
<br />
+
<CodeSnippet
isOneLine
noCopy
* 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 Echoes from '@sonarsource/echoes-react';
import * as React from 'react';
import { Link as ReactRouterDomLink, LinkProps as ReactRouterDomLinkProps } from 'react-router-dom';
import { isWebUri } from 'valid-url';
type OriginalLinkProps = ReactRouterDomLinkProps & React.RefAttributes<HTMLAnchorElement>;
+/** @deprecated Use {@link Echoes.LinkProps | LinkProps} from Echoes instead.
+ *
+ * Some of the props have changed or been renamed:
+ * - `blurAfterClick` is now `shouldBlurAfterClick`
+ * - ~`disabled`~ doesn't exist anymore, a disabled link is just a regular text
+ * - `forceExternal` is now `isExternal`
+ * - `icon` is now `iconLeft` and can only be used with LinkStandalone
+ * - `preventDefault` is now `shouldPreventDefault`
+ * - `showExternalIcon` is now `hasExternalIcon`
+ * - `stopPropagation` is now `shouldStopPropagation`
+ */
export interface LinkProps extends OriginalLinkProps {
size?: number;
}
);
}
+/** @deprecated Use either {@link Echoes.Link | Link} or {@link Echoes.LinkStandalone | LinkStandalone} from Echoes instead.
+ */
export default React.forwardRef(Link);