From 86680bd15caaf685761cdb66e785d57dfb28e838 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Thu, 27 Apr 2023 11:49:31 +0200 Subject: [PATCH] Revert "SONAR-19069 Add Fit for Development and Fit for Production facets" This reverts commit b97056771412fd5b5c339e3b2e3086b784c29b00. --- server/sonar-web/src/main/js/api/issues.ts | 1 - .../main/js/api/mocks/IssuesServiceMock.ts | 46 ++--- .../component-measures/sidebar/Sidebar.tsx | 2 +- .../js/apps/issues/__tests__/IssuesApp-it.tsx | 23 +-- .../js/apps/issues/__tests__/utils-test.ts | 2 - .../js/apps/issues/components/IssuesApp.tsx | 70 +++---- .../issues/sidebar/CharacteristicFacet.tsx | 172 ------------------ .../main/js/apps/issues/sidebar/Sidebar.tsx | 37 +--- .../issues/sidebar/__tests__/Sidebar-it.tsx | 12 +- .../src/main/js/apps/issues/test-utils.tsx | 5 - .../src/main/js/apps/issues/utils.ts | 22 +-- .../src/main/js/helpers/mocks/issues.ts | 1 - .../resources/org/sonar/l10n/core.properties | 2 - 13 files changed, 67 insertions(+), 328 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx diff --git a/server/sonar-web/src/main/js/api/issues.ts b/server/sonar-web/src/main/js/api/issues.ts index b4d6b407ad6..a0675a1dc09 100644 --- a/server/sonar-web/src/main/js/api/issues.ts +++ b/server/sonar-web/src/main/js/api/issues.ts @@ -35,7 +35,6 @@ type FacetName = | 'assigned_to_me' | 'assignees' | 'author' - | 'characteristics' | 'createdAt' | 'cwe' | 'directories' diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts index f2fc1dbfc74..a60cbccd420 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -29,12 +29,10 @@ import { mockRawIssue, mockRule, mockRuleDetails, - mockUser, } from '../../helpers/testMocks'; import { ASSIGNEE_ME, IssueActions, - IssueCharacteristic, IssueResolution, IssueScope, IssueSeverity, @@ -64,8 +62,8 @@ import { editIssueComment, getIssueChangelog, getIssueFlowSnippets, - searchIssueTags, searchIssues, + searchIssueTags, setIssueAssignee, setIssueSeverity, setIssueTags, @@ -120,7 +118,6 @@ export default class IssuesServiceMock { component: 'foo:test1.js', creationDate: '2023-01-05T09:36:01+0100', message: 'Issue with no location message', - characteristic: IssueCharacteristic.Secure, type: IssueType.Vulnerability, rule: 'simpleRuleId', textRange: { @@ -183,7 +180,6 @@ export default class IssuesServiceMock { component: 'foo:test1.js', creationDate: '2022-01-01T09:36:01+0100', message: 'FlowIssue', - characteristic: IssueCharacteristic.Clear, type: IssueType.CodeSmell, severity: IssueSeverity.Minor, rule: 'simpleRuleId', @@ -280,7 +276,6 @@ export default class IssuesServiceMock { component: 'foo:test1.js', message: 'Issue on file', assignee: mockLoggedInUser().login, - characteristic: IssueCharacteristic.Clear, type: IssueType.CodeSmell, rule: 'simpleRuleId', textRange: undefined, @@ -294,7 +289,6 @@ export default class IssuesServiceMock { key: 'issue1', component: 'foo:huge.js', message: 'Fix this', - characteristic: IssueCharacteristic.Secure, type: IssueType.Vulnerability, rule: 'simpleRuleId', textRange: { @@ -482,23 +476,23 @@ export default class IssuesServiceMock { this.list = cloneDeep(this.defaultList); - jest.mocked(searchIssues).mockImplementation(this.handleSearchIssues); - jest.mocked(getRuleDetails).mockImplementation(this.handleGetRuleDetails); + (searchIssues as jest.Mock).mockImplementation(this.handleSearchIssues); + (getRuleDetails as jest.Mock).mockImplementation(this.handleGetRuleDetails); jest.mocked(searchRules).mockImplementation(this.handleSearchRules); - jest.mocked(getIssueFlowSnippets).mockImplementation(this.handleGetIssueFlowSnippets); - jest.mocked(bulkChangeIssues).mockImplementation(this.handleBulkChangeIssues); - jest.mocked(getCurrentUser).mockImplementation(this.handleGetCurrentUser); - jest.mocked(dismissNotice).mockImplementation(this.handleDismissNotification); - jest.mocked(setIssueType).mockImplementation(this.handleSetIssueType); + (getIssueFlowSnippets as jest.Mock).mockImplementation(this.handleGetIssueFlowSnippets); + (bulkChangeIssues as jest.Mock).mockImplementation(this.handleBulkChangeIssues); + (getCurrentUser as jest.Mock).mockImplementation(this.handleGetCurrentUser); + (dismissNotice as jest.Mock).mockImplementation(this.handleDismissNotification); + (setIssueType as jest.Mock).mockImplementation(this.handleSetIssueType); jest.mocked(setIssueAssignee).mockImplementation(this.handleSetIssueAssignee); - jest.mocked(setIssueSeverity).mockImplementation(this.handleSetIssueSeverity); - jest.mocked(setIssueTransition).mockImplementation(this.handleSetIssueTransition); - jest.mocked(setIssueTags).mockImplementation(this.handleSetIssueTags); + (setIssueSeverity as jest.Mock).mockImplementation(this.handleSetIssueSeverity); + (setIssueTransition as jest.Mock).mockImplementation(this.handleSetIssueTransition); + (setIssueTags as jest.Mock).mockImplementation(this.handleSetIssueTags); jest.mocked(addIssueComment).mockImplementation(this.handleAddComment); jest.mocked(editIssueComment).mockImplementation(this.handleEditComment); jest.mocked(deleteIssueComment).mockImplementation(this.handleDeleteComment); - jest.mocked(searchUsers).mockImplementation(this.handleSearchUsers); - jest.mocked(searchIssueTags).mockImplementation(this.handleSearchIssueTags); + (searchUsers as jest.Mock).mockImplementation(this.handleSearchUsers); + (searchIssueTags as jest.Mock).mockImplementation(this.handleSearchIssueTags); jest.mocked(getIssueChangelog).mockImplementation(this.handleGetIssueChangelog); } @@ -534,14 +528,14 @@ export default class IssuesServiceMock { this.isAdmin = isAdmin; } - handleBulkChangeIssues = (issueKeys: string[], query: RequestData): Promise => { + handleBulkChangeIssues = (issueKeys: string[], query: RequestData) => { //For now we only check for issue severity change. this.list .filter((i) => issueKeys.includes(i.issue.key)) .forEach((data) => { data.issue.severity = query.set_severity; }); - return this.reply(undefined); + return this.reply({}); }; handleGetIssueFlowSnippets = (issueKey: string): Promise> => { @@ -746,11 +740,6 @@ export default class IssuesServiceMock { (item) => !query.createdAfter || new Date(item.issue.creationDate) >= new Date(query.createdAfter) ) - .filter( - (item) => - !query.characteristics || - query.characteristics.split(',').includes(item.issue.characteristic) - ) .filter((item) => !query.types || query.types.split(',').includes(item.issue.type)) .filter( (item) => !query.severities || query.severities.split(',').includes(item.issue.severity) @@ -927,10 +916,7 @@ export default class IssuesServiceMock { }; handleSearchUsers = () => { - return this.reply({ - paging: mockPaging({ pageIndex: 1, pageSize: 5, total: 1 }), - users: [mockUser({ login: 'luke', name: 'Skywalker' })], - }); + return this.reply({ users: [mockLoggedInUser()] }); }; handleSearchIssueTags = () => { diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx index 1a5c8fdbdfc..7ffcfb03c8d 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; import { translate } from '../../../helpers/l10n'; import { Dict, MeasureEnhanced } from '../../../types/types'; -import { KNOWN_DOMAINS, PROJECT_OVERVEW, Query, groupByDomains } from '../utils'; +import { groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils'; import DomainFacet from './DomainFacet'; import ProjectOverviewFacet from './ProjectOverviewFacet'; diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx index 9c26eaf7b00..ac00a46ee7e 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx @@ -274,7 +274,6 @@ describe('issues app', () => { await waitOnDataLoaded(); // Ensure issue type filter is unchecked - await user.click(ui.typeFacet.get()); expect(ui.codeSmellIssueTypeFilter.get()).not.toBeChecked(); expect(ui.vulnerabilityIssueTypeFilter.get()).not.toBeChecked(); expect(ui.issueItem1.get()).toBeInTheDocument(); @@ -328,21 +327,7 @@ describe('issues app', () => { renderIssueApp(); await waitOnDataLoaded(); - // Select a characteristic - await user.click(ui.clearCharacteristicFilter.get()); - expect(ui.issueItem1.query()).not.toBeInTheDocument(); - expect(ui.issueItem2.get()).toBeInTheDocument(); - - // Clicking on same filter should uncheck it - await user.click(ui.clearCharacteristicFilter.get()); - expect(ui.issueItem1.get()).toBeInTheDocument(); - expect(ui.issueItem2.get()).toBeInTheDocument(); - - // Select clarity characteristic (should make the first issue disappear) - await user.click(ui.clearCharacteristicFilter.get()); - - // Select only code smells - await user.click(ui.typeFacet.get()); + // Select only code smells (should make the first issue disappear) await user.click(ui.codeSmellIssueTypeFilter.get()); // Select code smells + major severity @@ -410,7 +395,6 @@ describe('issues app', () => { expect(ui.issueItem7.get()).toBeInTheDocument(); // Clear filters one by one - await user.click(ui.clearFitForDevelopmentFacet.get()); await user.click(ui.clearIssueTypeFacet.get()); await user.click(ui.clearSeverityFacet.get()); await user.click(ui.clearScopeFacet.get()); @@ -918,14 +902,11 @@ describe('redirects', () => { expect(screen.getByText('/security_hotspots?assignedToMe=false')).toBeInTheDocument(); }); - it('should filter out hotspots', async () => { - const user = userEvent.setup(); + it('should filter out hotspots', () => { renderProjectIssuesApp( `project/issues?types=${IssueType.SecurityHotspot},${IssueType.CodeSmell}` ); - await waitOnDataLoaded(); - await user.click(ui.typeFacet.get()); expect( screen.getByRole('checkbox', { name: `issue.type.${IssueType.CodeSmell}` }) ).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts index 01dd613a71a..9598f118904 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts @@ -40,7 +40,6 @@ describe('serialize/deserialize', () => { assigned: true, assignees: ['a', 'b'], author: ['a', 'b'], - characteristics: ['a', 'b'], createdAfter: new Date(1000000), createdAt: 'a', createdBefore: new Date(1000000), @@ -72,7 +71,6 @@ describe('serialize/deserialize', () => { ).toStrictEqual({ assignees: 'a,b', author: ['a', 'b'], - characteristics: 'a,b', createdAt: 'a', createdBefore: '1970-01-01', createdAfter: '1970-01-01', diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index dc3e038eb77..ffad87ea9bb 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -19,7 +19,7 @@ */ import styled from '@emotion/styled'; import classNames from 'classnames'; -import { debounce, get, keyBy, omit, set, without } from 'lodash'; +import { debounce, keyBy, omit, without } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { FormattedMessage } from 'react-intl'; @@ -69,7 +69,6 @@ import { ASSIGNEE_ME, Facet, FetchIssuesPromise, - IssueCharacteristicFitFor, ReferencedComponent, ReferencedLanguage, ReferencedRule, @@ -83,7 +82,6 @@ import ConciseIssuesListHeader from '../conciseIssuesList/ConciseIssuesListHeade import Sidebar from '../sidebar/Sidebar'; import '../styles.css'; import { - OpenFacets, Query, STANDARDS, areMyIssuesSelected, @@ -129,7 +127,7 @@ export interface State { loadingMore: boolean; locationsNavigator: boolean; myIssues: boolean; - openFacets: OpenFacets; + openFacets: Dict; openIssue?: Issue; openPopup?: { issue: string; name: string }; openRuleDetails?: RuleDetails; @@ -169,19 +167,16 @@ export class App extends React.PureComponent { locationsNavigator: false, myIssues: areMyIssuesSelected(props.location.query), openFacets: { - characteristics: { - [IssueCharacteristicFitFor.Production]: true, - [IssueCharacteristicFitFor.Development]: true, - }, - severities: true, owaspTop10: shouldOpenStandardsChildFacet({}, query, SecurityStandard.OWASP_TOP10), 'owaspTop10-2021': shouldOpenStandardsChildFacet( {}, query, SecurityStandard.OWASP_TOP10_2021 ), + severities: true, sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet({}, query), standards: shouldOpenStandardsFacet({}, query), + types: true, }, query, referencedComponentsById: {}, @@ -705,41 +700,32 @@ export class App extends React.PureComponent { })); }; - /** - * @param property Facet property within openFacets. Can be a path, e.g. 'characteristics.PRODUCTION' - */ - handleFacetToggle = async (property: string) => { - const willOpenProperty = !get(this.state.openFacets, property); - const newState = { - loadingFacets: this.state.loadingFacets, - openFacets: { ...this.state.openFacets }, - }; - set(newState.openFacets, property, willOpenProperty); - - // Try to open sonarsource security "subfacet" by default if the standard facet is open - if (property === STANDARDS && willOpenProperty) { - newState.openFacets.sonarsourceSecurity = shouldOpenSonarSourceSecurityFacet( - newState.openFacets, - this.state.query - ); - // Force loading of sonarsource security facet data - property = newState.openFacets.sonarsourceSecurity ? 'sonarsourceSecurity' : property; - } - - // No need to load facets data for standard facet - if (property !== STANDARDS) { - newState.loadingFacets[property] = true; - } + handleFacetToggle = (property: string) => { + this.setState((state) => { + const willOpenProperty = !state.openFacets[property]; + const newState = { + loadingFacets: state.loadingFacets, + openFacets: { ...state.openFacets, [property]: willOpenProperty }, + }; + + // Try to open sonarsource security "subfacet" by default if the standard facet is open + if (willOpenProperty && property === STANDARDS) { + newState.openFacets.sonarsourceSecurity = shouldOpenSonarSourceSecurityFacet( + newState.openFacets, + state.query + ); + // Force loading of sonarsource security facet data + property = newState.openFacets.sonarsourceSecurity ? 'sonarsourceSecurity' : property; + } - this.setState(newState); + // No need to load facets data for standard facet + if (property !== STANDARDS && !state.facets[property]) { + newState.loadingFacets[property] = true; + this.fetchFacet(property); + } - // No need to load facets data for standard facet - if (property !== STANDARDS) { - // Fetch facet from the backend, only keeping first level of the property, - // eg will send 'characteristics' for property 'characteristics.PRODUCTION' - const facetName = property.split('.')[0]; - await this.fetchFacet(facetName); - } + return newState; + }); }; handleReset = () => { diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx deleted file mode 100644 index 94efb0f82cd..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx +++ /dev/null @@ -1,172 +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 { orderBy, without } from 'lodash'; -import * as React from 'react'; -import FacetBox from '../../../components/facet/FacetBox'; -import FacetHeader from '../../../components/facet/FacetHeader'; -import FacetItem from '../../../components/facet/FacetItem'; -import FacetItemsList from '../../../components/facet/FacetItemsList'; -import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; -import IssueTypeIcon from '../../../components/icons/IssueTypeIcon'; -import { translate } from '../../../helpers/l10n'; -import { ISSUE_CHARACTERISTIC_TO_FIT_FOR, IssueCharacteristic } from '../../../types/issues'; -import { Dict } from '../../../types/types'; -import { Query, formatFacetStat } from '../utils'; - -interface Props { - fetching: boolean; - onChange: (changes: Partial) => void; - onToggle: (property: string) => void; - open: boolean; - stats: Dict | undefined; - fitFor: string; - characteristics: IssueCharacteristic[]; -} - -export default class CharacteristicFacet extends React.PureComponent { - property = 'characteristics'; - - static defaultProps = { - open: true, - }; - - handleItemClick = (itemValue: IssueCharacteristic, multiple: boolean) => { - const { characteristics } = this.props; - if (multiple) { - const newValue = orderBy( - characteristics.includes(itemValue) - ? without(characteristics, itemValue) - : [...characteristics, itemValue] - ); - this.props.onChange({ [this.property]: newValue }); - return; - } - - // Append if there is no characteristic selected yet in this fitFor - const selectedFitFor = characteristics.filter( - (characteristic) => ISSUE_CHARACTERISTIC_TO_FIT_FOR[characteristic] === this.props.fitFor - ); - if (selectedFitFor.length === 0) { - this.props.onChange({ [this.property]: [...characteristics, itemValue] }); - return; - } - - // If clicking on the only selected characteristic, clear it - if (selectedFitFor.length === 1 && selectedFitFor[0] === itemValue) { - this.props.onChange({ - [this.property]: characteristics.filter( - (characteristic) => ISSUE_CHARACTERISTIC_TO_FIT_FOR[characteristic] !== this.props.fitFor - ), - }); - return; - } - - // If there is already a selection for this fitFor, replace it - this.props.onChange({ - [this.property]: characteristics - .filter( - (characteristic) => ISSUE_CHARACTERISTIC_TO_FIT_FOR[characteristic] !== this.props.fitFor - ) - .concat([itemValue]), - }); - }; - - handleHeaderClick = () => { - this.props.onToggle(`${this.property}.${this.props.fitFor}`); - }; - - handleClear = () => { - // Clear characteristics for this fitFor - this.props.onChange({ - [this.property]: this.props.characteristics.filter( - (characteristic) => ISSUE_CHARACTERISTIC_TO_FIT_FOR[characteristic] !== this.props.fitFor - ), - }); - }; - - getStat(characteristic: string) { - const { stats } = this.props; - return stats ? stats[characteristic] : undefined; - } - - isFacetItemActive(characteristic: IssueCharacteristic) { - return this.props.characteristics.includes(characteristic); - } - - renderItem = (characteristic: IssueCharacteristic) => { - const active = this.isFacetItemActive(characteristic); - const stat = this.getStat(characteristic); - - return ( - - {' '} - {translate('issue.characteristic', characteristic)} - - } - onClick={this.handleItemClick} - stat={formatFacetStat(stat)} - value={characteristic} - /> - ); - }; - - render() { - const { characteristics, fitFor } = this.props; - const values = characteristics - .filter((characteristic) => ISSUE_CHARACTERISTIC_TO_FIT_FOR[characteristic] === fitFor) - .map((characteristic) => translate('issue.characteristic', characteristic)); - - const availableCharacteristics = Object.entries(ISSUE_CHARACTERISTIC_TO_FIT_FOR) - .filter(([, value]) => value === fitFor) - .map(([key]) => key as IssueCharacteristic); - - const headerId = `facet_${this.property}_${fitFor}`; - - return ( - - - - {this.props.open && ( - <> - - {availableCharacteristics.map(this.renderItem)} - - - - )} - - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx index 8f25db0785b..90460b7b8aa 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx @@ -30,8 +30,6 @@ import { } from '../../../types/component'; import { Facet, - IssueCharacteristic, - IssueCharacteristicFitFor, ReferencedComponent, ReferencedLanguage, ReferencedRule, @@ -39,10 +37,9 @@ import { import { GlobalSettingKeys } from '../../../types/settings'; import { Component, Dict } from '../../../types/types'; import { UserBase } from '../../../types/users'; -import { OpenFacets, Query } from '../utils'; +import { Query } from '../utils'; import AssigneeFacet from './AssigneeFacet'; import AuthorFacet from './AuthorFacet'; -import CharacteristicFacet from './CharacteristicFacet'; import CreationDateFacet from './CreationDateFacet'; import DirectoryFacet from './DirectoryFacet'; import FileFacet from './FileFacet'; @@ -63,13 +60,13 @@ export interface Props { branchLike?: BranchLike; component: Component | undefined; createdAfterIncludesTime: boolean; - facets: Dict; + facets: Dict; loadSearchResultCount: (property: string, changes: Partial) => Promise; loadingFacets: Dict; myIssues: boolean; onFacetToggle: (property: string) => void; onFilterChange: (changes: Partial) => void; - openFacets: OpenFacets; + openFacets: Dict; query: Query; referencedComponentsById: Dict; referencedComponentsByKey: Dict; @@ -150,23 +147,13 @@ export class Sidebar extends React.PureComponent { newCodeSelected={query.inNewCodePeriod} /> )} - - { severities={query.severities} stats={facets.severities} /> - { renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.Application }) }); expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ - 'issues.facet.characteristics.PRODUCTION', - 'issues.facet.characteristics.DEVELOPMENT', - 'issues.facet.severities', 'issues.facet.types', + 'issues.facet.severities', 'issues.facet.scopes', 'issues.facet.resolutions', 'issues.facet.statuses', @@ -52,10 +50,8 @@ it('should render correct facets for Application', () => { it('should render correct facets for Portfolio', () => { renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.Portfolio }) }); expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ - 'issues.facet.characteristics.PRODUCTION', - 'issues.facet.characteristics.DEVELOPMENT', - 'issues.facet.severities', 'issues.facet.types', + 'issues.facet.severities', 'issues.facet.scopes', 'issues.facet.resolutions', 'issues.facet.statuses', @@ -74,10 +70,8 @@ it('should render correct facets for Portfolio', () => { it('should render correct facets for SubPortfolio', () => { renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.SubPortfolio }) }); expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([ - 'issues.facet.characteristics.PRODUCTION', - 'issues.facet.characteristics.DEVELOPMENT', - 'issues.facet.severities', 'issues.facet.types', + 'issues.facet.severities', 'issues.facet.scopes', 'issues.facet.resolutions', 'issues.facet.statuses', diff --git a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx index 4e098253fcf..7a9b2d9ff73 100644 --- a/server/sonar-web/src/main/js/apps/issues/test-utils.tsx +++ b/server/sonar-web/src/main/js/apps/issues/test-utils.tsx @@ -51,11 +51,6 @@ export const ui = { issueItem7: byRole('region', { name: 'Issue with tags' }), issueItem8: byRole('region', { name: 'Issue on page 2' }), - clearFitForDevelopmentFacet: byRole('button', { - name: 'clear_x_filter.issues.facet.characteristics.DEVELOPMENT', - }), - clearCharacteristicFilter: byRole('checkbox', { name: 'issue.characteristic.CLEAR' }), - typeFacet: byRole('button', { name: 'issues.facet.types' }), clearIssueTypeFacet: byRole('button', { name: 'clear_x_filter.issues.facet.types' }), codeSmellIssueTypeFilter: byRole('checkbox', { name: 'issue.type.CODE_SMELL' }), vulnerabilityIssueTypeFilter: byRole('checkbox', { name: 'issue.type.VULNERABILITY' }), 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 c6b12923b24..400718aa381 100644 --- a/server/sonar-web/src/main/js/apps/issues/utils.ts +++ b/server/sonar-web/src/main/js/apps/issues/utils.ts @@ -44,7 +44,6 @@ export interface Query { assigned: boolean; assignees: string[]; author: string[]; - characteristics: string[]; createdAfter: Date | undefined; createdAt: string; createdBefore: Date | undefined; @@ -74,10 +73,6 @@ export interface Query { types: string[]; } -export type OpenFacets = Dict> & { - characteristics?: Dict; -}; - export const STANDARDS = 'standards'; // allow sorting by CREATION_DATE only @@ -89,7 +84,6 @@ export function parseQuery(query: RawQuery): Query { assigned: parseAsBoolean(query.assigned), assignees: parseAsArray(query.assignees, parseAsString), author: isArray(query.author) ? query.author : [query.author].filter(isDefined), - characteristics: parseAsArray(query.characteristics, parseAsString), createdAfter: parseAsDate(query.createdAfter), createdAt: parseAsString(query.createdAt), createdBefore: parseAsDate(query.createdBefore), @@ -136,7 +130,6 @@ export function serializeQuery(query: Query): RawQuery { assigned: query.assigned ? undefined : 'false', assignees: serializeStringArray(query.assignees), author: query.author, - characteristics: serializeStringArray(query.characteristics), createdAfter: serializeDateShort(query.createdAfter), createdAt: serializeString(query.createdAt), createdBefore: serializeDateShort(query.createdBefore), @@ -251,16 +244,19 @@ export function allLocationsEmpty( return getLocations(issue, selectedFlowIndex).every((location) => !location.msg); } -export function shouldOpenStandardsFacet(openFacets: OpenFacets, query: Partial): boolean { +export function shouldOpenStandardsFacet( + openFacets: Dict, + query: Partial +): boolean { return ( - !!openFacets[STANDARDS] || + openFacets[STANDARDS] || isFilteredBySecurityIssueTypes(query) || isOneStandardChildFacetOpen(openFacets, query) ); } export function shouldOpenStandardsChildFacet( - openFacets: OpenFacets, + openFacets: Dict, query: Partial, standardType: | SecurityStandard.CWE @@ -271,13 +267,13 @@ export function shouldOpenStandardsChildFacet( const filter = query[standardType]; return ( openFacets[STANDARDS] !== false && - (!!openFacets[standardType] || + (openFacets[standardType] || (standardType !== SecurityStandard.CWE && filter !== undefined && filter.length > 0)) ); } export function shouldOpenSonarSourceSecurityFacet( - openFacets: OpenFacets, + openFacets: Dict, query: Partial ): boolean { // Open it by default if the parent is open, and no other standard is open. @@ -291,7 +287,7 @@ function isFilteredBySecurityIssueTypes(query: Partial): boolean { return query.types !== undefined && query.types.includes('VULNERABILITY'); } -function isOneStandardChildFacetOpen(openFacets: OpenFacets, query: Partial): boolean { +function isOneStandardChildFacetOpen(openFacets: Dict, query: Partial): boolean { return [SecurityStandard.OWASP_TOP10, SecurityStandard.CWE, SecurityStandard.SONARSOURCE].some( ( standardType: diff --git a/server/sonar-web/src/main/js/helpers/mocks/issues.ts b/server/sonar-web/src/main/js/helpers/mocks/issues.ts index 2f0e74cd3fb..7eb14844271 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/issues.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/issues.ts @@ -71,7 +71,6 @@ export function mockQuery(overrides: Partial = {}): Query { assigned: false, assignees: [], author: [], - characteristics: [], createdAfter: undefined, createdAt: '', createdBefore: undefined, diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index cbcac183961..79abd1db97a 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1025,8 +1025,6 @@ issue.changelog.field.file=File # #------------------------------------------------------------------------------ issues.facet.period=Period -issues.facet.characteristics.DEVELOPMENT=Fit for Development -issues.facet.characteristics.PRODUCTION=Fit for Production issues.facet.types=Type issues.facet.severities=Severity issues.facet.scopes=Scope -- 2.39.5