aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorstanislavh <stanislav.honcharov@sonarsource.com>2023-08-18 14:57:33 +0200
committersonartech <sonartech@sonarsource.com>2023-08-22 20:03:05 +0000
commit46741d608660feeaf3af8aef4980e085b3e2ba5c (patch)
tree2abcc8cf3bdbf7091daf67f89b711529299751ae /server
parent8e85496afd806079b9125f351cf5574f65ff8147 (diff)
downloadsonarqube-46741d608660feeaf3af8aef4980e085b3e2ba5c.tar.gz
sonarqube-46741d608660feeaf3af8aef4980e085b3e2ba5c.zip
SONAR-20197 Rules facet filters use CCT
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts38
-rw-r--r--server/sonar-web/src/main/js/api/mocks/data/rules.ts9
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts53
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx (renamed from server/sonar-web/src/main/js/apps/coding-rules/components/ActivationSeverityFacet.tsx)39
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx67
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/LanguageFacet.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/SeverityFacet.tsx69
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx (renamed from server/sonar-web/src/main/js/apps/coding-rules/components/DefaultSeverityFacet.tsx)34
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/query.ts37
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/utils.ts26
-rw-r--r--server/sonar-web/src/main/js/components/facet/FacetHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/helpers/constants.ts11
-rw-r--r--server/sonar-web/src/main/js/helpers/query.ts26
-rw-r--r--server/sonar-web/src/main/js/types/rules.ts4
-rw-r--r--server/sonar-web/src/main/js/types/types.ts6
21 files changed, 312 insertions, 147 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts
index df68b91786c..d22c7cc83e0 100644
--- a/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/CodingRulesServiceMock.ts
@@ -67,12 +67,13 @@ jest.mock('../quality-profiles');
type FacetFilter = Pick<
SearchRulesQuery,
+ | 'impactSeverities'
+ | 'impactSoftwareQualities'
| 'languages'
| 'tags'
| 'available_since'
| 'q'
| 'types'
- | 'severities'
| 'repositories'
| 'qprofile'
| 'activation'
@@ -81,6 +82,7 @@ type FacetFilter = Pick<
| 'owaspTop10-2021'
| 'cwe'
| 'is_template'
+ | 'cleanCodeAttributeCategories'
>;
const FACET_RULE_MAP: { [key: string]: keyof Rule } = {
@@ -151,10 +153,12 @@ export default class CodingRulesServiceMock {
}
filterFacet({
+ impactSeverities,
+ impactSoftwareQualities,
+ cleanCodeAttributeCategories,
languages,
available_since,
q,
- severities,
types,
tags,
is_template,
@@ -167,15 +171,31 @@ export default class CodingRulesServiceMock {
activation,
}: FacetFilter) {
let filteredRules = this.rules;
+ if (cleanCodeAttributeCategories) {
+ filteredRules = filteredRules.filter(
+ (r) =>
+ r.cleanCodeAttributeCategory &&
+ cleanCodeAttributeCategories.includes(r.cleanCodeAttributeCategory)
+ );
+ }
+ if (impactSoftwareQualities) {
+ filteredRules = filteredRules.filter(
+ (r) =>
+ r.impacts &&
+ r.impacts.some(({ softwareQuality }) => impactSoftwareQualities.includes(softwareQuality))
+ );
+ }
+ if (impactSeverities) {
+ filteredRules = filteredRules.filter(
+ (r) => r.impacts && r.impacts.some(({ severity }) => impactSeverities.includes(severity))
+ );
+ }
if (types) {
filteredRules = filteredRules.filter((r) => types.includes(r.type));
}
if (languages) {
filteredRules = filteredRules.filter((r) => r.lang && languages.includes(r.lang));
}
- if (severities) {
- filteredRules = filteredRules.filter((r) => r.severity && severities.includes(r.severity));
- }
if (qprofile) {
const qProfileLang = this.qualityProfile.find((p) => p.key === qprofile)?.language;
filteredRules = filteredRules
@@ -365,7 +385,8 @@ export default class CodingRulesServiceMock {
p,
ps,
available_since,
- severities,
+ impactSeverities,
+ impactSoftwareQualities,
repositories,
qprofile,
sonarsourceSecurity,
@@ -377,6 +398,7 @@ export default class CodingRulesServiceMock {
rule_key,
is_template,
activation,
+ cleanCodeAttributeCategories,
}: SearchRulesQuery): Promise<SearchRulesResponse> => {
const standards = await getStandards();
const facetCounts: Array<{ property: string; values: { val: string; count: number }[] }> = [];
@@ -424,7 +446,9 @@ export default class CodingRulesServiceMock {
languages,
available_since,
q,
- severities,
+ impactSeverities,
+ impactSoftwareQualities,
+ cleanCodeAttributeCategories,
repositories,
types,
tags,
diff --git a/server/sonar-web/src/main/js/api/mocks/data/rules.ts b/server/sonar-web/src/main/js/api/mocks/data/rules.ts
index 785966acdb7..63855d8ac1e 100644
--- a/server/sonar-web/src/main/js/api/mocks/data/rules.ts
+++ b/server/sonar-web/src/main/js/api/mocks/data/rules.ts
@@ -21,6 +21,11 @@
import { RuleDescriptionSections } from '../../../apps/coding-rules/rule';
import { mockRule, mockRuleActivation, mockRuleDetails } from '../../../helpers/testMocks';
import {
+ CleanCodeAttributeCategory,
+ SoftwareImpactSeverity,
+ SoftwareQuality,
+} from '../../../types/clean-code-taxonomy';
+import {
ADVANCED_RULE,
RULE_1,
RULE_10,
@@ -113,6 +118,9 @@ export function mockRuleDetailsList() {
key: RULE_3,
repo: 'repo2',
name: 'Unknown rule',
+ impacts: [
+ { softwareQuality: SoftwareQuality.Maintainability, severity: SoftwareImpactSeverity.Low },
+ ],
lang: 'js',
langName: 'JavaScript',
}),
@@ -128,6 +136,7 @@ export function mockRuleDetailsList() {
type: 'VULNERABILITY',
lang: 'py',
langName: 'Python',
+ cleanCodeAttributeCategory: CleanCodeAttributeCategory.Consistent,
name: 'Awsome Python rule',
descriptionSections: [
{ key: RuleDescriptionSections.INTRODUCTION, content: introTitle },
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
index 4134304b6c6..a81bc28eee4 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
+++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
@@ -20,7 +20,7 @@
import { act, fireEvent, screen } from '@testing-library/react';
import selectEvent from 'react-select-event';
import CodingRulesServiceMock, { RULE_TAGS_MOCK } from '../../../api/mocks/CodingRulesServiceMock';
-import { RULE_TYPES } from '../../../helpers/constants';
+import { CLEAN_CODE_CATEGORIES, SOFTWARE_QUALITIES } from '../../../helpers/constants';
import { parseDate } from '../../../helpers/dates';
import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
import { dateInputEvent, renderAppRoutes } from '../../../helpers/testReactTestingUtils';
@@ -57,8 +57,12 @@ describe('Rules app list', () => {
1
);
- // Renders type facets
- RULE_TYPES.map((type) => `issue.type.${type}`).forEach((name) =>
+ // Renders clean code categories and software qualities facets
+ CLEAN_CODE_CATEGORIES.map(
+ (category) => `issue.clean_code_attribute_category.${category}`
+ ).forEach((name) => expect(ui.facetItem(name).get()).toBeInTheDocument());
+
+ SOFTWARE_QUALITIES.map((quality) => `issue.software_quality.${quality}`).forEach((name) =>
expect(ui.facetItem(name).get()).toBeInTheDocument()
);
@@ -77,6 +81,7 @@ describe('Rules app list', () => {
ui.availableSinceFacet,
ui.templateFacet,
ui.qpFacet,
+ ui.typeFacet,
].forEach((facet) => {
expect(facet.get()).toHaveAttribute('aria-expanded', 'false');
});
@@ -159,6 +164,31 @@ describe('Rules app list', () => {
await user.click(ui.facetItem('cute').get());
});
expect(ui.ruleListItem.getAll(ui.rulesList.get())).toHaveLength(1);
+
+ // Clear all filters
+ await act(async () => {
+ await user.click(ui.clearAllFiltersButton.get());
+ });
+ expect(ui.ruleListItem.getAll(ui.rulesList.get())).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);
+
+ // Filter by software quality
+ await act(async () => {
+ await user.click(ui.facetItem('issue.software_quality.MAINTAINABILITY').get());
+ });
+ expect(ui.ruleListItem.getAll(ui.rulesList.get())).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);
});
it('filter by standards', async () => {
@@ -368,7 +398,8 @@ describe('Rule app details', () => {
ui.ruleCleanCodeAttributeCategory(CleanCodeAttributeCategory.Adaptable).get()
).toBeInTheDocument();
expect(ui.ruleCleanCodeAttribute(CleanCodeAttribute.Clear).get()).toBeInTheDocument();
- expect(ui.ruleSoftwareQuality(SoftwareQuality.Maintainability).get()).toBeInTheDocument();
+ // 1 In Rule details + 1 in facet
+ expect(ui.ruleSoftwareQuality(SoftwareQuality.Maintainability).getAll()).toHaveLength(2);
expect(document.title).toEqual('page_title.template.with_category.coding_rules.page');
expect(screen.getByText('Why')).toBeInTheDocument();
expect(screen.getByText('Because')).toBeInTheDocument();
@@ -683,11 +714,17 @@ describe('redirects', () => {
});
it('should handle hash parameters', async () => {
- renderCodingRulesApp(mockLoggedInUser(), 'coding_rules#languages=c,js|types=BUG');
- // 2 languages
+ const { ui } = getPageObjects();
+
+ renderCodingRulesApp(
+ mockLoggedInUser(),
+ 'coding_rules#languages=c,js|types=BUG|cleanCodeAttributeCategories=ADAPTABLE'
+ );
expect(await screen.findByText('x_selected.2')).toBeInTheDocument();
- expect(screen.getAllByTitle('issue.type.BUG')).toHaveLength(2);
- // Only 3 rules shown
+ expect(screen.getByTitle('issue.type.BUG')).toBeInTheDocument();
+ expect(ui.facetItem('issue.clean_code_attribute_category.ADAPTABLE').get()).toBeChecked();
+
+ // Only 2 rules shown
expect(screen.getByText('x_of_y_shown.2.2')).toBeInTheDocument();
});
});
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationSeverityFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx
index 7754b3efbe3..b0396e8053c 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationSeverityFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx
@@ -18,32 +18,23 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import SeverityHelper from '../../../components/shared/SeverityHelper';
-import { SEVERITIES } from '../../../helpers/constants';
+import { CLEAN_CODE_CATEGORIES } from '../../../helpers/constants';
import { translate } from '../../../helpers/l10n';
import Facet, { BasicProps } from './Facet';
-interface Props extends BasicProps {
- disabled: boolean;
-}
-
-export default class ActivationSeverityFacet extends React.PureComponent<Props> {
- renderName = (severity: string) => <SeverityHelper severity={severity} />;
-
- renderTextName = (severity: string) => translate('severity', severity);
+export default function AttributeCategoryFacet(props: BasicProps) {
+ const renderName = React.useCallback(
+ (attribute: string) => translate('issue.clean_code_attribute_category', attribute),
+ []
+ );
- render() {
- return (
- <Facet
- {...this.props}
- disabled={this.props.disabled}
- disabledHelper={translate('coding_rules.filters.active_severity.inactive')}
- halfWidth
- options={SEVERITIES}
- property="activationSeverities"
- renderName={this.renderName}
- renderTextName={this.renderTextName}
- />
- );
- }
+ return (
+ <Facet
+ {...props}
+ options={CLEAN_CODE_CATEGORIES}
+ property="cleanCodeAttributeCategories"
+ renderName={renderName}
+ renderTextName={renderName}
+ />
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx
index 3cef1985e96..3998d0fc355 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx
@@ -58,10 +58,8 @@ import {
OpenFacets,
Query,
areQueriesEqual,
- getAppFacet,
getOpen,
getSelected,
- getServerFacet,
hasRuleKey,
parseQuery,
serializeQuery,
@@ -114,7 +112,8 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
),
sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet({}, query),
standards: shouldOpenStandardsFacet({}, query),
- types: true,
+ cleanCodeAttributeCategories: true,
+ impactSoftwareQualities: true,
},
referencedProfiles: {},
referencedRepositories: {},
@@ -199,8 +198,7 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
const { openFacets } = this.state;
return Object.keys(openFacets)
.filter((facet: FacetKey) => openFacets[facet])
- .filter((facet: FacetKey) => shouldRequestFacet(facet))
- .map((facet: FacetKey) => getServerFacet(facet));
+ .filter((facet: FacetKey) => shouldRequestFacet(facet));
};
getFieldsToFetch = () => {
@@ -214,6 +212,7 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
'sysTags',
'tags',
'templateKey',
+ 'cleanCodeAttribute',
];
if (parseQuery(this.props.location.query).profile) {
fields.push('actives', 'params');
@@ -226,7 +225,7 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
facets: this.getFacetsToFetch().join(),
ps: PAGE_SIZE,
s: 'name',
- ...this.props.location.query,
+ ...serializeQuery(parseQuery(this.props.location.query)),
});
stopLoading = () => {
@@ -299,7 +298,7 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
};
fetchFacet = (facet: FacetKey) => {
- this.makeFetchRequest({ ps: 1, facets: getServerFacet(facet) }).then(({ facets }) => {
+ this.makeFetchRequest({ ps: 1, facets: facet }).then(({ facets }) => {
if (this.mounted) {
this.setState((state) => ({ facets: { ...state.facets, ...facets }, loading: false }));
}
@@ -711,7 +710,7 @@ function parseFacets(rawFacets: { property: string; values: { count: number; val
for (const rawValue of rawFacet.values) {
values[rawValue.val] = rawValue.count;
}
- facets[getAppFacet(rawFacet.property)] = values;
+ facets[rawFacet.property as FacetKey] = values;
}
return facets;
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
index 2d43646bba3..4b8cfe0ca18 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/FacetsList.tsx
@@ -21,13 +21,15 @@ import * as React from 'react';
import { Profile } from '../../../api/quality-profiles';
import { Dict } from '../../../types/types';
import { Facets, OpenFacets, Query } from '../query';
-import ActivationSeverityFacet from './ActivationSeverityFacet';
+import AttributeCategoryFacet from './AttributeCategoryFacet';
import AvailableSinceFacet from './AvailableSinceFacet';
-import DefaultSeverityFacet from './DefaultSeverityFacet';
import InheritanceFacet from './InheritanceFacet';
import LanguageFacet from './LanguageFacet';
import ProfileFacet from './ProfileFacet';
+import SoftwareQualityFacet from './SoftwareQualityFacet';
+
import RepositoryFacet from './RepositoryFacet';
+import SeverityFacet from './SeverityFacet';
import { StandardFacet } from './StandardFacet';
import StatusFacet from './StatusFacet';
import TagFacet from './TagFacet';
@@ -54,10 +56,6 @@ export default function FacetsList(props: FacetsListProps) {
props.selectedProfile === undefined ||
!props.selectedProfile.isInherited;
- const activationSeverityDisabled =
- props.query.compareToProfile !== undefined ||
- props.selectedProfile === undefined ||
- !props.query.activation;
return (
<>
<LanguageFacet
@@ -68,18 +66,43 @@ export default function FacetsList(props: FacetsListProps) {
stats={props.facets && props.facets.languages}
values={props.query.languages}
/>
+
+ <AttributeCategoryFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.cleanCodeAttributeCategories}
+ stats={props.facets?.cleanCodeAttributeCategories}
+ values={props.query.cleanCodeAttributeCategories}
+ />
+
+ <SoftwareQualityFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.impactSoftwareQualities}
+ stats={props.facets?.impactSoftwareQualities}
+ values={props.query.impactSoftwareQualities}
+ />
+
+ <SeverityFacet
+ onChange={props.onFilterChange}
+ onToggle={props.onFacetToggle}
+ open={!!props.openFacets.impactSeverities}
+ stats={props.facets?.impactSeverities}
+ values={props.query.impactSeverities}
+ />
+
<TypeFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
open={!!props.openFacets.types}
- stats={props.facets && props.facets.types}
+ stats={props.facets?.types}
values={props.query.types}
/>
<TagFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
open={!!props.openFacets.tags}
- stats={props.facets && props.facets.tags}
+ stats={props.facets?.tags}
values={props.query.tags}
/>
<RepositoryFacet
@@ -87,27 +110,21 @@ export default function FacetsList(props: FacetsListProps) {
onToggle={props.onFacetToggle}
open={!!props.openFacets.repositories}
referencedRepositories={props.referencedRepositories}
- stats={props.facets && props.facets.repositories}
+ stats={props.facets?.repositories}
values={props.query.repositories}
/>
- <DefaultSeverityFacet
- onChange={props.onFilterChange}
- onToggle={props.onFacetToggle}
- open={!!props.openFacets.severities}
- stats={props.facets && props.facets.severities}
- values={props.query.severities}
- />
+
<StatusFacet
onChange={props.onFilterChange}
onToggle={props.onFacetToggle}
open={!!props.openFacets.statuses}
- stats={props.facets && props.facets.statuses}
+ stats={props.facets?.statuses}
values={props.query.statuses}
/>
<StandardFacet
cwe={props.query.cwe}
cweOpen={!!props.openFacets.cwe}
- cweStats={props.facets && props.facets.cwe}
+ cweStats={props.facets?.cwe}
fetchingCwe={false}
fetchingOwaspTop10={false}
fetchingOwaspTop10-2021={false}
@@ -117,14 +134,14 @@ export default function FacetsList(props: FacetsListProps) {
open={!!props.openFacets.standards}
owaspTop10={props.query.owaspTop10}
owaspTop10Open={!!props.openFacets.owaspTop10}
- owaspTop10Stats={props.facets && props.facets.owaspTop10}
+ owaspTop10Stats={props.facets?.owaspTop10}
owaspTop10-2021={props.query['owaspTop10-2021']}
owaspTop10-2021Open={!!props.openFacets['owaspTop10-2021']}
- owaspTop10-2021Stats={props.facets && props.facets['owaspTop10-2021']}
+ owaspTop10-2021Stats={props.facets?.['owaspTop10-2021']}
query={props.query}
sonarsourceSecurity={props.query.sonarsourceSecurity}
sonarsourceSecurityOpen={!!props.openFacets.sonarsourceSecurity}
- sonarsourceSecurityStats={props.facets && props.facets.sonarsourceSecurity}
+ sonarsourceSecurityStats={props.facets?.sonarsourceSecurity}
/>
<AvailableSinceFacet
onChange={props.onFilterChange}
@@ -157,14 +174,6 @@ export default function FacetsList(props: FacetsListProps) {
open={!!props.openFacets.inheritance}
value={props.query.inheritance}
/>
- <ActivationSeverityFacet
- disabled={activationSeverityDisabled}
- onChange={props.onFilterChange}
- onToggle={props.onFacetToggle}
- open={!!props.openFacets.activationSeverities}
- stats={props.facets && props.facets.activationSeverities}
- values={props.query.activationSeverities}
- />
</>
)}
</>
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/LanguageFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/LanguageFacet.tsx
index 1e6fc871fbf..81e9b7b44ba 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/LanguageFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/LanguageFacet.tsx
@@ -74,6 +74,7 @@ class LanguageFacet extends React.PureComponent<Props> {
getFacetItemText={this.getLanguageName}
getSearchResultKey={(language) => language.key}
getSearchResultText={(language) => language.name}
+ maxInitialItems={10}
minSearchLength={1}
onChange={this.props.onChange}
onSearch={this.handleSearch}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx
index 8c9c3247362..f791e997634 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx
@@ -56,7 +56,6 @@ export default class ProfileFacet extends React.PureComponent<Props> {
handleClear = () =>
this.props.onChange({
activation: undefined,
- activationSeverities: [],
compareToProfile: undefined,
inheritance: undefined,
profile: undefined,
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/SeverityFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/SeverityFacet.tsx
new file mode 100644
index 00000000000..cdb4f11e5b9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/SeverityFacet.tsx
@@ -0,0 +1,69 @@
+/*
+ * 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 DocumentationTooltip from '../../../components/common/DocumentationTooltip';
+import SoftwareImpactSeverityIcon from '../../../components/icons/SoftwareImpactSeverityIcon';
+import { IMPACT_SEVERITIES } from '../../../helpers/constants';
+import { translate } from '../../../helpers/l10n';
+import Facet, { BasicProps } from './Facet';
+
+export default function SeverityFacet(props: BasicProps) {
+ const renderName = React.useCallback(
+ (severity: string) => (
+ <div className="sw-flex">
+ <SoftwareImpactSeverityIcon severity={severity} />
+ <span className="sw-ml-1">{translate('severity', severity)}</span>
+ </div>
+ ),
+ []
+ );
+
+ const renderTextName = React.useCallback(
+ (severity: string) => translate('severity', severity),
+ []
+ );
+
+ return (
+ <Facet
+ {...props}
+ options={IMPACT_SEVERITIES}
+ property="impactSeverities"
+ renderName={renderName}
+ renderTextName={renderTextName}
+ >
+ <DocumentationTooltip
+ className="spacer-left"
+ placement="right"
+ content={
+ <>
+ <p>{translate('issues.facet.impactSeverities.help.line1')}</p>
+ <p className="sw-mt-2">{translate('issues.facet.impactSeverities.help.line2')}</p>
+ </>
+ }
+ links={[
+ {
+ href: '/user-guide/clean-code',
+ label: translate('learn_more'),
+ },
+ ]}
+ />
+ </Facet>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/DefaultSeverityFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx
index da51af94cd6..59d0da4736b 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/DefaultSeverityFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx
@@ -17,27 +17,25 @@
* 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 SeverityHelper from '../../../components/shared/SeverityHelper';
-import { SEVERITIES } from '../../../helpers/constants';
+import { SOFTWARE_QUALITIES } from '../../../helpers/constants';
import { translate } from '../../../helpers/l10n';
import Facet, { BasicProps } from './Facet';
-export default class DefaultSeverityFacet extends React.PureComponent<BasicProps> {
- renderName = (severity: string) => <SeverityHelper severity={severity} />;
-
- renderTextName = (severity: string) => translate('severity', severity);
+export default function SoftwareQualityFacet(props: BasicProps) {
+ const renderName = React.useCallback(
+ (quality: string) => translate('issue.software_quality', quality),
+ []
+ );
- render() {
- return (
- <Facet
- {...this.props}
- halfWidth
- options={SEVERITIES}
- property="severities"
- renderName={this.renderName}
- renderTextName={this.renderTextName}
- />
- );
- }
+ return (
+ <Facet
+ {...props}
+ options={SOFTWARE_QUALITIES}
+ property="impactSoftwareQualities"
+ renderName={renderName}
+ renderTextName={renderName}
+ />
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/query.ts b/server/sonar-web/src/main/js/apps/coding-rules/query.ts
index 540ebece009..eb31bc35298 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/query.ts
+++ b/server/sonar-web/src/main/js/apps/coding-rules/query.ts
@@ -24,20 +24,28 @@ import {
parseAsOptionalBoolean,
parseAsOptionalString,
parseAsString,
+ parseImpactSeverityQuery,
queriesEqual,
serializeDateShort,
serializeOptionalBoolean,
serializeString,
serializeStringArray,
} from '../../helpers/query';
+import {
+ CleanCodeAttributeCategory,
+ SoftwareImpactSeverity,
+ SoftwareQuality,
+} from '../../types/clean-code-taxonomy';
import { Dict, RawQuery, RuleInheritance } from '../../types/types';
export interface Query {
activation: boolean | undefined;
- activationSeverities: string[];
availableSince: Date | undefined;
+ cleanCodeAttributeCategories: CleanCodeAttributeCategory[];
compareToProfile: string | undefined;
cwe: string[];
+ impactSeverities: SoftwareImpactSeverity[];
+ impactSoftwareQualities: SoftwareQuality[];
inheritance: RuleInheritance | undefined;
languages: string[];
owaspTop10: string[];
@@ -78,10 +86,18 @@ export interface Actives {
export function parseQuery(query: RawQuery): Query {
return {
activation: parseAsOptionalBoolean(query.activation),
- activationSeverities: parseAsArray(query.active_severities, parseAsString),
availableSince: parseAsDate(query.available_since),
+ cleanCodeAttributeCategories: parseAsArray<CleanCodeAttributeCategory>(
+ query.cleanCodeAttributeCategories,
+ parseAsString
+ ),
compareToProfile: parseAsOptionalString(query.compareToProfile),
cwe: parseAsArray(query.cwe, parseAsString),
+ impactSeverities: parseImpactSeverityQuery(query.impactSeverities, query.severities),
+ impactSoftwareQualities: parseAsArray<SoftwareQuality>(
+ query.impactSoftwareQualities,
+ parseAsString
+ ),
inheritance: parseAsInheritance(query.inheritance),
languages: parseAsArray(query.languages, parseAsString),
owaspTop10: parseAsArray(query.owaspTop10, parseAsString),
@@ -102,11 +118,13 @@ export function parseQuery(query: RawQuery): Query {
export function serializeQuery(query: Query): RawQuery {
return cleanQuery({
activation: serializeOptionalBoolean(query.activation),
- active_severities: serializeStringArray(query.activationSeverities),
available_since: serializeDateShort(query.availableSince),
+ cleanCodeAttributeCategories: serializeStringArray(query.cleanCodeAttributeCategories),
compareToProfile: serializeString(query.compareToProfile),
cwe: serializeStringArray(query.cwe),
inheritance: serializeInheritance(query.inheritance),
+ impactSeverities: serializeStringArray(query.impactSeverities),
+ impactSoftwareQualities: serializeStringArray(query.impactSoftwareQualities),
is_template: serializeOptionalBoolean(query.template),
languages: serializeStringArray(query.languages),
owaspTop10: serializeStringArray(query.owaspTop10),
@@ -115,7 +133,7 @@ export function serializeQuery(query: Query): RawQuery {
qprofile: serializeString(query.profile),
repositories: serializeStringArray(query.repositories),
rule_key: serializeString(query.ruleKey),
- severities: serializeStringArray(query.severities),
+ severities: undefined,
sonarsourceSecurity: serializeStringArray(query.sonarsourceSecurity),
statuses: serializeStringArray(query.statuses),
tags: serializeStringArray(query.tags),
@@ -141,18 +159,13 @@ export function shouldRequestFacet(facet: string): facet is FacetKey {
'statuses',
'tags',
'types',
+ 'cleanCodeAttributeCategories',
+ 'impactSoftwareQualities',
+ 'impactSeverities',
];
return facetsToRequest.includes(facet);
}
-export function getServerFacet(facet: FacetKey) {
- return facet === 'activationSeverities' ? 'active_severities' : facet;
-}
-
-export function getAppFacet(serverFacet: string): FacetKey {
- return serverFacet === 'active_severities' ? 'activationSeverities' : (serverFacet as FacetKey);
-}
-
export function getOpen(query: RawQuery) {
return query.open;
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx b/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx
index 8847ba6f056..dd518f5c590 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/utils-tests.tsx
@@ -41,11 +41,15 @@ const selectors = {
clearAllFiltersButton: byRole('button', { name: 'clear_all_filters' }),
// Facets
+ cleanCodeCategoriesFacet: byRole('button', {
+ name: 'coding_rules.facet.cleanCodeAttributeCategories',
+ }),
languagesFacet: byRole('button', { name: 'coding_rules.facet.languages' }),
typeFacet: byRole('button', { name: 'coding_rules.facet.types' }),
tagsFacet: byRole('button', { name: 'coding_rules.facet.tags' }),
repositoriesFacet: byRole('button', { name: 'coding_rules.facet.repositories' }),
- severetiesFacet: byRole('button', { name: 'coding_rules.facet.severities' }),
+ softwareQualitiesFacet: byRole('button', { name: 'coding_rules.facet.impactSoftwareQualities' }),
+ severetiesFacet: byRole('button', { name: 'coding_rules.facet.impactSeverities' }),
statusesFacet: byRole('button', { name: 'coding_rules.facet.statuses' }),
standardsFacet: byRole('button', { name: 'issues.facet.standards' }),
standardsOwasp2017Top10Facet: byRole('button', { name: 'issues.facet.owaspTop10' }),
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx
index 28779bd54fb..981f502c3d1 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/AttributeCategoryFacet.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
+import { CLEAN_CODE_CATEGORIES } from '../../../helpers/constants';
import { CleanCodeAttributeCategory } from '../../../types/clean-code-taxonomy';
import { CommonProps, SimpleListStyleFacet } from './SimpleListStyleFacet';
@@ -26,8 +27,6 @@ interface Props extends CommonProps {
categories: Array<CleanCodeAttributeCategory>;
}
-const CATEGORIES = Object.values(CleanCodeAttributeCategory);
-
export function AttributeCategoryFacet(props: Props) {
const { categories = [], ...rest } = props;
@@ -35,7 +34,7 @@ export function AttributeCategoryFacet(props: Props) {
<SimpleListStyleFacet
property="cleanCodeAttributeCategories"
itemNamePrefix="issue.clean_code_attribute_category"
- listItems={CATEGORIES}
+ listItems={CLEAN_CODE_CATEGORIES}
selectedItems={categories}
{...rest}
/>
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx
index 4b9fba32883..6247269a42a 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.tsx
@@ -21,6 +21,7 @@
import * as React from 'react';
import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
import SoftwareImpactSeverityIcon from '../../../components/icons/SoftwareImpactSeverityIcon';
+import { IMPACT_SEVERITIES } from '../../../helpers/constants';
import { translate } from '../../../helpers/l10n';
import { SoftwareImpactSeverity } from '../../../types/clean-code-taxonomy';
import { CommonProps, SimpleListStyleFacet } from './SimpleListStyleFacet';
@@ -29,8 +30,6 @@ interface Props extends CommonProps {
severities: SoftwareImpactSeverity[];
}
-const SEVERITIES = Object.values(SoftwareImpactSeverity);
-
export function SeverityFacet(props: Props) {
const { severities = [], ...rest } = props;
@@ -38,7 +37,7 @@ export function SeverityFacet(props: Props) {
<SimpleListStyleFacet
property="impactSeverities"
itemNamePrefix="severity"
- listItems={SEVERITIES}
+ listItems={IMPACT_SEVERITIES}
selectedItems={severities}
renderIcon={(severity: string) => <SoftwareImpactSeverityIcon severity={severity} />}
help={
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx
index 2888116505e..0c94d42c751 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx
@@ -19,6 +19,7 @@
*/
import * as React from 'react';
+import { SOFTWARE_QUALITIES } from '../../../helpers/constants';
import { SoftwareQuality } from '../../../types/clean-code-taxonomy';
import { CommonProps, SimpleListStyleFacet } from './SimpleListStyleFacet';
@@ -26,8 +27,6 @@ interface Props extends CommonProps {
qualities: Array<SoftwareQuality>;
}
-const QUALITIES = Object.values(SoftwareQuality);
-
export function SoftwareQualityFacet(props: Props) {
const { qualities = [], ...rest } = props;
@@ -35,7 +34,7 @@ export function SoftwareQualityFacet(props: Props) {
<SimpleListStyleFacet
property="impactSoftwareQualities"
itemNamePrefix="issue.software_quality"
- listItems={QUALITIES}
+ listItems={SOFTWARE_QUALITIES}
selectedItems={qualities}
{...rest}
/>
diff --git a/server/sonar-web/src/main/js/apps/issues/utils.ts b/server/sonar-web/src/main/js/apps/issues/utils.ts
index 31220895075..f3f9c40661b 100644
--- a/server/sonar-web/src/main/js/apps/issues/utils.ts
+++ b/server/sonar-web/src/main/js/apps/issues/utils.ts
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { compact, isArray, uniq } from 'lodash';
+import { isArray } from 'lodash';
import { getUsers } from '../../api/users';
import { formatMeasure } from '../../helpers/measures';
import {
@@ -26,6 +26,7 @@ import {
parseAsBoolean,
parseAsDate,
parseAsString,
+ parseImpactSeverityQuery,
queriesEqual,
serializeDateShort,
serializeString,
@@ -134,29 +135,6 @@ export function parseQuery(query: RawQuery): Query {
};
}
-function parseImpactSeverityQuery(
- newSeverities: string,
- oldSeverities?: string
-): SoftwareImpactSeverity[] {
- const OLD_TO_NEW_MAPPER = {
- BLOCKER: SoftwareImpactSeverity.High,
- CRITICAL: SoftwareImpactSeverity.High,
- MAJOR: SoftwareImpactSeverity.Medium,
- MINOR: SoftwareImpactSeverity.Low,
- INFO: SoftwareImpactSeverity.Low,
- };
-
- // Merging new and old severities includes mapping for old to new
- return compact(
- uniq([
- ...parseAsArray<SoftwareImpactSeverity>(newSeverities, parseAsString),
- ...parseAsArray(oldSeverities, parseAsString).map(
- (oldSeverity: string) => OLD_TO_NEW_MAPPER[oldSeverity as keyof typeof OLD_TO_NEW_MAPPER]
- ),
- ])
- );
-}
-
export function getOpen(query: RawQuery): string | undefined {
return query.open;
}
diff --git a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx
index e741e3084ee..35f16da7730 100644
--- a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx
+++ b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx
@@ -91,7 +91,7 @@ export default class FacetHeader extends React.PureComponent<Props> {
{this.props.onClick ? (
<span className="search-navigator-facet-header display-flex-center">
<button
- className="button-link"
+ className="button-link display-flex-center"
type="button"
onClick={this.handleClick}
aria-expanded={open}
diff --git a/server/sonar-web/src/main/js/helpers/constants.ts b/server/sonar-web/src/main/js/helpers/constants.ts
index 828f442b408..17615740c75 100644
--- a/server/sonar-web/src/main/js/helpers/constants.ts
+++ b/server/sonar-web/src/main/js/helpers/constants.ts
@@ -19,12 +19,23 @@
*/
import { colors } from '../app/theme';
import { AlmKeys } from '../types/alm-settings';
+import {
+ CleanCodeAttributeCategory,
+ SoftwareImpactSeverity,
+ SoftwareQuality,
+} from '../types/clean-code-taxonomy';
import { ComponentQualifier } from '../types/component';
import { IssueResolution, IssueScope, IssueSeverity, IssueType } from '../types/issues';
import { RuleType } from '../types/types';
export const SEVERITIES = Object.values(IssueSeverity);
+export const IMPACT_SEVERITIES = Object.values(SoftwareImpactSeverity);
+
+export const CLEAN_CODE_CATEGORIES = Object.values(CleanCodeAttributeCategory);
+
+export const SOFTWARE_QUALITIES = Object.values(SoftwareQuality);
+
export const STATUSES = ['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED', 'CLOSED'];
export const ISSUE_TYPES: IssueType[] = [
diff --git a/server/sonar-web/src/main/js/helpers/query.ts b/server/sonar-web/src/main/js/helpers/query.ts
index 37ced126f60..97238fc4ab8 100644
--- a/server/sonar-web/src/main/js/helpers/query.ts
+++ b/server/sonar-web/src/main/js/helpers/query.ts
@@ -17,7 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { isEqual, isNil, omitBy } from 'lodash';
+import { compact, isEqual, isNil, omitBy, uniq } from 'lodash';
+import { SoftwareImpactSeverity } from '../types/clean-code-taxonomy';
import { RawQuery } from '../types/types';
import { isValidDate, parseDate, toISO8601WithOffsetString, toShortISO8601String } from './dates';
@@ -125,3 +126,26 @@ export function serializeOptionalBoolean(value: boolean | undefined): string | u
}
return undefined;
}
+
+export function parseImpactSeverityQuery(
+ newSeverities: string,
+ oldSeverities?: string
+): SoftwareImpactSeverity[] {
+ const OLD_TO_NEW_MAPPER = {
+ BLOCKER: SoftwareImpactSeverity.High,
+ CRITICAL: SoftwareImpactSeverity.High,
+ MAJOR: SoftwareImpactSeverity.Medium,
+ MINOR: SoftwareImpactSeverity.Low,
+ INFO: SoftwareImpactSeverity.Low,
+ };
+
+ // Merging new and old severities includes mapping for old to new
+ return compact(
+ uniq([
+ ...parseAsArray<SoftwareImpactSeverity>(newSeverities, parseAsString),
+ ...parseAsArray(oldSeverities, parseAsString).map(
+ (oldSeverity: string) => OLD_TO_NEW_MAPPER[oldSeverity as keyof typeof OLD_TO_NEW_MAPPER]
+ ),
+ ])
+ );
+}
diff --git a/server/sonar-web/src/main/js/types/rules.ts b/server/sonar-web/src/main/js/types/rules.ts
index 4e85cb1e1a7..11128fdcf36 100644
--- a/server/sonar-web/src/main/js/types/rules.ts
+++ b/server/sonar-web/src/main/js/types/rules.ts
@@ -29,6 +29,7 @@ export interface SearchRulesQuery {
active_severities?: string;
asc?: boolean | string;
available_since?: string;
+ cleanCodeAttributeCategories?: string;
cwe?: string;
f?: string;
facets?: string;
@@ -45,7 +46,8 @@ export interface SearchRulesQuery {
repositories?: string;
rule_key?: string;
s?: string;
- severities?: string;
+ impactSoftwareQualities?: string;
+ impactSeverities?: string;
sonarsourceSecurity?: string;
statuses?: string;
tags?: string;
diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts
index bab27408f09..aa26d7d3192 100644
--- a/server/sonar-web/src/main/js/types/types.ts
+++ b/server/sonar-web/src/main/js/types/types.ts
@@ -533,9 +533,9 @@ export interface QualityGate {
export type RawQuery = Dict<any>;
export interface Rule {
- cleanCodeAttributeCategory: CleanCodeAttributeCategory;
- cleanCodeAttribute: CleanCodeAttribute;
- impacts: Array<{
+ cleanCodeAttributeCategory?: CleanCodeAttributeCategory;
+ cleanCodeAttribute?: CleanCodeAttribute;
+ impacts?: Array<{
softwareQuality: SoftwareQuality;
severity: SoftwareImpactSeverity;
}>;