@@ -99,20 +99,20 @@ describe('Rules app list', () => { | |||
renderCodingRulesApp(mockCurrentUser()); | |||
await ui.appLoaded(); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(11); | |||
expect(ui.getAllRuleListItems()).toHaveLength(11); | |||
// Filter by language facet | |||
await act(async () => { | |||
await user.type(ui.facetSearchInput('search.search_for_languages').get(), 'ja'); | |||
await user.click(ui.facetItem('JavaScript').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(2); | |||
expect(ui.getAllRuleListItems()).toHaveLength(2); | |||
// Clear language facet and search box, and filter by python language | |||
await act(async () => { | |||
await user.clear(ui.facetSearchInput('search.search_for_languages').get()); | |||
await user.click(ui.facetItem('Python').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(6); | |||
expect(ui.getAllRuleListItems()).toHaveLength(6); | |||
// Filter by date facet | |||
await act(async () => { | |||
@@ -134,40 +134,40 @@ describe('Rules app list', () => { | |||
await user.click(screen.getByText('1', { selector: 'button' })); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1); | |||
expect(ui.getAllRuleListItems()).toHaveLength(1); | |||
// Clear filters | |||
await act(async () => { | |||
await user.click(ui.clearAllFiltersButton.get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(11); | |||
expect(ui.getAllRuleListItems()).toHaveLength(11); | |||
// Filter by repository | |||
await act(async () => { | |||
await user.click(ui.repositoriesFacet.get()); | |||
await user.click(ui.facetItem('Repository 1').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(2); | |||
expect(ui.getAllRuleListItems()).toHaveLength(2); | |||
// Search second repository | |||
await act(async () => { | |||
await user.type(ui.facetSearchInput('search.search_for_repositories').get(), 'y 2'); | |||
await user.click(ui.facetItem('Repository 2').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1); | |||
expect(ui.getAllRuleListItems()).toHaveLength(1); | |||
// Clear filters | |||
await act(async () => { | |||
await user.click(ui.clearAllFiltersButton.get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(11); | |||
expect(ui.getAllRuleListItems()).toHaveLength(11); | |||
// Filter by quality profile | |||
await act(async () => { | |||
await user.click(ui.qpFacet.get()); | |||
await user.click(ui.facetItem('QP Foo Java').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1); | |||
expect(ui.getAllRuleListItems()).toHaveLength(1); | |||
// Filter by tag | |||
await act(async () => { | |||
@@ -175,7 +175,7 @@ describe('Rules app list', () => { | |||
await user.click(ui.tagsFacet.get()); | |||
await user.click(ui.facetItem('awesome').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(5); | |||
expect(ui.getAllRuleListItems()).toHaveLength(5); | |||
// Search by tag | |||
await act(async () => { | |||
@@ -187,27 +187,27 @@ describe('Rules app list', () => { | |||
await act(async () => { | |||
await user.click(ui.clearAllFiltersButton.get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(11); | |||
expect(ui.getAllRuleListItems()).toHaveLength(11); | |||
// Filter by clean code category | |||
await act(async () => { | |||
await user.click(ui.facetItem('issue.clean_code_attribute_category.ADAPTABLE').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(10); | |||
expect(ui.getAllRuleListItems()).toHaveLength(10); | |||
// Filter by software quality | |||
await act(async () => { | |||
await user.click(ui.facetItem('software_quality.MAINTAINABILITY').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(10); | |||
expect(ui.getAllRuleListItems()).toHaveLength(10); | |||
// Filter by severity | |||
await act(async () => { | |||
await user.click(ui.severetiesFacet.get()); | |||
await user.click(ui.facetItem(/severity.HIGH/).get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(9); | |||
expect(ui.getAllRuleListItems()).toHaveLength(9); | |||
}); | |||
it('filter by standards', async () => { | |||
@@ -215,31 +215,31 @@ describe('Rules app list', () => { | |||
renderCodingRulesApp(mockCurrentUser()); | |||
await ui.appLoaded(); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(11); | |||
expect(ui.getAllRuleListItems()).toHaveLength(11); | |||
await act(async () => { | |||
await user.click(ui.standardsFacet.get()); | |||
await user.click(ui.facetItem('Buffer Overflow').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(6); | |||
expect(ui.getAllRuleListItems()).toHaveLength(6); | |||
await act(async () => { | |||
await user.click(ui.standardsOwasp2021Top10Facet.get()); | |||
await user.click(ui.facetItem('A2 - Cryptographic Failures').get()); | |||
await user.click(ui.standardsOwasp2021Top10Facet.get()); // Close facet | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(5); | |||
expect(ui.getAllRuleListItems()).toHaveLength(5); | |||
await act(async () => { | |||
await user.click(ui.standardsOwasp2017Top10Facet.get()); | |||
await user.click(ui.facetItem('A3 - Sensitive Data Exposure').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(4); | |||
expect(ui.getAllRuleListItems()).toHaveLength(4); | |||
await act(async () => { | |||
await user.click(ui.standardsCweFacet.get()); | |||
await user.click(ui.facetItem('CWE-102 - Struts: Duplicate Validation Forms').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(3); | |||
expect(ui.getAllRuleListItems()).toHaveLength(3); | |||
await act(async () => { | |||
await user.type(ui.facetSearchInput('search.search_for_cwe').get(), 'Certificate'); | |||
@@ -247,12 +247,12 @@ describe('Rules app list', () => { | |||
ui.facetItem('CWE-297 - Improper Validation of Certificate with Host Mismatch').get(), | |||
); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(2); | |||
expect(ui.getAllRuleListItems()).toHaveLength(2); | |||
await act(async () => { | |||
await user.click(ui.facetClear('clear-issues.facet.standards').get()); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(11); | |||
expect(ui.getAllRuleListItems()).toHaveLength(11); | |||
}); | |||
it('filters by search', async () => { | |||
@@ -263,13 +263,13 @@ describe('Rules app list', () => { | |||
await act(async () => { | |||
await user.type(ui.searchInput.get(), 'Python'); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(4); | |||
expect(ui.getAllRuleListItems()).toHaveLength(4); | |||
await act(async () => { | |||
await user.clear(ui.searchInput.get()); | |||
await user.type(ui.searchInput.get(), 'Hot hotspot'); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1); | |||
expect(ui.getAllRuleListItems()).toHaveLength(1); | |||
}); | |||
}); | |||
@@ -352,13 +352,13 @@ describe('Rules app list', () => { | |||
}); | |||
// Only 4 rules are activated in selected QP | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(4); | |||
expect(ui.getAllRuleListItems()).toHaveLength(4); | |||
// Switch to inactive rules | |||
await act(async () => { | |||
await user.click(ui.qpInactiveRadio.get(ui.facetItem('QP Bar Python').get())); | |||
}); | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(2); | |||
expect(ui.getAllRuleListItems()).toHaveLength(2); | |||
expect(ui.activateButton.getAll()).toHaveLength(2); | |||
// Activate Rule for qp | |||
@@ -385,7 +385,7 @@ describe('Rules app list', () => { | |||
await ui.appLoaded(); | |||
// Only rule 9 is shown (inherited, activated) | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1); | |||
expect(ui.getAllRuleListItems()).toHaveLength(1); | |||
expect(ui.deactivateButton.get()).toBeDisabled(); | |||
}); | |||
@@ -690,7 +690,7 @@ describe('Rule app details', () => { | |||
await user.click(ui.facetItem('coding_rules.filters.template.is_template').get()); | |||
}); | |||
// Shows only one template rule | |||
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1); | |||
expect(ui.getAllRuleListItems()).toHaveLength(1); | |||
// Show template rule details | |||
await act(async () => { |
@@ -39,7 +39,7 @@ import Tooltip from '../../../components/controls/Tooltip'; | |||
import IssueSeverityIcon from '../../../components/icon-mappers/IssueSeverityIcon'; | |||
import IssueTypeIcon from '../../../components/icon-mappers/IssueTypeIcon'; | |||
import { CleanCodeAttributePill } from '../../../components/shared/CleanCodeAttributePill'; | |||
import SoftwareImpactPill from '../../../components/shared/SoftwareImpactPill'; | |||
import SoftwareImpactPillList from '../../../components/shared/SoftwareImpactPillList'; | |||
import TagsList from '../../../components/tags/TagsList'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { getPathUrlAsString, getRuleUrl } from '../../../helpers/urls'; | |||
@@ -290,17 +290,11 @@ export default class RuleDetailsMeta extends React.PureComponent<Props> { | |||
{!!ruleDetails.impacts.length && ( | |||
<div className="sw-flex sw-items-center sw-flex-1"> | |||
<Note>{translate('issue.software_qualities.label')}</Note> | |||
<ul className="sw-flex sw-gap-2 sw-ml-1"> | |||
{ruleDetails.impacts.map(({ severity, softwareQuality }) => ( | |||
<li key={softwareQuality}> | |||
<SoftwareImpactPill | |||
severity={severity} | |||
quality={softwareQuality} | |||
type="rule" | |||
/> | |||
</li> | |||
))} | |||
</ul> | |||
<SoftwareImpactPillList | |||
className="sw-ml-1" | |||
softwareImpacts={ruleDetails.impacts} | |||
type="rule" | |||
/> | |||
</div> | |||
)} | |||
</div> |
@@ -34,7 +34,7 @@ import DocumentationTooltip from '../../../components/common/DocumentationToolti | |||
import ConfirmButton from '../../../components/controls/ConfirmButton'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { CleanCodeAttributePill } from '../../../components/shared/CleanCodeAttributePill'; | |||
import SoftwareImpactPill from '../../../components/shared/SoftwareImpactPill'; | |||
import SoftwareImpactPillList from '../../../components/shared/SoftwareImpactPillList'; | |||
import TypeHelper from '../../../components/shared/TypeHelper'; | |||
import TagsList from '../../../components/tags/TagsList'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
@@ -245,15 +245,11 @@ export default class RuleListItem extends React.PureComponent<Props> { | |||
</div> | |||
<div className="sw-flex sw-items-center sw-ml-2"> | |||
<span>{rule.langName}</span> | |||
{rule.impacts.map(({ severity, softwareQuality }) => ( | |||
<SoftwareImpactPill | |||
className="sw-ml-3" | |||
key={softwareQuality} | |||
severity={severity} | |||
quality={softwareQuality} | |||
type="rule" | |||
/> | |||
))} | |||
<SoftwareImpactPillList | |||
className="sw-ml-3 sw-gap-3" | |||
softwareImpacts={rule.impacts} | |||
type="rule" | |||
/> | |||
<DocumentationTooltip | |||
content={ |
@@ -39,7 +39,11 @@ const selectors = { | |||
// List | |||
rulesList: byRole('list', { name: 'list_of_rules' }), | |||
ruleListItemLink: (name: string) => byRole('link', { name }), | |||
ruleListItem: byRole('listitem'), | |||
getAllRuleListItems: () => | |||
byRole('list', { name: 'list_of_rules' }) | |||
.byRole('listitem') | |||
.getAll() | |||
.filter((item) => !!item.getAttribute('data-rule')), | |||
currentListItem: byRole('listitem', { current: true }), | |||
// Filters |
@@ -32,7 +32,7 @@ import { setIssueAssignee } from '../../../api/issues'; | |||
import { updateIssue } from '../../../components/issue/actions'; | |||
import IssueActionsBar from '../../../components/issue/components/IssueActionsBar'; | |||
import { CleanCodeAttributePill } from '../../../components/shared/CleanCodeAttributePill'; | |||
import SoftwareImpactPill from '../../../components/shared/SoftwareImpactPill'; | |||
import SoftwareImpactPillList from '../../../components/shared/SoftwareImpactPillList'; | |||
import { WorkspaceContext } from '../../../components/workspace/context'; | |||
import { getBranchLikeQuery } from '../../../helpers/branch-like'; | |||
import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers'; | |||
@@ -191,13 +191,9 @@ export default class IssueHeader extends React.PureComponent<Props, State> { | |||
</div> | |||
<div className="sw-flex sw-items-center"> | |||
<Note>{translate('issue.software_qualities.label')}</Note> | |||
<ul className="sw-ml-1 sw-flex sw-gap-2" data-guiding-id="issue-2"> | |||
{issue.impacts.map(({ severity, softwareQuality }) => ( | |||
<li key={softwareQuality}> | |||
<SoftwareImpactPill severity={severity} quality={softwareQuality} /> | |||
</li> | |||
))} | |||
</ul> | |||
<div data-guiding-id="issue-2"> | |||
<SoftwareImpactPillList className="sw-ml-1" softwareImpacts={issue.impacts} /> | |||
</div> | |||
</div> | |||
<BasicSeparator className="sw-my-3" /> | |||
<IssueActionsBar |
@@ -35,7 +35,7 @@ import * as React from 'react'; | |||
import { FormattedMessage, useIntl } from 'react-intl'; | |||
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; | |||
import { CleanCodeAttributePill } from '../../../components/shared/CleanCodeAttributePill'; | |||
import SoftwareImpactPill from '../../../components/shared/SoftwareImpactPill'; | |||
import SoftwareImpactPillList from '../../../components/shared/SoftwareImpactPillList'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { ProfileChangelogEvent } from '../types'; | |||
@@ -139,13 +139,7 @@ export default function Changelog(props: Props) { | |||
cleanCodeAttributeCategory={event.cleanCodeAttributeCategory} | |||
/> | |||
)} | |||
{event.impacts?.map((impact) => ( | |||
<SoftwareImpactPill | |||
key={impact.softwareQuality} | |||
quality={impact.softwareQuality} | |||
severity={impact.severity} | |||
/> | |||
))} | |||
<SoftwareImpactPillList softwareImpacts={event.impacts} /> | |||
</div> | |||
</CellComponent> | |||
@@ -92,7 +92,7 @@ it('should see the changelog', async () => { | |||
'April 23, 2019', | |||
'System', | |||
'quality_profiles.changelog.DEACTIVATED', | |||
'Rule 0issue.clean_code_attribute_category.RESPONSIBLE.title_shortsoftware_quality.MAINTAINABILITYsoftware_quality.SECURITY', | |||
'Rule 0issue.clean_code_attribute_category.RESPONSIBLE.title_shortsoftware_quality.SECURITYsoftware_quality.MAINTAINABILITY', | |||
[/quality_profiles.severity_set_to severity.MAJOR/], | |||
); | |||
ui.checkRow( | |||
@@ -100,7 +100,7 @@ it('should see the changelog', async () => { | |||
'', | |||
'', | |||
'', | |||
'Rule 1issue.clean_code_attribute_category.RESPONSIBLE.title_shortsoftware_quality.MAINTAINABILITYsoftware_quality.SECURITY', | |||
'Rule 1issue.clean_code_attribute_category.RESPONSIBLE.title_shortsoftware_quality.SECURITYsoftware_quality.MAINTAINABILITY', | |||
[ | |||
/quality_profiles.severity_set_to severity.CRITICAL/, | |||
/quality_profiles.changelog.cca_and_category_changed.*COMPLETE.*INTENTIONAL.*LAWFUL.*RESPONSIBLE/, |
@@ -24,7 +24,7 @@ import { useIntl } from 'react-intl'; | |||
import { CompareResponse, Profile, RuleCompare } from '../../../api/quality-profiles'; | |||
import IssueSeverityIcon from '../../../components/icon-mappers/IssueSeverityIcon'; | |||
import { CleanCodeAttributePill } from '../../../components/shared/CleanCodeAttributePill'; | |||
import SoftwareImpactPill from '../../../components/shared/SoftwareImpactPill'; | |||
import SoftwareImpactPillList from '../../../components/shared/SoftwareImpactPillList'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { IssueSeverity } from '../../../types/issues'; | |||
import { Dict } from '../../../types/types'; | |||
@@ -240,11 +240,11 @@ function RuleCell({ rule, severity }: Readonly<{ rule: RuleCompare; severity?: s | |||
/> | |||
</li> | |||
)} | |||
{rule.impacts.map(({ severity, softwareQuality }) => ( | |||
<li key={softwareQuality} className="sw-ml-2"> | |||
<SoftwareImpactPill type="rule" quality={softwareQuality} severity={severity} /> | |||
{rule.impacts.length > 0 && ( | |||
<li> | |||
<SoftwareImpactPillList className="sw-ml-2" softwareImpacts={rule.impacts} /> | |||
</li> | |||
))} | |||
)} | |||
</ul> | |||
)} | |||
</div> |
@@ -22,7 +22,7 @@ import * as React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { IssueActions, IssueResolution, IssueType as IssueTypeEnum } from '../../../types/issues'; | |||
import { Issue } from '../../../types/types'; | |||
import SoftwareImpactPill from '../../shared/SoftwareImpactPill'; | |||
import SoftwareImpactPillList from '../../shared/SoftwareImpactPillList'; | |||
import IssueAssign from './IssueAssign'; | |||
import { SonarLintBadge } from './IssueBadges'; | |||
import IssueCommentAction from './IssueCommentAction'; | |||
@@ -109,14 +109,8 @@ export default function IssueActionsBar(props: Props) { | |||
</li> | |||
{showIssueImpact && ( | |||
<li className="sw-flex sw-gap-3" data-guiding-id="issue-2"> | |||
{issue.impacts.map(({ severity, softwareQuality }) => ( | |||
<SoftwareImpactPill | |||
key={softwareQuality} | |||
severity={severity} | |||
quality={softwareQuality} | |||
/> | |||
))} | |||
<li data-guiding-id="issue-2"> | |||
<SoftwareImpactPillList className="sw-gap-3" softwareImpacts={issue.impacts} /> | |||
</li> | |||
)} | |||
@@ -22,7 +22,7 @@ import { Pill } from 'design-system'; | |||
import React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { SoftwareImpactSeverity, SoftwareQuality } from '../../types/clean-code-taxonomy'; | |||
import { SoftwareImpactSeverity } from '../../types/clean-code-taxonomy'; | |||
import DocumentationTooltip from '../common/DocumentationTooltip'; | |||
import SoftwareImpactSeverityIcon from '../icons/SoftwareImpactSeverityIcon'; | |||
@@ -30,7 +30,7 @@ export interface Props { | |||
className?: string; | |||
severity: SoftwareImpactSeverity; | |||
type?: 'issue' | 'rule'; | |||
quality: SoftwareQuality; | |||
quality: string; | |||
} | |||
export default function SoftwareImpactPill(props: Props) { | |||
@@ -50,7 +50,7 @@ export default function SoftwareImpactPill(props: Props) { | |||
defaultMessage={translate(`${type}.impact.severity.tooltip`)} | |||
values={{ | |||
severity: translate('severity', severity).toLowerCase(), | |||
quality: translate('software_quality', quality).toLowerCase(), | |||
quality: quality.toLowerCase(), | |||
}} | |||
/> | |||
} | |||
@@ -62,7 +62,7 @@ export default function SoftwareImpactPill(props: Props) { | |||
]} | |||
> | |||
<Pill className={classNames('sw-flex sw-gap-1 sw-items-center', className)} variant={variant}> | |||
{translate('software_quality', quality)} | |||
{quality} | |||
<SoftwareImpactSeverityIcon severity={severity} data-guiding-id="issue-3" /> | |||
</Pill> | |||
</DocumentationTooltip> |
@@ -0,0 +1,72 @@ | |||
/* | |||
* 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 classNames from 'classnames'; | |||
import React from 'react'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { SoftwareImpactSeverity, SoftwareQuality } from '../../types/clean-code-taxonomy'; | |||
import SoftwareImpactPill from './SoftwareImpactPill'; | |||
interface SoftwareImpact { | |||
softwareQuality: SoftwareQuality; | |||
severity: SoftwareImpactSeverity; | |||
} | |||
interface SoftwareImpactPillListProps | |||
extends Pick<Parameters<typeof SoftwareImpactPill>[0], 'type'> { | |||
softwareImpacts: Array<SoftwareImpact>; | |||
className?: string; | |||
} | |||
const severityMap = { | |||
[SoftwareImpactSeverity.High]: 2, | |||
[SoftwareImpactSeverity.Medium]: 1, | |||
[SoftwareImpactSeverity.Low]: 0, | |||
}; | |||
export default function SoftwareImpactPillList({ | |||
softwareImpacts, | |||
type, | |||
className, | |||
}: Readonly<SoftwareImpactPillListProps>) { | |||
const getQualityLabel = (quality: SoftwareQuality) => translate('software_quality', quality); | |||
const sortingFn = (a: SoftwareImpact, b: SoftwareImpact) => { | |||
if (a.severity !== b.severity) { | |||
return severityMap[b.severity] - severityMap[a.severity]; | |||
} | |||
return getQualityLabel(a.softwareQuality).localeCompare(getQualityLabel(b.softwareQuality)); | |||
}; | |||
return ( | |||
<ul className={classNames('sw-flex sw-gap-2', className)}> | |||
{softwareImpacts | |||
.slice() | |||
.sort(sortingFn) | |||
.map(({ severity, softwareQuality }) => ( | |||
<li key={softwareQuality}> | |||
<SoftwareImpactPill | |||
severity={severity} | |||
quality={getQualityLabel(softwareQuality)} | |||
type={type} | |||
/> | |||
</li> | |||
))} | |||
</ul> | |||
); | |||
} |