aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/design-system/src/components/Link.tsx4
-rw-r--r--server/sonar-web/design-system/src/components/Tags.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueHeaderSide.tsx2
-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/components/issue/components/IssueActionsBar.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueBadges.tsx75
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueMetaBar.tsx53
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTags.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx37
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueView.tsx30
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/SonarLintBadge.tsx84
-rw-r--r--server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx7
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties3
16 files changed, 198 insertions, 157 deletions
diff --git a/server/sonar-web/design-system/src/components/Link.tsx b/server/sonar-web/design-system/src/components/Link.tsx
index 47177ca0f54..241b0d3d9fb 100644
--- a/server/sonar-web/design-system/src/components/Link.tsx
+++ b/server/sonar-web/design-system/src/components/Link.tsx
@@ -180,6 +180,10 @@ export const HoverLink = styled(StyledBaseLink)`
--active: ${themeColor('linkTooltipActive')};
--borderActive: ${themeBorder('default', 'linkBorder')};
}
+
+ ${ExternalIcon} {
+ color: ${themeColor('linkDiscreet')};
+ }
`;
HoverLink.displayName = 'HoverLink';
diff --git a/server/sonar-web/design-system/src/components/Tags.tsx b/server/sonar-web/design-system/src/components/Tags.tsx
index 200415cfb65..fb8c71481e3 100644
--- a/server/sonar-web/design-system/src/components/Tags.tsx
+++ b/server/sonar-web/design-system/src/components/Tags.tsx
@@ -38,6 +38,7 @@ interface Props {
overlay?: React.ReactNode;
popupPlacement?: PopupPlacement;
tags: string[];
+ tagsClassName?: string;
tagsToDisplay?: number;
tooltip?: React.ComponentType<React.PropsWithChildren<{ overlay: React.ReactNode }>>;
}
@@ -46,6 +47,7 @@ export function Tags({
allowUpdate = false,
ariaTagsListLabel,
className,
+ tagsClassName,
emptyText,
menuId = '',
overlay,
@@ -55,7 +57,7 @@ export function Tags({
tooltip,
open,
onClose,
-}: Props) {
+}: Readonly<Props>) {
const displayedTags = tags.slice(0, tagsToDisplay);
const extraTags = tags.slice(tagsToDisplay);
const Tooltip = tooltip || React.Fragment;
@@ -95,7 +97,10 @@ export function Tags({
>
{({ a11yAttrs, onToggleClick, open }) => (
<WrapperButton
- className="sw-flex sw-items-center sw-gap-1 sw-p-0 sw-h-auto sw-rounded-0"
+ className={classNames(
+ 'sw-flex sw-items-center sw-gap-1 sw-p-0 sw-h-auto sw-rounded-0',
+ tagsClassName,
+ )}
onClick={onToggleClick}
{...a11yAttrs}
>
@@ -115,7 +120,6 @@ const TagLabel = styled.span`
color: ${themeContrast('tag')};
background: ${themeColor('tag')};
- ${tw`sw-body-sm`}
${tw`sw-box-border`}
${tw`sw-truncate`}
${tw`sw-rounded-1/2`}
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
index 4fc43afb01c..3b94be4800e 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
@@ -392,16 +392,16 @@ describe('issue app', () => {
).toHaveAttribute('aria-current', 'true');
});
- it('should show issue tags if applicable', async () => {
+ it('should show sonarlint badge if applicable', async () => {
const user = userEvent.setup();
issuesHandler.setIsAdmin(true);
renderIssueApp();
- // Select an issue with an advanced rule
+ // Select an issue with quick fix available
await user.click(await ui.issueItemAction7.find());
- await expect(
- screen.getByText('issue.quick_fix_available_with_sonarlint_no_link'),
- ).toHaveATooltipWithContent('issue.quick_fix_available_with_sonarlint');
+ await expect(screen.getByText('issue.quick_fix')).toHaveATooltipWithContent(
+ 'issue.quick_fix_available_with_sonarlint',
+ );
});
});
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx
index e15e23d3223..f97abb9a48d 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueHeader-it.tsx
@@ -84,7 +84,7 @@ it('renders correctly', async () => {
expect(byText('issue.effort').get()).toBeInTheDocument();
// SonarLint badge
- expect(byText('issue.quick_fix_available_with_sonarlint_no_link').get()).toBeInTheDocument();
+ expect(byText('issue.quick_fix').get()).toBeInTheDocument();
// Rule external engine
expect(byText('eslint').get()).toBeInTheDocument();
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx
index 4feb23909d2..be6ea4d8239 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderMeta.tsx
@@ -31,21 +31,7 @@ interface Props {
export default function IssueHeaderMeta({ issue }: Readonly<Props>) {
return (
- <Note className="sw-flex sw-items-center sw-gap-2 sw-text-xs">
- {!!issue.codeVariants?.length && (
- <>
- <div className="sw-flex sw-gap-1">
- <span>{translate('issue.code_variants')}</span>
- <Tooltip overlay={issue.codeVariants?.join(', ')}>
- <span className="sw-font-semibold">
- <LightLabel>{issue.codeVariants?.join(', ')}</LightLabel>
- </span>
- </Tooltip>
- </div>
- <SeparatorCircleIcon />
- </>
- )}
-
+ <Note className="sw-flex sw-flex-wrap sw-items-center sw-gap-2 sw-text-xs">
{typeof issue.line === 'number' && (
<>
<div className="sw-flex sw-gap-1">
@@ -76,6 +62,20 @@ export default function IssueHeaderMeta({ issue }: Readonly<Props>) {
</div>
<SeparatorCircleIcon />
+ {!!issue.codeVariants?.length && (
+ <>
+ <div className="sw-flex sw-gap-1">
+ <span>{translate('issue.code_variants')}</span>
+ <Tooltip overlay={issue.codeVariants?.join(', ')}>
+ <span className="sw-font-semibold">
+ <LightLabel>{issue.codeVariants?.join(', ')}</LightLabel>
+ </span>
+ </Tooltip>
+ </div>
+ <SeparatorCircleIcon />
+ </>
+ )}
+
<IssueType issue={issue} />
<SeparatorCircleIcon data-guiding-id="issue-4" />
<IssueSeverity issue={issue} />
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderSide.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderSide.tsx
index 1617fd00eaa..8358c0c1cde 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderSide.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssueHeaderSide.tsx
@@ -31,7 +31,7 @@ interface Props {
export default function IssueHeaderSide({ issue }: Readonly<Props>) {
return (
- <StyledSection className="sw-flex sw-flex-col sw-pl-4 sw-w-[200px]">
+ <StyledSection className="sw-flex sw-flex-col sw-pl-4 sw-max-w-[250px]">
<IssueHeaderInfo title={translate('issue.cct_attribute.label')} className="sw-mb-6">
<CleanCodeAttributePill
cleanCodeAttributeCategory={issue.cleanCodeAttributeCategory}
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 6dfa5c12702..0e1da3369ba 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
@@ -178,6 +178,7 @@ function renderFirstLine(
<>
<SeparatorCircleIcon className="sw-mx-1" />
<Tags
+ className="sw-body-sm"
emptyText={translate('issue.no_tag')}
ariaTagsListLabel={translate('issue.tags')}
tooltip={Tooltip}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
index e03b4723437..84968af99b9 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
@@ -23,10 +23,10 @@ import * as React from 'react';
import { IssueActions } from '../../../types/issues';
import { Issue } from '../../../types/types';
import IssueAssign from './IssueAssign';
-import { SonarLintBadge } from './IssueBadges';
import IssueCommentAction from './IssueCommentAction';
import IssueTags from './IssueTags';
import IssueTransition from './IssueTransition';
+import SonarLintBadge from './SonarLintBadge';
interface Props {
issue: Issue;
@@ -104,7 +104,7 @@ export default function IssueActionsBar(props: Readonly<Props>) {
{showSonarLintBadge && issue.quickFixAvailable && (
<li>
- <SonarLintBadge quickFixAvailable={issue.quickFixAvailable} />
+ <SonarLintBadge />
</li>
)}
</ul>
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueBadges.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueBadges.tsx
deleted file mode 100644
index fadbab92857..00000000000
--- a/server/sonar-web/src/main/js/components/issue/components/IssueBadges.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { FormattedMessage } from 'react-intl';
-import { translate } from '../../../helpers/l10n';
-import Link from '../../common/Link';
-import Tooltip from '../../controls/Tooltip';
-import SonarLintIcon from '../../icons/SonarLintIcon';
-
-export interface IssueBadgesProps {
- quickFixAvailable?: boolean;
-}
-
-export default function IssueBadges(props: IssueBadgesProps) {
- const { quickFixAvailable } = props;
-
- return (
- <div className="sw-flex">
- <SonarLintBadge quickFixAvailable={quickFixAvailable} />
- </div>
- );
-}
-
-export function SonarLintBadge({ quickFixAvailable }: { quickFixAvailable?: boolean }) {
- if (quickFixAvailable) {
- return (
- <Tooltip
- overlay={
- <FormattedMessage
- id="issue.quick_fix_available_with_sonarlint"
- defaultMessage={translate('issue.quick_fix_available_with_sonarlint')}
- values={{
- link: (
- <Link
- to="https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarqube-quick-fix"
- target="_blank"
- >
- SonarLint
- </Link>
- ),
- }}
- />
- }
- mouseLeaveDelay={0.5}
- >
- <div className="sw-flex sw-items-center">
- <SonarLintIcon
- className="it__issues-sonarlint-quick-fix"
- size={15}
- description={translate('issue.quick_fix_available_with_sonarlint_no_link')}
- />
- </div>
- </Tooltip>
- );
- }
-
- return null;
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMetaBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueMetaBar.tsx
index d25f0d6cf5d..c00d796976a 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueMetaBar.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueMetaBar.tsx
@@ -19,6 +19,7 @@
*/
import styled from '@emotion/styled';
+import classNames from 'classnames';
import { Badge, CommentIcon, SeparatorCircleIcon, themeColor } from 'design-system';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
@@ -27,14 +28,16 @@ import { Issue } from '../../../types/types';
import Tooltip from '../../controls/Tooltip';
import DateFromNow from '../../intl/DateFromNow';
import { WorkspaceContext } from '../../workspace/context';
-import IssueBadges from './IssueBadges';
+import IssueSeverity from './IssueSeverity';
+import IssueType from './IssueType';
+import SonarLintBadge from './SonarLintBadge';
interface Props {
issue: Issue;
showLine?: boolean;
}
-export default function IssueMetaBar(props: Props) {
+export default function IssueMetaBar(props: Readonly<Props>) {
const { issue, showLine } = props;
const { externalRulesRepoNames } = React.useContext(WorkspaceContext);
@@ -46,22 +49,32 @@ export default function IssueMetaBar(props: Props) {
const hasComments = !!issue.comments?.length;
const issueMetaListItemClassNames =
- 'sw-body-sm sw-overflow-hidden sw-whitespace-nowrap sw-max-w-abs-150';
+ 'sw-body-xs sw-overflow-hidden sw-whitespace-nowrap sw-max-w-abs-150';
return (
- <ul className="sw-flex sw-items-center sw-gap-2 sw-body-sm">
- <li className={issueMetaListItemClassNames}>
- <IssueBadges quickFixAvailable={issue.quickFixAvailable} />
- </li>
+ <ul className="sw-flex sw-items-center sw-gap-2 sw-body-xs">
+ {issue.quickFixAvailable && (
+ <>
+ <li className={issueMetaListItemClassNames}>
+ <SonarLintBadge compact />
+ </li>
+ <SeparatorCircleIcon aria-hidden as="li" />
+ </>
+ )}
{ruleEngine && (
- <li className={issueMetaListItemClassNames}>
- <Tooltip overlay={translateWithParameters('issue.from_external_rule_engine', ruleEngine)}>
- <span>
- <Badge>{ruleEngine}</Badge>
- </span>
- </Tooltip>
- </li>
+ <>
+ <li className={issueMetaListItemClassNames}>
+ <Tooltip
+ overlay={translateWithParameters('issue.from_external_rule_engine', ruleEngine)}
+ >
+ <span>
+ <Badge>{ruleEngine}</Badge>
+ </span>
+ </Tooltip>
+ </li>
+ <SeparatorCircleIcon aria-hidden as="li" />
+ </>
)}
{!!issue.codeVariants?.length && (
@@ -81,7 +94,9 @@ export default function IssueMetaBar(props: Props) {
{hasComments && (
<>
- <IssueMetaListItem className={issueMetaListItemClassNames}>
+ <IssueMetaListItem
+ className={classNames(issueMetaListItemClassNames, 'sw-flex sw-gap-1')}
+ >
<CommentIcon aria-label={translate('issue.comment.formlink')} />
{issue.comments?.length}
</IssueMetaListItem>
@@ -115,6 +130,14 @@ export default function IssueMetaBar(props: Props) {
<IssueMetaListItem className={issueMetaListItemClassNames}>
<DateFromNow date={issue.creationDate} />
</IssueMetaListItem>
+
+ <SeparatorCircleIcon aria-hidden as="li" />
+
+ <IssueType issue={issue} />
+
+ <SeparatorCircleIcon data-guiding-id="issue-4" aria-hidden as="li" />
+
+ <IssueSeverity issue={issue} />
</ul>
);
}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTags.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTags.tsx
index fd5c14f1b8b..1e60ef1cfac 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueTags.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueTags.tsx
@@ -67,7 +67,8 @@ export class IssueTags extends React.PureComponent<Props> {
<Tags
allowUpdate={this.props.canSetTags && !component?.needIssueSync}
ariaTagsListLabel={translate('issue.tags')}
- className="js-issue-edit-tags"
+ className="js-issue-edit-tags sw-body-xs"
+ tagsClassName="sw-body-xs"
emptyText={translate('issue.no_tag')}
menuId="issue-tags-menu"
onClose={this.handleClose}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
index bbfd98d5cc6..7f630c1c473 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
@@ -20,49 +20,30 @@
import * as React from 'react';
import { BranchLike } from '../../../types/branch-like';
-import { IssueActions } from '../../../types/issues';
import { Issue } from '../../../types/types';
import { CleanCodeAttributePill } from '../../shared/CleanCodeAttributePill';
import IssueMessage from './IssueMessage';
-import IssueTags from './IssueTags';
export interface IssueTitleBarProps {
- currentPopup?: string;
branchLike?: BranchLike;
displayWhyIsThisAnIssue?: boolean;
issue: Issue;
- onChange: (issue: Issue) => void;
- togglePopup: (popup: string, show?: boolean) => void;
}
-export default function IssueTitleBar(props: IssueTitleBarProps) {
- const { issue, displayWhyIsThisAnIssue, currentPopup } = props;
- const canSetTags = issue.actions.includes(IssueActions.SetTags);
+export default function IssueTitleBar(props: Readonly<IssueTitleBarProps>) {
+ const { issue, displayWhyIsThisAnIssue, branchLike } = props;
return (
- <div className="sw-flex sw-items-end">
- <div className="sw-w-full sw-flex sw-flex-col">
- <CleanCodeAttributePill
- className="sw-mb-2"
- cleanCodeAttributeCategory={issue.cleanCodeAttributeCategory}
- />
- <div className="sw-w-fit">
- <IssueMessage
- issue={issue}
- branchLike={props.branchLike}
- displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
- />
- </div>
- </div>
- <div className="js-issue-tags sw-body-sm sw-grow-0 sw-whitespace-nowrap">
- <IssueTags
- canSetTags={canSetTags}
+ <div className="sw-mt-1 sw-flex sw-items-start sw-justify-between sw-gap-8">
+ <div className="sw-w-fit">
+ <IssueMessage
issue={issue}
- onChange={props.onChange}
- togglePopup={props.togglePopup}
- open={currentPopup === 'edit-tags' && canSetTags}
+ branchLike={branchLike}
+ displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
/>
</div>
+
+ <CleanCodeAttributePill cleanCodeAttributeCategory={issue.cleanCodeAttributeCategory} />
</div>
);
}
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueView.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueView.tsx
index 7d7f6364ca4..7ad428f169f 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueView.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueView.tsx
@@ -20,15 +20,18 @@
import styled from '@emotion/styled';
import classNames from 'classnames';
-import { Checkbox, themeBorder } from 'design-system';
+import { BasicSeparator, Checkbox, themeBorder } from 'design-system';
import * as React from 'react';
import { deleteIssueComment, editIssueComment } from '../../../api/issues';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { BranchLike } from '../../../types/branch-like';
+import { IssueActions } from '../../../types/issues';
import { Issue } from '../../../types/types';
+import SoftwareImpactPillList from '../../shared/SoftwareImpactPillList';
import { updateIssue } from '../actions';
import IssueActionsBar from './IssueActionsBar';
import IssueMetaBar from './IssueMetaBar';
+import IssueTags from './IssueTags';
import IssueTitleBar from './IssueTitleBar';
interface Props {
@@ -80,6 +83,7 @@ export default class IssueView extends React.PureComponent<Props> {
const { issue, branchLike, checked, currentPopup, displayWhyIsThisAnIssue } = this.props;
const hasCheckbox = this.props.onCheck != null;
+ const canSetTags = issue.actions.includes(IssueActions.SetTags);
const issueClass = classNames('it__issue-item sw-p-3 sw-mb-4 sw-rounded-1 sw-bg-white', {
selected: this.props.selected,
@@ -93,9 +97,9 @@ export default class IssueView extends React.PureComponent<Props> {
aria-label={issue.message}
ref={(node) => (this.nodeRef = node)}
>
- <div className="sw-flex sw-gap-4">
+ <div className="sw-flex sw-gap-3">
{hasCheckbox && (
- <span className="sw-mt-7 sw-self-start">
+ <span className="sw-mt-1/2 sw-ml-1 sw-self-start">
<Checkbox
checked={checked ?? false}
onCheck={this.handleCheck}
@@ -105,16 +109,28 @@ export default class IssueView extends React.PureComponent<Props> {
</span>
)}
- <div className="sw-flex sw-flex-col sw-grow sw-gap-4">
+ <div className="sw-flex sw-flex-col sw-grow sw-gap-3">
<IssueTitleBar
- currentPopup={currentPopup}
branchLike={branchLike}
displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
issue={issue}
- onChange={this.props.onChange}
- togglePopup={this.props.togglePopup}
/>
+ <div className="sw-mt-1 sw-flex sw-items-start sw-justify-between">
+ <SoftwareImpactPillList data-guiding-id="issue-2" softwareImpacts={issue.impacts} />
+ <div className="sw-grow-0 sw-whitespace-nowrap">
+ <IssueTags
+ issue={issue}
+ onChange={this.props.onChange}
+ togglePopup={this.props.togglePopup}
+ canSetTags={canSetTags}
+ open={currentPopup === 'edit-tags' && canSetTags}
+ />
+ </div>
+ </div>
+
+ <BasicSeparator />
+
<div className="sw-flex sw-gap-2 sw-flex-wrap sw-items-center sw-justify-between">
<IssueActionsBar
currentPopup={currentPopup}
diff --git a/server/sonar-web/src/main/js/components/issue/components/SonarLintBadge.tsx b/server/sonar-web/src/main/js/components/issue/components/SonarLintBadge.tsx
new file mode 100644
index 00000000000..2e097d6da0e
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/issue/components/SonarLintBadge.tsx
@@ -0,0 +1,84 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { HoverLink } from 'design-system';
+import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { translate } from '../../../helpers/l10n';
+import Link from '../../common/Link';
+import Tooltip from '../../controls/Tooltip';
+import SonarLintIcon from '../../icons/SonarLintIcon';
+
+const SONARLINT_URL =
+ 'https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=sonarqube-quick-fix';
+
+interface Props {
+ compact?: boolean;
+}
+
+export default function SonarLintBadge({ compact }: Readonly<Props>) {
+ return compact ? <SonarLintBadgeCompact /> : <SonarLintBadgeFull />;
+}
+
+function SonarLintBadgeFull() {
+ return (
+ <Tooltip
+ overlay={translate('issue.quick_fix_available_with_sonarlint_no_link')}
+ mouseLeaveDelay={0.5}
+ >
+ <HoverLink to={SONARLINT_URL} className="sw-flex sw-items-center" isExternal showExternalIcon>
+ <SonarLintIcon
+ className="it__issues-sonarlint-quick-fix"
+ size={20}
+ description={translate('issue.quick_fix_available_with_sonarlint_no_link')}
+ />
+ <span className="sw-ml-1">{translate('issue.quick_fix')}</span>
+ </HoverLink>
+ </Tooltip>
+ );
+}
+
+function SonarLintBadgeCompact() {
+ return (
+ <Tooltip
+ overlay={
+ <FormattedMessage
+ id="issue.quick_fix_available_with_sonarlint"
+ defaultMessage={translate('issue.quick_fix_available_with_sonarlint')}
+ values={{
+ link: (
+ <Link to={SONARLINT_URL} target="_blank">
+ SonarLint
+ </Link>
+ ),
+ }}
+ />
+ }
+ mouseLeaveDelay={0.5}
+ >
+ <div className="sw-flex sw-items-center">
+ <SonarLintIcon
+ className="it__issues-sonarlint-quick-fix"
+ size={15}
+ description={translate('issue.quick_fix_available_with_sonarlint_no_link')}
+ />
+ </div>
+ </Tooltip>
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx b/server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx
index b7103e4faf5..de84d2d5437 100644
--- a/server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx
+++ b/server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx
@@ -28,10 +28,10 @@ interface SoftwareImpact {
severity: SoftwareImpactSeverity;
}
-interface SoftwareImpactPillListProps
- extends Pick<Parameters<typeof SoftwareImpactPill>[0], 'type'> {
+interface SoftwareImpactPillListProps extends React.HTMLAttributes<HTMLUListElement> {
softwareImpacts: Array<SoftwareImpact>;
className?: string;
+ type?: Parameters<typeof SoftwareImpactPill>[0]['type'];
}
const severityMap = {
@@ -44,6 +44,7 @@ export default function SoftwareImpactPillList({
softwareImpacts,
type,
className,
+ ...props
}: Readonly<SoftwareImpactPillListProps>) {
const getQualityLabel = (quality: SoftwareQuality) => translate('software_quality', quality);
const sortingFn = (a: SoftwareImpact, b: SoftwareImpact) => {
@@ -54,7 +55,7 @@ export default function SoftwareImpactPillList({
};
return (
- <ul className={classNames('sw-flex sw-gap-2', className)}>
+ <ul className={classNames('sw-flex sw-gap-2', className)} {...props}>
{softwareImpacts
.slice()
.sort(sortingFn)
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 0cf4fc63f0b..d47606dfb64 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -900,6 +900,7 @@ issue.assign.assigned_to_x_click_to_change=Assigned to {0}, click to change
issue.assign.unassigned_click_to_assign=Unassigned, click to assign issue
issue.assign.formlink=Assign
issue.assign.to_me=to me
+issue.quick_fix=Quick fix
issue.quick_fix_available_with_sonarlint=Quick fix available in {link}
issue.quick_fix_available_with_sonarlint_no_link=Quick fix available in SonarLint
issue.comment.add_comment=Add Comment
@@ -1082,7 +1083,7 @@ issue.unresolved.description=Unresolved issues have not been addressed in any wa
issue.action.permalink=Get permalink
issue.line_affected=Line affected:
issue.introduced=Introduced:
-issue.code_variants=Code variant:
+issue.code_variants=Variants:
issue.rule_status=Rule status
issue.effort=Effort:
issue.x_effort={0} effort