瀏覽代碼

SONAR-20690 Sort Software quality badges

tags/10.3.0.82913
Viktor Vorona 6 月之前
父節點
當前提交
05cde6391b

+ 27
- 27
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts 查看文件

@@ -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 () => {

+ 6
- 12
server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx 查看文件

@@ -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>

+ 6
- 10
server/sonar-web/src/main/js/apps/coding-rules/components/RuleListItem.tsx 查看文件

@@ -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={

+ 5
- 1
server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx 查看文件

@@ -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

+ 4
- 8
server/sonar-web/src/main/js/apps/issues/components/IssueHeader.tsx 查看文件

@@ -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

+ 2
- 8
server/sonar-web/src/main/js/apps/quality-profiles/changelog/Changelog.tsx 查看文件

@@ -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>


+ 2
- 2
server/sonar-web/src/main/js/apps/quality-profiles/changelog/__tests__/ChangelogContainer-it.tsx 查看文件

@@ -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/,

+ 5
- 5
server/sonar-web/src/main/js/apps/quality-profiles/compare/ComparisonResults.tsx 查看文件

@@ -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>

+ 3
- 9
server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx 查看文件

@@ -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>
)}


+ 4
- 4
server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx 查看文件

@@ -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>

+ 72
- 0
server/sonar-web/src/main/js/components/shared/SoftwareImpactPillList.tsx 查看文件

@@ -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>
);
}

Loading…
取消
儲存