From: stanislavh Date: Fri, 18 Aug 2023 12:57:33 +0000 (+0200) Subject: SONAR-20197 Rules facet filters use CCT X-Git-Tag: 10.2.0.77647~127 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=46741d608660feeaf3af8aef4980e085b3e2ba5c;p=sonarqube.git SONAR-20197 Rules facet filters use CCT --- 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 => { 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 @@ -20,6 +20,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, @@ -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/ActivationSeverityFacet.tsx deleted file mode 100644 index 7754b3efbe3..00000000000 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/ActivationSeverityFacet.tsx +++ /dev/null @@ -1,49 +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 SeverityHelper from '../../../components/shared/SeverityHelper'; -import { SEVERITIES } 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 { - renderName = (severity: string) => ; - - renderTextName = (severity: string) => translate('severity', severity); - - render() { - return ( - - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx new file mode 100644 index 00000000000..b0396e8053c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/AttributeCategoryFacet.tsx @@ -0,0 +1,40 @@ +/* + * 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 { CLEAN_CODE_CATEGORIES } from '../../../helpers/constants'; +import { translate } from '../../../helpers/l10n'; +import Facet, { BasicProps } from './Facet'; + +export default function AttributeCategoryFacet(props: BasicProps) { + const renderName = React.useCallback( + (attribute: string) => translate('issue.clean_code_attribute_category', attribute), + [] + ); + + return ( + + ); +} 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 { ), sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet({}, query), standards: shouldOpenStandardsFacet({}, query), - types: true, + cleanCodeAttributeCategories: true, + impactSoftwareQualities: true, }, referencedProfiles: {}, referencedRepositories: {}, @@ -199,8 +198,7 @@ export class CodingRulesApp extends React.PureComponent { 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 { '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 { 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 { }; 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/DefaultSeverityFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/DefaultSeverityFacet.tsx deleted file mode 100644 index da51af94cd6..00000000000 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/DefaultSeverityFacet.tsx +++ /dev/null @@ -1,43 +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 SeverityHelper from '../../../components/shared/SeverityHelper'; -import { SEVERITIES } from '../../../helpers/constants'; -import { translate } from '../../../helpers/l10n'; -import Facet, { BasicProps } from './Facet'; - -export default class DefaultSeverityFacet extends React.PureComponent { - renderName = (severity: string) => ; - - renderTextName = (severity: string) => translate('severity', severity); - - render() { - return ( - - ); - } -} 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 ( <> + + + + + + + - + - )} 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 { 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 { 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) => ( +
+ + {translate('severity', severity)} +
+ ), + [] + ); + + const renderTextName = React.useCallback( + (severity: string) => translate('severity', severity), + [] + ); + + return ( + + +

{translate('issues.facet.impactSeverities.help.line1')}

+

{translate('issues.facet.impactSeverities.help.line2')}

+ + } + links={[ + { + href: '/user-guide/clean-code', + label: translate('learn_more'), + }, + ]} + /> +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx new file mode 100644 index 00000000000..59d0da4736b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/SoftwareQualityFacet.tsx @@ -0,0 +1,41 @@ +/* + * 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 { SOFTWARE_QUALITIES } from '../../../helpers/constants'; +import { translate } from '../../../helpers/l10n'; +import Facet, { BasicProps } from './Facet'; + +export default function SoftwareQualityFacet(props: BasicProps) { + const renderName = React.useCallback( + (quality: string) => translate('issue.software_quality', quality), + [] + ); + + return ( + + ); +} 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( + query.cleanCodeAttributeCategories, + parseAsString + ), compareToProfile: parseAsOptionalString(query.compareToProfile), cwe: parseAsArray(query.cwe, parseAsString), + impactSeverities: parseImpactSeverityQuery(query.impactSeverities, query.severities), + impactSoftwareQualities: parseAsArray( + 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; } -const CATEGORIES = Object.values(CleanCodeAttributeCategory); - export function AttributeCategoryFacet(props: Props) { const { categories = [], ...rest } = props; @@ -35,7 +34,7 @@ export function AttributeCategoryFacet(props: Props) { 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) { } 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; } -const QUALITIES = Object.values(SoftwareQuality); - export function SoftwareQualityFacet(props: Props) { const { qualities = [], ...rest } = props; @@ -35,7 +34,7 @@ export function SoftwareQualityFacet(props: Props) { 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(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 { {this.props.onClick ? (