@@ -180,6 +180,10 @@ export const HoverLink = styled(StyledBaseLink)` | |||
--active: ${themeColor('linkTooltipActive')}; | |||
--borderActive: ${themeBorder('default', 'linkBorder')}; | |||
} | |||
${ExternalIcon} { | |||
color: ${themeColor('linkDiscreet')}; | |||
} | |||
`; | |||
HoverLink.displayName = 'HoverLink'; | |||
@@ -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`} |
@@ -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', | |||
); | |||
}); | |||
}); |
@@ -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(); |
@@ -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} /> |
@@ -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} |
@@ -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} |
@@ -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> |
@@ -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; | |||
} |
@@ -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> | |||
); | |||
} |
@@ -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} |
@@ -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> | |||
); | |||
} |
@@ -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} |
@@ -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> | |||
); | |||
} |
@@ -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) |
@@ -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 |