aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2023-04-27 11:49:31 +0200
committersonartech <sonartech@sonarsource.com>2023-04-28 20:02:58 +0000
commit86680bd15caaf685761cdb66e785d57dfb28e838 (patch)
tree642088697f2deda4b2b554210f70c79d64f18b52 /server/sonar-web/src/main/js
parentbe0b33fcdfc6059816109e141752ff16775a5202 (diff)
downloadsonarqube-86680bd15caaf685761cdb66e785d57dfb28e838.tar.gz
sonarqube-86680bd15caaf685761cdb66e785d57dfb28e838.zip
Revert "SONAR-19069 Add Fit for Development and Fit for Production facets"
This reverts commit b97056771412fd5b5c339e3b2e3086b784c29b00.
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/api/issues.ts1
-rw-r--r--server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts46
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx70
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/CharacteristicFacet.tsx172
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx37
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/issues/test-utils.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/utils.ts22
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/issues.ts1
12 files changed, 67 insertions, 326 deletions
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<void> => {
+ 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<Dict<SnippetsByComponent>> => {
@@ -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<boolean>;
openIssue?: Issue;
openPopup?: { issue: string; name: string };
openRuleDetails?: RuleDetails;
@@ -169,19 +167,16 @@ export class App extends React.PureComponent<Props, State> {
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<Props, State> {
}));
};
- /**
- * @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<Query>) => void;
- onToggle: (property: string) => void;
- open: boolean;
- stats: Dict<number> | undefined;
- fitFor: string;
- characteristics: IssueCharacteristic[];
-}
-
-export default class CharacteristicFacet extends React.PureComponent<Props> {
- 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 (
- <FacetItem
- active={active}
- key={characteristic}
- name={
- <span className="display-flex-center">
- <IssueTypeIcon className="little-spacer-right" query={characteristic} />{' '}
- {translate('issue.characteristic', characteristic)}
- </span>
- }
- 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 (
- <FacetBox property={this.property}>
- <FacetHeader
- fetching={this.props.fetching}
- id={headerId}
- name={translate('issues.facet.characteristics', fitFor)}
- onClear={this.handleClear}
- onClick={this.handleHeaderClick}
- open={this.props.open}
- values={values}
- />
-
- {this.props.open && (
- <>
- <FacetItemsList labelledby={headerId}>
- {availableCharacteristics.map(this.renderItem)}
- </FacetItemsList>
- <MultipleSelectionHint
- options={Object.keys(availableCharacteristics).length}
- values={values.length}
- />
- </>
- )}
- </FacetBox>
- );
- }
-}
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<Facet>;
+ facets: Dict<Facet | undefined>;
loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>;
loadingFacets: Dict<boolean>;
myIssues: boolean;
onFacetToggle: (property: string) => void;
onFilterChange: (changes: Partial<Query>) => void;
- openFacets: OpenFacets;
+ openFacets: Dict<boolean>;
query: Query;
referencedComponentsById: Dict<ReferencedComponent>;
referencedComponentsByKey: Dict<ReferencedComponent>;
@@ -150,23 +147,13 @@ export class Sidebar extends React.PureComponent<Props> {
newCodeSelected={query.inNewCodePeriod}
/>
)}
- <CharacteristicFacet
- fetching={this.props.loadingFacets.characteristics === true}
- onChange={this.props.onFilterChange}
- onToggle={this.props.onFacetToggle}
- open={openFacets.characteristics?.[IssueCharacteristicFitFor.Production]}
- stats={facets.characteristics}
- fitFor={IssueCharacteristicFitFor.Production}
- characteristics={query.characteristics as IssueCharacteristic[]}
- />
- <CharacteristicFacet
- fetching={this.props.loadingFacets.characteristics === true}
+ <TypeFacet
+ fetching={this.props.loadingFacets.types === true}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
- open={openFacets.characteristics?.[IssueCharacteristicFitFor.Development]}
- stats={facets.characteristics}
- fitFor={IssueCharacteristicFitFor.Development}
- characteristics={query.characteristics as IssueCharacteristic[]}
+ open={!!openFacets.types}
+ stats={facets.types}
+ types={query.types}
/>
<SeverityFacet
fetching={this.props.loadingFacets.severities === true}
@@ -176,14 +163,6 @@ export class Sidebar extends React.PureComponent<Props> {
severities={query.severities}
stats={facets.severities}
/>
- <TypeFacet
- fetching={this.props.loadingFacets.types === true}
- onChange={this.props.onFilterChange}
- onToggle={this.props.onFacetToggle}
- open={!!openFacets.types}
- stats={facets.types}
- types={query.types}
- />
<ScopeFacet
fetching={this.props.loadingFacets.scopes === true}
onChange={this.props.onFilterChange}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx
index a02ea0f3be6..52fdec70fee 100644
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx
@@ -30,10 +30,8 @@ import { Sidebar } from '../Sidebar';
it('should render correct facets for Application', () => {
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<boolean | Dict<boolean>> & {
- characteristics?: Dict<boolean>;
-};
-
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<Query>): boolean {
+export function shouldOpenStandardsFacet(
+ openFacets: Dict<boolean>,
+ query: Partial<Query>
+): boolean {
return (
- !!openFacets[STANDARDS] ||
+ openFacets[STANDARDS] ||
isFilteredBySecurityIssueTypes(query) ||
isOneStandardChildFacetOpen(openFacets, query)
);
}
export function shouldOpenStandardsChildFacet(
- openFacets: OpenFacets,
+ openFacets: Dict<boolean>,
query: Partial<Query>,
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<boolean>,
query: Partial<Query>
): boolean {
// Open it by default if the parent is open, and no other standard is open.
@@ -291,7 +287,7 @@ function isFilteredBySecurityIssueTypes(query: Partial<Query>): boolean {
return query.types !== undefined && query.types.includes('VULNERABILITY');
}
-function isOneStandardChildFacetOpen(openFacets: OpenFacets, query: Partial<Query>): boolean {
+function isOneStandardChildFacetOpen(openFacets: Dict<boolean>, query: Partial<Query>): 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> = {}): Query {
assigned: false,
assignees: [],
author: [],
- characteristics: [],
createdAfter: undefined,
createdAt: '',
createdBefore: undefined,