aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorstanislavh <stanislav.honcharov@sonarsource.com>2023-10-27 15:51:27 +0200
committersonartech <sonartech@sonarsource.com>2023-11-08 20:02:52 +0000
commita1be2cd1286ff3a24fc27d9c9a387069f5eafb91 (patch)
tree72f1828893ff5829da5a2dcb86128105b0911c49
parent01a084c37da6150434a250334d933ea0443c06a2 (diff)
downloadsonarqube-a1be2cd1286ff3a24fc27d9c9a387069f5eafb91.tar.gz
sonarqube-a1be2cd1286ff3a24fc27d9c9a387069f5eafb91.zip
SONAR-20871 Add new status facet in issues list
-rw-r--r--server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts21
-rw-r--r--server/sonar-web/src/main/js/api/mocks/data/issues.ts3
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx3
-rw-r--r--server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts75
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx102
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx143
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/Sidebar.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/SimpleStatusFacet.tsx109
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx139
-rw-r--r--server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/issues/test-utils.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/utils.ts83
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPanel.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.ts5
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx3
-rw-r--r--server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts4
-rw-r--r--server/sonar-web/src/main/js/components/shared/utils.ts49
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts5
-rw-r--r--server/sonar-web/src/main/js/helpers/constants.ts16
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/issues.ts4
-rw-r--r--server/sonar-web/src/main/js/helpers/query.ts7
-rw-r--r--server/sonar-web/src/main/js/helpers/testMocks.ts4
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.ts3
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties9
36 files changed, 381 insertions, 528 deletions
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 581b44578d2..48027575795 100644
--- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
@@ -20,13 +20,7 @@
import { cloneDeep, uniqueId } from 'lodash';
import { RuleDescriptionSections } from '../../apps/coding-rules/rule';
-import {
- ISSUE_TYPES,
- RESOLUTIONS,
- SEVERITIES,
- SOURCE_SCOPES,
- STATUSES,
-} from '../../helpers/constants';
+import { ISSUE_TYPES, SEVERITIES, SIMPLE_STATUSES, SOURCE_SCOPES } from '../../helpers/constants';
import { mockIssueAuthors, mockIssueChangelog } from '../../helpers/mocks/issues';
import { RequestData } from '../../helpers/request';
import { getStandards } from '../../helpers/security-standard';
@@ -336,9 +330,8 @@ export default class IssuesServiceMock {
property: name,
values: (
{
- resolutions: RESOLUTIONS,
severities: SEVERITIES,
- statuses: STATUSES,
+ simpleStatuses: SIMPLE_STATUSES,
types: ISSUE_TYPES,
scopes: SOURCE_SCOPES.map(({ scope }) => scope),
projects: ['org.project1', 'org.project2'],
@@ -389,6 +382,11 @@ export default class IssuesServiceMock {
// Filter list (only supports assignee, type and severity)
const filteredList = this.list
+ .filter(
+ (item) =>
+ !query.simpleStatuses ||
+ query.simpleStatuses.split(',').includes(item.issue.simpleStatus),
+ )
.filter((item) => {
if (!query.cleanCodeAttributeCategories) {
return true;
@@ -448,15 +446,10 @@ export default class IssuesServiceMock {
(item) => !query.severities || query.severities.split(',').includes(item.issue.severity),
)
.filter((item) => !query.scopes || query.scopes.split(',').includes(item.issue.scope))
- .filter((item) => !query.statuses || query.statuses.split(',').includes(item.issue.status))
.filter((item) => !query.projects || query.projects.split(',').includes(item.issue.project))
.filter((item) => !query.rules || query.rules.split(',').includes(item.issue.rule))
.filter(
(item) =>
- !query.resolutions || query.resolutions.split(',').includes(item.issue.resolution),
- )
- .filter(
- (item) =>
!query.inNewCodePeriod || new Date(item.issue.creationDate) > new Date('2023-01-10'),
)
.filter((item) => {
diff --git a/server/sonar-web/src/main/js/api/mocks/data/issues.ts b/server/sonar-web/src/main/js/api/mocks/data/issues.ts
index f3d42d750bf..116e77c6771 100644
--- a/server/sonar-web/src/main/js/api/mocks/data/issues.ts
+++ b/server/sonar-web/src/main/js/api/mocks/data/issues.ts
@@ -317,10 +317,10 @@ export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY): IssueDa
impacts: [
{ softwareQuality: SoftwareQuality.Security, severity: SoftwareImpactSeverity.High },
],
- ruleDescriptionContextKey: 'spring',
resolution: IssueResolution.Unresolved,
status: IssueStatus.Open,
simpleStatus: IssueSimpleStatus.Open,
+ ruleDescriptionContextKey: 'spring',
}),
snippets: keyBy(
[
@@ -347,6 +347,7 @@ export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY): IssueDa
},
resolution: IssueResolution.Fixed,
status: IssueStatus.Confirmed,
+ simpleStatus: IssueSimpleStatus.Confirmed,
}),
snippets: keyBy(
[
diff --git a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
index e37b2475bc6..c9c1f352fc2 100644
--- a/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
+++ b/server/sonar-web/src/main/js/app/components/__tests__/ComponentContainer-test.tsx
@@ -87,7 +87,7 @@ it('should render the component nav correctly for portfolio', async () => {
expect(await ui.portfolioTitle.find()).toHaveAttribute('href', '/portfolio?id=portfolioKey');
expect(ui.issuesPageLink.get()).toHaveAttribute(
'href',
- '/project/issues?id=portfolioKey&resolved=false',
+ '/project/issues?id=portfolioKey&simpleStatuses=OPEN%2CCONFIRMED',
);
expect(ui.measuresPageLink.get()).toHaveAttribute('href', '/component_measures?id=portfolioKey');
expect(ui.activityPageLink.get()).toHaveAttribute('href', '/project/activity?id=portfolioKey');
@@ -119,7 +119,7 @@ it('should render the component nav correctly for projects', async () => {
expect(ui.overviewPageLink.get()).toHaveAttribute('href', '/dashboard?id=project-key');
expect(ui.issuesPageLink.get()).toHaveAttribute(
'href',
- '/project/issues?id=project-key&resolved=false',
+ '/project/issues?id=project-key&simpleStatuses=OPEN%2CCONFIRMED',
);
expect(ui.hotspotsPageLink.get()).toHaveAttribute('href', '/security_hotspots?id=project-key');
expect(ui.measuresPageLink.get()).toHaveAttribute('href', '/component_measures?id=project-key');
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
index 03d341ff571..0ebe49b816c 100644
--- a/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/component/Menu.tsx
@@ -27,6 +27,7 @@ import {
} from 'design-system';
import * as React from 'react';
import Tooltip from '../../../../components/controls/Tooltip';
+import { DEFAULT_ISSUES_QUERY } from '../../../../components/shared/utils';
import { getBranchLikeQuery, isPullRequest } from '../../../../helpers/branch-like';
import { hasMessage, translate, translateWithParameters } from '../../../../helpers/l10n';
import { getPortfolioUrl, getProjectQueryUrl } from '../../../../helpers/urls';
@@ -183,7 +184,7 @@ export function Menu(props: Props) {
return renderMenuLink({
label: translate('issues.page'),
pathname: '/project/issues',
- additionalQueryParams: { resolved: 'false' },
+ additionalQueryParams: DEFAULT_ISSUES_QUERY,
});
};
diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
index 6c81cdc7325..69d0ba9648b 100644
--- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
+++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavMenu.tsx
@@ -23,6 +23,7 @@ import * as React from 'react';
import { NavLink } from 'react-router-dom';
import { isMySet } from '../../../../apps/issues/utils';
import Link from '../../../../components/common/Link';
+import { DEFAULT_ISSUES_QUERY } from '../../../../components/shared/utils';
import { translate } from '../../../../helpers/l10n';
import { getQualityGatesUrl } from '../../../../helpers/urls';
import { AppState } from '../../../../types/appstate';
@@ -71,8 +72,8 @@ class GlobalNavMenu extends React.PureComponent<Props> {
renderIssuesLink() {
const search = (
this.props.currentUser.isLoggedIn && isMySet()
- ? new URLSearchParams({ resolved: 'false', myIssues: 'true' })
- : new URLSearchParams({ resolved: 'false' })
+ ? new URLSearchParams({ myIssues: 'true', ...DEFAULT_ISSUES_QUERY })
+ : new URLSearchParams(DEFAULT_ISSUES_QUERY)
).toString();
return (
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
index f6f1431dd5f..9e66325c971 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsIssues.tsx
@@ -27,6 +27,7 @@ import withAvailableFeatures, {
WithAvailableFeaturesProps,
} from '../../../app/components/available-features/withAvailableFeatures';
import Tooltip from '../../../components/controls/Tooltip';
+import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import { translate } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { getIssuesUrl } from '../../../helpers/urls';
@@ -81,7 +82,7 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> {
this.setState({ loading: true });
getFacet(
{
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
rules: key,
},
FacetName.Projects,
@@ -139,7 +140,7 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> {
if (total === undefined) {
return null;
}
- const path = getIssuesUrl({ resolved: 'false', rules: key });
+ const path = getIssuesUrl({ ...DEFAULT_ISSUES_QUERY, rules: key });
const totalItem = (
<span className="little-spacer-left">
@@ -163,7 +164,7 @@ export class RuleDetailsIssues extends React.PureComponent<Props, State> {
ruleDetails: { key },
} = this.props;
- const path = getIssuesUrl({ resolved: 'false', rules: key, projects: project.key });
+ const path = getIssuesUrl({ ...DEFAULT_ISSUES_QUERY, rules: key, projects: project.key });
return (
<TableRow key={project.key}>
<ContentCell>{project.name}</ContentCell>
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx
index 527b152307d..ce0e9931252 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-Filtering-it.tsx
@@ -86,17 +86,11 @@ describe('issues app filtering', () => {
await user.click(ui.mainScopeFilter.get());
expect(ui.issueItem4.query()).not.toBeInTheDocument();
- // Resolution
- await user.click(ui.resolutionFacet.get());
- await user.click(ui.fixedResolutionFilter.get());
- expect(ui.issueItem2.query()).not.toBeInTheDocument();
-
// Check that filters were applied as expected
expect(ui.issueItem6.get()).toBeInTheDocument();
// Status
- await user.click(ui.statusFacet.get());
-
+ await user.click(ui.simpleStatusFacet.get());
await user.click(ui.openStatusFilter.get());
expect(ui.issueItem6.query()).not.toBeInTheDocument(); // Issue 6 should vanish
@@ -106,9 +100,6 @@ describe('issues app filtering', () => {
await user.keyboard('{/Control}');
expect(ui.issueItem6.get()).toBeInTheDocument(); // Issue 6 should come back
- // Clear resolution filter
- await user.click(ui.clearResolutionFacet.get());
-
// Rule
await user.click(ui.ruleFacet.get());
await user.click(screen.getByRole('checkbox', { name: 'other' }));
@@ -154,7 +145,6 @@ describe('issues app filtering', () => {
await user.click(ui.clearIssueTypeFacet.get());
await user.click(ui.clearSeverityFacet.get());
await user.click(ui.clearScopeFacet.get());
- await user.click(ui.clearStatusFacet.get());
await user.click(ui.clearRuleFacet.get());
await user.click(ui.clearTagFacet.get());
await user.click(ui.clearProjectFacet.get());
@@ -360,7 +350,7 @@ describe('issues app when reindexing', () => {
expect(ui.resolutionFacet.query()).not.toBeInTheDocument();
expect(ui.ruleFacet.query()).not.toBeInTheDocument();
expect(ui.scopeFacet.query()).not.toBeInTheDocument();
- expect(ui.statusFacet.query()).not.toBeInTheDocument();
+ expect(ui.simpleStatusFacet.query()).not.toBeInTheDocument();
expect(ui.tagFacet.query()).not.toBeInTheDocument();
// Indexation message
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 398f7b9b791..7f55fb9c1e2 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
@@ -22,6 +22,7 @@ import {
SoftwareImpactSeverity,
SoftwareQuality,
} from '../../../types/clean-code-taxonomy';
+import { IssueSimpleStatus } from '../../../types/issues';
import { SecurityStandard } from '../../../types/security';
import {
parseQuery,
@@ -62,15 +63,13 @@ describe('serialize/deserialize', () => {
'owaspAsvs-4.0': ['2'],
owaspAsvsLevel: '2',
projects: ['a', 'b'],
- resolutions: ['a', 'b'],
- resolved: true,
rules: ['a', 'b'],
sort: 'rules',
scopes: ['a', 'b'],
severities: ['a', 'b'],
inNewCodePeriod: true,
sonarsourceSecurity: ['a', 'b'],
- statuses: ['a', 'b'],
+ simpleStatuses: [IssueSimpleStatus.Accepted, IssueSimpleStatus.Confirmed],
tags: ['a', 'b'],
types: ['a', 'b'],
}),
@@ -97,14 +96,13 @@ describe('serialize/deserialize', () => {
'owaspAsvs-4.0': '2',
owaspAsvsLevel: '2',
projects: 'a,b',
- resolutions: 'a,b',
rules: 'a,b',
s: 'rules',
scopes: 'a,b',
inNewCodePeriod: 'true',
severities: 'a,b',
sonarsourceSecurity: 'a,b',
- statuses: 'a,b',
+ simpleStatuses: 'ACCEPTED,CONFIRMED',
tags: 'a,b',
types: 'a,b',
});
@@ -146,18 +144,79 @@ describe('serialize/deserialize', () => {
'pciDss-3.2': [],
'pciDss-4.0': [],
projects: [],
- resolutions: [],
- resolved: true,
rules: [],
scopes: [],
severities: ['CRITICAL', 'MAJOR'],
sonarsourceSecurity: [],
sort: '',
- statuses: [],
+ simpleStatuses: [],
tags: [],
types: [],
});
});
+
+ it('should map deprecated status and resolution query to new simple statuses', () => {
+ expect(parseQuery({ statuses: 'OPEN' }).simpleStatuses).toEqual([IssueSimpleStatus.Open]);
+ expect(parseQuery({ statuses: 'REOPENED' }).simpleStatuses).toEqual([IssueSimpleStatus.Open]);
+ expect(parseQuery({ statuses: 'CONFIRMED' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Confirmed,
+ ]);
+ expect(parseQuery({ statuses: 'RESOLVED' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Fixed,
+ IssueSimpleStatus.Accepted,
+ IssueSimpleStatus.FalsePositive,
+ ]);
+ expect(parseQuery({ statuses: 'OPEN,REOPENED' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Open,
+ ]);
+ expect(parseQuery({ statuses: 'OPEN,CONFIRMED' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Open,
+ IssueSimpleStatus.Confirmed,
+ ]);
+
+ // Resolutions
+ expect(parseQuery({ resolutions: 'FALSE-POSITIVE' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.FalsePositive,
+ ]);
+ expect(parseQuery({ resolutions: 'WONTFIX' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Accepted,
+ ]);
+ expect(parseQuery({ resolutions: 'REMOVED' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Fixed,
+ ]);
+ expect(parseQuery({ resolutions: 'REMOVED,WONTFIX,FALSE-POSITIVE' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Fixed,
+ IssueSimpleStatus.Accepted,
+ IssueSimpleStatus.FalsePositive,
+ ]);
+
+ // Both statuses and resolutions
+ expect(
+ parseQuery({ resolutions: 'FALSE-POSITIVE', statuses: 'RESOLVED' }).simpleStatuses,
+ ).toEqual([IssueSimpleStatus.FalsePositive]);
+ expect(parseQuery({ resolutions: 'WONTFIX', statuses: 'RESOLVED' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Accepted,
+ ]);
+
+ // With resolved=false
+ expect(
+ parseQuery({ resolutions: 'WONTFIX', statuses: 'RESOLVED', resolved: 'false' })
+ .simpleStatuses,
+ ).toEqual([IssueSimpleStatus.Accepted, IssueSimpleStatus.Open, IssueSimpleStatus.Confirmed]);
+ expect(parseQuery({ statuses: 'OPEN', resolved: 'false' }).simpleStatuses).toEqual([
+ IssueSimpleStatus.Open,
+ ]);
+
+ // With simple status
+ expect(
+ parseQuery({
+ resolutions: 'WONTFIX',
+ statuses: 'RESOLVED',
+ resolved: 'false',
+ simpleStatuses: 'FIXED',
+ }).simpleStatuses,
+ ).toEqual([IssueSimpleStatus.Fixed]);
+ });
});
describe('shouldOpenStandardsFacet', () => {
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 e2ed868e887..fffb416f365 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
@@ -51,6 +51,7 @@ import withIndexationGuard from '../../../components/hoc/withIndexationGuard';
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import IssueTabViewer from '../../../components/rules/IssueTabViewer';
import '../../../components/search-navigator.css';
+import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import Spinner from '../../../components/ui/Spinner';
import { fillBranchLike, getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-like';
import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication';
@@ -148,7 +149,6 @@ export interface State {
selectedLocationIndex?: number;
}
-const DEFAULT_QUERY = { resolved: 'false' };
const MAX_INITAL_FETCH = 1000;
const VARIANTS_FACET = 'codeVariants';
const ISSUES_PAGE_SIZE = 100;
@@ -700,7 +700,7 @@ export class App extends React.PureComponent<Props, State> {
isFiltered = () => {
const serialized = serializeQuery(this.state.query);
- return !areQueriesEqual(serialized, DEFAULT_QUERY);
+ return !areQueriesEqual(serialized, DEFAULT_ISSUES_QUERY);
};
getCheckedIssues = () => {
@@ -828,7 +828,7 @@ export class App extends React.PureComponent<Props, State> {
this.props.router.push({
pathname: this.props.location.pathname,
query: {
- ...DEFAULT_QUERY,
+ ...DEFAULT_ISSUES_QUERY,
...getBranchLikeQuery(this.props.branchLike),
id: this.props.component?.key,
myIssues: this.state.myIssues ? 'true' : undefined,
@@ -1156,7 +1156,6 @@ export class App extends React.PureComponent<Props, State> {
checked={this.state.checked}
component={component}
issues={issues}
- onFilterChange={this.handleFilterChange}
onIssueChange={this.handleIssueChange}
onIssueCheck={currentUser.isLoggedIn ? this.handleIssueCheck : undefined}
onIssueSelect={this.selectIssue}
@@ -1234,7 +1233,6 @@ export class App extends React.PureComponent<Props, State> {
{this.renderHeader({ openIssue, paging })}
<Spinner loading={loadingRule}>
- {/* eslint-disable-next-line local-rules/no-conditional-rendering-of-deferredspinner */}
{openIssue && openRuleDetails ? (
<IssueTabViewer
activityTabContent={
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
index ffc1c603931..2c526415fa6 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
@@ -19,18 +19,17 @@
*/
import { groupBy } from 'lodash';
import * as React from 'react';
+import IssueItem from '../../../components/issue/Issue';
import { BranchLike } from '../../../types/branch-like';
import { Component, Issue } from '../../../types/types';
-import { Query } from '../utils';
+
import ComponentBreadcrumbs from './ComponentBreadcrumbs';
-import ListItem from './ListItem';
interface Props {
branchLike: BranchLike | undefined;
checked: string[];
component: Component | undefined;
issues: Issue[];
- onFilterChange: (changes: Partial<Query>) => void;
onIssueChange: (issue: Issue) => void;
onIssueCheck: ((issueKey: string) => void) | undefined;
onIssueSelect: (issueKey: string) => void;
@@ -69,7 +68,7 @@ export default class IssuesList extends React.PureComponent<Props, State> {
</li>
<ul>
{issues.map((issue) => (
- <ListItem
+ <IssueItem
branchLike={branchLike}
checked={checked.includes(issue.key)}
issue={issue}
@@ -77,7 +76,6 @@ export default class IssuesList extends React.PureComponent<Props, State> {
onChange={this.props.onIssueChange}
onCheck={this.props.onIssueCheck}
onSelect={this.props.onIssueSelect}
- onFilterChange={this.props.onFilterChange}
onPopupToggle={this.props.onPopupToggle}
openPopup={openPopup && openPopup.issue === issue.key ? openPopup.name : undefined}
selected={selectedIssue != null && selectedIssue.key === issue.key}
diff --git a/server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx b/server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx
deleted file mode 100644
index 0dfaca0f545..00000000000
--- a/server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx
+++ /dev/null
@@ -1,102 +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 Issue from '../../../components/issue/Issue';
-import { BranchLike } from '../../../types/branch-like';
-import { Issue as TypeIssue } from '../../../types/types';
-import { Query } from '../utils';
-
-interface Props {
- branchLike: BranchLike | undefined;
- checked: boolean;
- issue: TypeIssue;
- onChange: (issue: TypeIssue) => void;
- onCheck: ((issueKey: string) => void) | undefined;
- onSelect: (issueKey: string) => void;
- onFilterChange: (changes: Partial<Query>) => void;
- onPopupToggle: (issue: string, popupName: string, open?: boolean) => void;
- openPopup: string | undefined;
- selected: boolean;
-}
-
-export default class ListItem extends React.PureComponent<Props> {
- handleFilter = (property: string, issue: TypeIssue) => {
- const { onFilterChange } = this.props;
-
- const issuesReset = { issues: [] };
-
- if (property.startsWith('tag###')) {
- const tag = property.substring('tag###'.length);
- onFilterChange({ ...issuesReset, tags: [tag] });
- } else {
- switch (property) {
- case 'type':
- onFilterChange({ ...issuesReset, types: [issue.type] });
- break;
- case 'severity':
- onFilterChange({ ...issuesReset, severities: [issue.severity] });
- break;
- case 'status':
- onFilterChange({ ...issuesReset, statuses: [issue.status] });
- break;
- case 'resolution':
- if (issue.resolution) {
- onFilterChange({ ...issuesReset, resolved: true, resolutions: [issue.resolution] });
- } else {
- onFilterChange({ ...issuesReset, resolved: false, resolutions: [] });
- }
- break;
- case 'assignee':
- if (issue.assignee) {
- onFilterChange({ ...issuesReset, assigned: true, assignees: [issue.assignee] });
- } else {
- onFilterChange({ ...issuesReset, assigned: false, assignees: [] });
- }
- break;
- case 'rule':
- onFilterChange({ ...issuesReset, rules: [issue.rule] });
- break;
- case 'project':
- onFilterChange({ ...issuesReset, projects: [issue.projectKey] });
- break;
- case 'file':
- onFilterChange({ ...issuesReset, files: [issue.componentUuid] });
- }
- }
- };
-
- render() {
- const { branchLike, issue } = this.props;
-
- return (
- <Issue
- branchLike={branchLike}
- checked={this.props.checked}
- issue={issue}
- onChange={this.props.onChange}
- onCheck={this.props.onCheck}
- onSelect={this.props.onSelect}
- onPopupToggle={this.props.onPopupToggle}
- openPopup={this.props.openPopup}
- selected={this.props.selected}
- />
- );
- }
-}
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
index e97d9794092..65364ec12cf 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
@@ -37,6 +37,7 @@ import { ComponentContext } from '../../../app/components/componentContext/Compo
import { useCurrentUser } from '../../../app/components/current-user/CurrentUserContext';
import Tooltip from '../../../components/controls/Tooltip';
import { ClipboardBase } from '../../../components/controls/clipboard';
+import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import { getBranchLikeQuery, isBranch, isPullRequest } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { collapsedDirFromPath, fileFromPath } from '../../../helpers/path';
@@ -181,7 +182,7 @@ export function IssueSourceViewerHeader(props: Readonly<Props>) {
to={getComponentIssuesUrl(project, {
...getBranchLikeQuery(branchLike),
files: path,
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
})}
>
{translate('source_viewer.view_all_issues')}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx
deleted file mode 100644
index b2995f774de..00000000000
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx
+++ /dev/null
@@ -1,143 +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 { FacetBox, FacetItem } from 'design-system';
-import { orderBy, without } from 'lodash';
-import * as React from 'react';
-import { RESOLUTIONS } from '../../../helpers/constants';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { Dict } from '../../../types/types';
-import { Query, formatFacetStat } from '../utils';
-import { FacetItemsColumns } from './FacetItemsColumns';
-import { MultipleSelectionHint } from './MultipleSelectionHint';
-
-interface Props {
- fetching: boolean;
- onChange: (changes: Partial<Query>) => void;
- onToggle: (property: string) => void;
- open: boolean;
- resolved: boolean;
- resolutions: string[];
- stats: Dict<number> | undefined;
-}
-
-export class ResolutionFacet extends React.PureComponent<Props> {
- property = 'resolutions';
-
- static defaultProps = {
- open: true,
- };
-
- handleItemClick = (itemValue: string, multiple: boolean) => {
- const { resolutions } = this.props;
-
- if (itemValue === '') {
- // unresolved
- this.props.onChange({ resolved: !this.props.resolved, resolutions: [] });
- } else if (multiple) {
- const newValue = orderBy(
- resolutions.includes(itemValue)
- ? without(resolutions, itemValue)
- : [...resolutions, itemValue],
- );
-
- this.props.onChange({ resolved: true, [this.property]: newValue });
- } else {
- this.props.onChange({
- resolved: true,
- [this.property]:
- resolutions.includes(itemValue) && resolutions.length < 2 ? [] : [itemValue],
- });
- }
- };
-
- handleHeaderClick = () => {
- this.props.onToggle(this.property);
- };
-
- handleClear = () => {
- this.props.onChange({ resolved: false, resolutions: [] });
- };
-
- isFacetItemActive(resolution: string) {
- return resolution === '' ? !this.props.resolved : this.props.resolutions.includes(resolution);
- }
-
- getFacetItemName(resolution: string) {
- return resolution === '' ? translate('unresolved') : translate('issue.resolution', resolution);
- }
-
- getStat(resolution: string) {
- const { stats } = this.props;
-
- return stats ? stats[resolution] : undefined;
- }
-
- renderItem = (resolution: string) => {
- const active = this.isFacetItemActive(resolution);
- const stat = this.getStat(resolution);
-
- return (
- <FacetItem
- active={active}
- className="it__search-navigator-facet"
- key={resolution}
- name={this.getFacetItemName(resolution)}
- onClick={this.handleItemClick}
- stat={formatFacetStat(stat) ?? 0}
- tooltip={this.getFacetItemName(resolution)}
- value={resolution}
- />
- );
- };
-
- render() {
- const { fetching, open, resolutions } = this.props;
-
- // below: -1 because "Unresolved" is mutually exclusive with the rest
- const nbSelectableItems = RESOLUTIONS.filter(this.getStat.bind(this)).length - 1;
-
- const nbSelectedItems = resolutions.length;
- const headerId = `facet_${this.property}`;
-
- return (
- <FacetBox
- className="it__search-navigator-facet-box it__search-navigator-facet-header"
- clearIconLabel={translate('clear')}
- count={nbSelectedItems}
- countLabel={translateWithParameters('x_selected', nbSelectedItems)}
- data-property={this.property}
- id={headerId}
- loading={fetching}
- name={translate('issues.facet', this.property)}
- onClear={this.handleClear}
- onClick={this.handleHeaderClick}
- open={open}
- >
- <FacetItemsColumns>{RESOLUTIONS.map(this.renderItem)}</FacetItemsColumns>
-
- <MultipleSelectionHint
- nbSelectableItems={nbSelectableItems}
- nbSelectedItems={nbSelectedItems}
- />
- </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 2b86e04154c..141cb21fe1b 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
@@ -52,13 +52,12 @@ import { FileFacet } from './FileFacet';
import { LanguageFacet } from './LanguageFacet';
import { PeriodFilter } from './PeriodFilter';
import { ProjectFacet } from './ProjectFacet';
-import { ResolutionFacet } from './ResolutionFacet';
import { RuleFacet } from './RuleFacet';
import { ScopeFacet } from './ScopeFacet';
import { SeverityFacet } from './SeverityFacet';
+import { SimpleStatusFacet } from './SimpleStatusFacet';
import { SoftwareQualityFacet } from './SoftwareQualityFacet';
import { StandardFacet } from './StandardFacet';
-import { StatusFacet } from './StatusFacet';
import { TagFacet } from './TagFacet';
import { TypeFacet } from './TypeFacet';
import { VariantFacet } from './VariantFacet';
@@ -246,25 +245,13 @@ export class SidebarClass extends React.PureComponent<Props> {
<BasicSeparator className="sw-my-4" />
- <ResolutionFacet
- fetching={this.props.loadingFacets.resolutions === true}
+ <SimpleStatusFacet
+ fetching={this.props.loadingFacets.simpleStatuses === true}
onChange={this.props.onFilterChange}
onToggle={this.props.onFacetToggle}
- open={!!openFacets.resolutions}
- resolutions={query.resolutions}
- resolved={query.resolved}
- stats={facets.resolutions}
- />
-
- <BasicSeparator className="sw-my-4" />
-
- <StatusFacet
- fetching={this.props.loadingFacets.statuses === true}
- onChange={this.props.onFilterChange}
- onToggle={this.props.onFacetToggle}
- open={!!openFacets.statuses}
- stats={facets.statuses}
- statuses={query.statuses}
+ open={!!openFacets.simpleStatuses}
+ simpleStatuses={query.simpleStatuses}
+ stats={facets.simpleStatuses}
/>
<BasicSeparator className="sw-my-4" />
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SimpleStatusFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/SimpleStatusFacet.tsx
new file mode 100644
index 00000000000..5889d9fc2c2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/issues/sidebar/SimpleStatusFacet.tsx
@@ -0,0 +1,109 @@
+/*
+ * 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 { FacetBox, FacetItem } from 'design-system';
+import { FacetItemsList } from './FacetItemsList';
+
+import { isEqual, sortBy, without } from 'lodash';
+import * as React from 'react';
+import { useIntl } from 'react-intl';
+import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
+import { SIMPLE_STATUSES } from '../../../helpers/constants';
+import { IssueSimpleStatus } from '../../../types/issues';
+import { formatFacetStat } from '../utils';
+import { MultipleSelectionHint } from './MultipleSelectionHint';
+import { CommonProps } from './SimpleListStyleFacet';
+
+interface Props extends CommonProps {
+ simpleStatuses: Array<IssueSimpleStatus>;
+}
+
+const property = 'simpleStatuses';
+const headerId = `facet_${property}`;
+
+const defaultStatuses = DEFAULT_ISSUES_QUERY.simpleStatuses.split(',') as IssueSimpleStatus[];
+
+export function SimpleStatusFacet(props: Readonly<Props>) {
+ const { simpleStatuses = [], stats = {}, fetching, open, help, needIssueSync } = props;
+ const intl = useIntl();
+
+ const nbSelectableItems = SIMPLE_STATUSES.filter(
+ (item) => !defaultStatuses.includes(item) && stats[item],
+ ).length;
+ const hasDefaultSelection = isEqual(sortBy(simpleStatuses), sortBy(defaultStatuses));
+ const nbSelectedItems = hasDefaultSelection ? 0 : simpleStatuses.length;
+
+ return (
+ <FacetBox
+ className="it__search-navigator-facet-box it__search-navigator-facet-header"
+ clearIconLabel={intl.formatMessage({ id: 'clear' })}
+ count={nbSelectedItems}
+ countLabel={intl.formatMessage({ id: 'x_selected' }, { '0': nbSelectedItems })}
+ data-property={property}
+ id={headerId}
+ loading={fetching}
+ name={intl.formatMessage({ id: `issues.facet.${property}` })}
+ onClear={() =>
+ props.onChange({
+ [property]: defaultStatuses,
+ })
+ }
+ onClick={() => props.onToggle(property)}
+ open={open}
+ help={help}
+ >
+ <FacetItemsList labelledby={headerId}>
+ {SIMPLE_STATUSES.map((item) => {
+ const active = simpleStatuses.includes(item);
+ const stat = stats[item];
+
+ return (
+ <FacetItem
+ active={active}
+ className="it__search-navigator-facet"
+ key={item}
+ name={intl.formatMessage({ id: `issue.simple_status.${item}` })}
+ onClick={(itemValue: IssueSimpleStatus, multiple) => {
+ if (multiple) {
+ props.onChange({
+ [property]: active
+ ? without(simpleStatuses, itemValue)
+ : [...simpleStatuses, itemValue],
+ });
+ } else {
+ props.onChange({
+ [property]: active && simpleStatuses.length === 1 ? [] : [itemValue],
+ });
+ }
+ }}
+ stat={(!needIssueSync && formatFacetStat(stat)) ?? 0}
+ value={item}
+ />
+ );
+ })}
+ </FacetItemsList>
+
+ <MultipleSelectionHint
+ nbSelectableItems={nbSelectableItems}
+ nbSelectedItems={simpleStatuses.length}
+ />
+ </FacetBox>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx
deleted file mode 100644
index f87bb220321..00000000000
--- a/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx
+++ /dev/null
@@ -1,139 +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 {
- FacetBox,
- FacetItem,
- StatusConfirmedIcon,
- StatusOpenIcon,
- StatusReopenedIcon,
- StatusResolvedIcon,
-} from 'design-system';
-import { orderBy, without } from 'lodash';
-import * as React from 'react';
-import { STATUSES } from '../../../helpers/constants';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { Dict } from '../../../types/types';
-import { Query, formatFacetStat } from '../utils';
-import { FacetItemsColumns } from './FacetItemsColumns';
-import { MultipleSelectionHint } from './MultipleSelectionHint';
-
-interface Props {
- fetching: boolean;
- onChange: (changes: Partial<Query>) => void;
- onToggle: (property: string) => void;
- open: boolean;
- stats: Dict<number> | undefined;
- statuses: string[];
-}
-
-export class StatusFacet extends React.PureComponent<Props> {
- property = 'statuses';
-
- static defaultProps = { open: true };
-
- handleItemClick = (itemValue: string, multiple: boolean) => {
- const { statuses } = this.props;
-
- if (multiple) {
- const newValue = orderBy(
- statuses.includes(itemValue) ? without(statuses, itemValue) : [...statuses, itemValue],
- );
-
- this.props.onChange({ [this.property]: newValue });
- } else {
- this.props.onChange({
- [this.property]: statuses.includes(itemValue) && statuses.length < 2 ? [] : [itemValue],
- });
- }
- };
-
- handleHeaderClick = () => {
- this.props.onToggle(this.property);
- };
-
- handleClear = () => {
- this.props.onChange({ [this.property]: [] });
- };
-
- getStat(status: string) {
- const { stats } = this.props;
-
- return stats ? stats[status] : undefined;
- }
-
- renderItem = (status: string) => {
- const active = this.props.statuses.includes(status);
- const stat = this.getStat(status);
-
- return (
- <FacetItem
- active={active}
- className="it__search-navigator-facet"
- icon={
- {
- CLOSED: <StatusResolvedIcon />,
- CONFIRMED: <StatusConfirmedIcon />,
- OPEN: <StatusOpenIcon />,
- REOPENED: <StatusReopenedIcon />,
- RESOLVED: <StatusResolvedIcon />,
- }[status]
- }
- key={status}
- name={translate('issue.status', status)}
- onClick={this.handleItemClick}
- stat={formatFacetStat(stat) ?? 0}
- tooltip={translate('issue.status', status)}
- value={status}
- />
- );
- };
-
- render() {
- const { fetching, open, statuses } = this.props;
-
- const nbSelectableItems = STATUSES.filter(this.getStat.bind(this)).length;
- const nbSelectedItems = statuses.length;
- const headerId = `facet_${this.property}`;
-
- return (
- <FacetBox
- className="it__search-navigator-facet-box it__search-navigator-facet-header"
- clearIconLabel={translate('clear')}
- count={nbSelectedItems}
- countLabel={translateWithParameters('x_selected', nbSelectedItems)}
- data-property={this.property}
- id={headerId}
- loading={fetching}
- name={translate('issues.facet', this.property)}
- onClear={this.handleClear}
- onClick={this.handleHeaderClick}
- open={open}
- >
- <FacetItemsColumns>{STATUSES.map(this.renderItem)}</FacetItemsColumns>
-
- <MultipleSelectionHint
- nbSelectableItems={nbSelectableItems}
- nbSelectedItems={nbSelectedItems}
- />
- </FacetBox>
- );
- }
-}
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 dd743080664..cd19d2908bb 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
@@ -51,8 +51,7 @@ it('should render correct facets for Application', () => {
'issues.facet.impactSeverities',
'issues.facet.types',
'issues.facet.scopes',
- 'issues.facet.resolutions',
- 'issues.facet.statuses',
+ 'issues.facet.simpleStatuses',
'issues.facet.standards',
'issues.facet.createdAt',
'issues.facet.languages',
@@ -74,8 +73,7 @@ it('should render correct facets for Portfolio', () => {
'issues.facet.impactSeverities',
'issues.facet.types',
'issues.facet.scopes',
- 'issues.facet.resolutions',
- 'issues.facet.statuses',
+ 'issues.facet.simpleStatuses',
'issues.facet.standards',
'issues.facet.createdAt',
'issues.facet.languages',
@@ -97,8 +95,7 @@ it('should render correct facets for SubPortfolio', () => {
'issues.facet.impactSeverities',
'issues.facet.types',
'issues.facet.scopes',
- 'issues.facet.resolutions',
- 'issues.facet.statuses',
+ 'issues.facet.simpleStatuses',
'issues.facet.standards',
'issues.facet.createdAt',
'issues.facet.languages',
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 1830e38c66d..5644efe5ca4 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
@@ -74,7 +74,7 @@ export const ui = {
resolutionFacet: byRole('button', { name: 'issues.facet.resolutions' }),
ruleFacet: byRole('button', { name: 'issues.facet.rules' }),
scopeFacet: byRole('button', { name: 'issues.facet.scopes' }),
- statusFacet: byRole('button', { name: 'issues.facet.statuses' }),
+ simpleStatusFacet: byRole('button', { name: 'issues.facet.simpleStatuses' }),
tagFacet: byRole('button', { name: 'issues.facet.tags' }),
typeFacet: byRole('button', { name: 'issues.facet.types' }),
cleanCodeAttributeCategoryFacet: byRole('button', {
@@ -97,7 +97,7 @@ export const ui = {
clearRuleFacet: byTestId('clear-issues.facet.rules'),
clearScopeFacet: byTestId('clear-issues.facet.scopes'),
clearSeverityFacet: byTestId('clear-issues.facet.impactSeverities'),
- clearStatusFacet: byTestId('clear-issues.facet.statuses'),
+ clearSimpleStatusFacet: byTestId('clear-issues.facet.simpleStatuses'),
clearTagFacet: byTestId('clear-issues.facet.tags'),
responsibleCategoryFilter: byRole('checkbox', {
@@ -110,11 +110,11 @@ export const ui = {
name: `software_quality.${SoftwareQuality.Maintainability}`,
}),
codeSmellIssueTypeFilter: byRole('checkbox', { name: 'issue.type.CODE_SMELL' }),
- confirmedStatusFilter: byRole('checkbox', { name: 'issue.status.CONFIRMED' }),
+ confirmedStatusFilter: byRole('checkbox', { name: 'issue.simple_status.CONFIRMED' }),
fixedResolutionFilter: byRole('checkbox', { name: 'issue.resolution.FIXED' }),
mainScopeFilter: byRole('checkbox', { name: 'issue.scope.MAIN' }),
mediumSeverityFilter: byRole('checkbox', { name: `severity.${SoftwareImpactSeverity.Medium}` }),
- openStatusFilter: byRole('checkbox', { name: 'issue.status.OPEN' }),
+ openStatusFilter: byRole('checkbox', { name: 'issue.simple_status.OPEN' }),
vulnerabilityIssueTypeFilter: byRole('checkbox', { name: 'issue.type.VULNERABILITY' }),
clearAllFilters: byRole('button', { name: 'clear_all_filters' }),
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 f043d71ee6a..85ebc2f8d99 100644
--- a/server/sonar-web/src/main/js/apps/issues/utils.ts
+++ b/server/sonar-web/src/main/js/apps/issues/utils.ts
@@ -17,8 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { isArray } from 'lodash';
+import { intersection, isArray, uniq } from 'lodash';
import { getUsers } from '../../api/users';
+import { DEFAULT_ISSUES_QUERY } from '../../components/shared/utils';
import { formatMeasure } from '../../helpers/measures';
import {
cleanQuery,
@@ -38,7 +39,13 @@ import {
SoftwareImpactSeverity,
SoftwareQuality,
} from '../../types/clean-code-taxonomy';
-import { Facet, RawFacet } from '../../types/issues';
+import {
+ Facet,
+ IssueResolution,
+ IssueSimpleStatus,
+ IssueStatus,
+ RawFacet,
+} from '../../types/issues';
import { MetricType } from '../../types/metrics';
import { SecurityStandard } from '../../types/security';
import { Dict, Issue, Paging, RawQuery } from '../../types/types';
@@ -70,15 +77,13 @@ export interface Query {
[OWASP_ASVS_4_0]: string[];
owaspAsvsLevel: string;
projects: string[];
- resolutions: string[];
- resolved: boolean;
rules: string[];
scopes: string[];
severities: string[];
inNewCodePeriod: boolean;
sonarsourceSecurity: string[];
sort: string;
- statuses: string[];
+ simpleStatuses: IssueSimpleStatus[];
tags: string[];
types: string[];
}
@@ -120,20 +125,78 @@ export function parseQuery(query: RawQuery): Query {
[OWASP_ASVS_4_0]: parseAsArray(query[OWASP_ASVS_4_0], parseAsString),
owaspAsvsLevel: parseAsString(query['owaspAsvsLevel']),
projects: parseAsArray(query.projects, parseAsString),
- resolutions: parseAsArray(query.resolutions, parseAsString),
- resolved: parseAsBoolean(query.resolved),
rules: parseAsArray(query.rules, parseAsString),
scopes: parseAsArray(query.scopes, parseAsString),
severities: parseAsArray(query.severities, parseAsString),
sonarsourceSecurity: parseAsArray(query.sonarsourceSecurity, parseAsString),
sort: parseAsSort(query.s),
- statuses: parseAsArray(query.statuses, parseAsString),
+ simpleStatuses: parseSimpleStatuses(query),
tags: parseAsArray(query.tags, parseAsString),
types: parseAsArray(query.types, parseAsString),
codeVariants: parseAsArray(query.codeVariants, parseAsString),
};
}
+function parseSimpleStatuses(query: RawQuery) {
+ let result: Array<IssueSimpleStatus> = [];
+
+ if (query.simpleStatuses) {
+ return parseAsArray<IssueSimpleStatus>(query.simpleStatuses, parseAsString);
+ }
+
+ const deprecatedStatusesMap = {
+ [IssueStatus.Open]: [IssueSimpleStatus.Open],
+ [IssueStatus.Confirmed]: [IssueSimpleStatus.Confirmed],
+ [IssueStatus.Reopened]: [IssueSimpleStatus.Open],
+ [IssueStatus.Resolved]: [
+ IssueSimpleStatus.Fixed,
+ IssueSimpleStatus.Accepted,
+ IssueSimpleStatus.FalsePositive,
+ ],
+ [IssueStatus.Closed]: [IssueSimpleStatus.Fixed],
+ };
+ const deprecatedResolutionsMap = {
+ [IssueResolution.FalsePositive]: [IssueSimpleStatus.FalsePositive],
+ [IssueResolution.WontFix]: [IssueSimpleStatus.Accepted],
+ [IssueResolution.Fixed]: [IssueSimpleStatus.Fixed],
+ [IssueResolution.Removed]: [IssueSimpleStatus.Fixed],
+ [IssueResolution.Unresolved]: [IssueSimpleStatus.Open, IssueSimpleStatus.Confirmed],
+ };
+
+ const simpleStatusesFromStatuses = parseAsArray<IssueStatus>(query.statuses, parseAsString)
+ .map((status) => deprecatedStatusesMap[status])
+ .filter(Boolean)
+ .flat();
+ const simpleStatusesFromResolutions = parseAsArray<IssueResolution>(
+ query.resolutions,
+ parseAsString,
+ )
+ .map((status) => deprecatedResolutionsMap[status])
+ .filter(Boolean)
+ .flat();
+
+ const intesectedSimpleStatuses = intersection(
+ simpleStatusesFromStatuses,
+ simpleStatusesFromResolutions,
+ );
+ result = intesectedSimpleStatuses.length
+ ? intesectedSimpleStatuses
+ : simpleStatusesFromResolutions.concat(simpleStatusesFromStatuses);
+
+ if (
+ query.resolved === 'false' &&
+ [IssueSimpleStatus.Open, IssueSimpleStatus.Confirmed].every(
+ (status) => !result.includes(status),
+ )
+ ) {
+ result = result.concat(
+ parseAsArray<IssueSimpleStatus>(DEFAULT_ISSUES_QUERY.simpleStatuses, parseAsString),
+ );
+ }
+
+ return uniq(result);
+}
+
export function getOpen(query: RawQuery): string | undefined {
return query.open;
}
@@ -167,8 +230,6 @@ export function serializeQuery(query: Query): RawQuery {
[OWASP_ASVS_4_0]: serializeStringArray(query[OWASP_ASVS_4_0]),
owaspAsvsLevel: serializeString(query['owaspAsvsLevel']),
projects: serializeStringArray(query.projects),
- resolutions: serializeStringArray(query.resolutions),
- resolved: query.resolved ? undefined : 'false',
rules: serializeStringArray(query.rules),
s: serializeString(query.sort),
scopes: serializeStringArray(query.scopes),
@@ -177,7 +238,7 @@ export function serializeQuery(query: Query): RawQuery {
impactSoftwareQualities: serializeStringArray(query.impactSoftwareQualities),
inNewCodePeriod: query.inNewCodePeriod ? 'true' : undefined,
sonarsourceSecurity: serializeStringArray(query.sonarsourceSecurity),
- statuses: serializeStringArray(query.statuses),
+ simpleStatuses: serializeStringArray(query.simpleStatuses),
tags: serializeStringArray(query.tags),
types: serializeStringArray(query.types),
codeVariants: serializeStringArray(query.codeVariants),
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPanel.tsx
index cf919d5561f..a909f2d2ed2 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresCardPanel.tsx
@@ -21,6 +21,7 @@ import classNames from 'classnames';
import * as React from 'react';
import { useIntl } from 'react-intl';
import { getLeakValue } from '../../../components/measure/utils';
+import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { findMeasure } from '../../../helpers/measures';
import {
@@ -62,7 +63,7 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>)
label={newViolations === '1' ? 'issue' : 'issues'}
url={getComponentIssuesUrl(component.key, {
...getBranchLikeQuery(branchLike),
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
})}
value={newViolations}
failedConditions={failedConditions}
@@ -104,7 +105,6 @@ export default function MeasuresCardPanel(props: React.PropsWithChildren<Props>)
}
url={getComponentSecurityHotspotsUrl(component.key, {
...getBranchLikeQuery(branchLike),
- resolved: 'false',
})}
value={newSecurityHotspots}
failedConditions={failedConditions}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx b/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx
index 0477b195282..22b53b8a9ee 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/BranchQualityGateConditions.tsx
@@ -21,7 +21,11 @@
import { ChevronRightIcon, DangerButtonSecondary } from 'design-system';
import React from 'react';
import { useIntl } from 'react-intl';
-import { isIssueMeasure, propsToIssueParams } from '../../../components/shared/utils';
+import {
+ DEFAULT_ISSUES_QUERY,
+ isIssueMeasure,
+ propsToIssueParams,
+} from '../../../components/shared/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { getLocalizedMetricName } from '../../../helpers/l10n';
import { formatMeasure, getShortType, isDiffMetric } from '../../../helpers/measures';
@@ -181,7 +185,7 @@ function getQGConditionUrl(
});
}
return getComponentIssuesUrl(componentKey, {
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
types: ratingIssueType,
...getBranchLikeQuery(branchLike),
...(sinceLeakPeriod ? { sinceLeakPeriod: 'true' } : {}),
diff --git a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx
index a2a1c55f1db..7209e447cde 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx
@@ -23,6 +23,7 @@ import * as React from 'react';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import Tooltip from '../../../components/controls/Tooltip';
import { getLeakValue } from '../../../components/measure/utils';
+import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { findMeasure, formatMeasure, localizeMetric } from '../../../helpers/measures';
@@ -58,7 +59,7 @@ export function IssueLabel(props: IssueLabelProps) {
const params = {
...getBranchLikeQuery(branchLike),
inNewCodePeriod: useDiffMetric ? 'true' : 'false',
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
types: type,
};
diff --git a/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
index 2104460da8a..cd154bb377b 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/QualityGateCondition.tsx
@@ -22,7 +22,11 @@ import * as React from 'react';
import { Path } from 'react-router-dom';
import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
import MeasureIndicator from '../../../components/measure/MeasureIndicator';
-import { isIssueMeasure, propsToIssueParams } from '../../../components/shared/utils';
+import {
+ DEFAULT_ISSUES_QUERY,
+ isIssueMeasure,
+ propsToIssueParams,
+} from '../../../components/shared/utils';
import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { formatMeasure, isDiffMetric, localizeMetric } from '../../../helpers/measures';
@@ -47,7 +51,7 @@ interface Props {
export default class QualityGateCondition extends React.PureComponent<Props> {
getIssuesUrl = (inNewCodePeriod: boolean, customQuery: Dict<string>) => {
const query: Dict<string | undefined> = {
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
...getBranchLikeQuery(this.props.branchLike),
...customQuery,
};
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx
index 48a2dd79305..a38af0f8da2 100644
--- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/BranchQualityGate-it.tsx
@@ -39,7 +39,7 @@ it('renders failed QG', () => {
expect(maintainabilityRatingLink).toBeInTheDocument();
expect(maintainabilityRatingLink).toHaveAttribute(
'href',
- '/project/issues?resolved=false&types=CODE_SMELL&pullRequest=1001&sinceLeakPeriod=true&id=my-project',
+ '/project/issues?simpleStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&sinceLeakPeriod=true&id=my-project',
);
// Security Hotspots rating condition
@@ -59,7 +59,7 @@ it('renders failed QG', () => {
expect(codeSmellsLink).toBeInTheDocument();
expect(codeSmellsLink).toHaveAttribute(
'href',
- '/project/issues?resolved=false&types=CODE_SMELL&pullRequest=1001&id=my-project',
+ '/project/issues?simpleStatuses=OPEN%2CCONFIRMED&types=CODE_SMELL&pullRequest=1001&id=my-project',
);
// Conditions to cover
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
index 0685021f205..20a1ca08152 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx
@@ -37,7 +37,6 @@ import {
themeColor,
} from 'design-system';
import * as React from 'react';
-
import { getBranchLikeQuery } from '../../helpers/branch-like';
import { ISSUE_TYPES } from '../../helpers/constants';
import { ISSUETYPE_METRIC_KEYS_MAP } from '../../helpers/issues';
@@ -52,6 +51,7 @@ import {
getComponentIssuesUrl,
getComponentSecurityHotspotsUrl,
} from '../../helpers/urls';
+import { DEFAULT_ISSUES_QUERY } from '../shared/utils';
import { ComponentQualifier } from '../../types/component';
import { IssueType } from '../../types/issues';
@@ -89,7 +89,7 @@ export default class SourceViewerHeader extends React.PureComponent<Props> {
const params = {
...getBranchLikeQuery(branchLike),
files: sourceViewerFile.path,
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
types: type,
};
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.ts b/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.ts
index cc9656f1ef1..a29d83f34c0 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.ts
+++ b/server/sonar-web/src/main/js/components/SourceViewer/helpers/loadIssues.ts
@@ -23,6 +23,7 @@ import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { parseIssueFromResponse } from '../../../helpers/issues';
import { BranchLike } from '../../../types/branch-like';
import { Issue, RawQuery } from '../../../types/types';
+import { DEFAULT_ISSUES_QUERY } from '../../shared/utils';
// maximum possible value
const PAGE_SIZE = 500;
@@ -32,16 +33,16 @@ const PAGE_MAX = 20;
function buildListQuery(component: string, branchLike: BranchLike | undefined) {
return {
component,
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
...getBranchLikeQuery(branchLike),
};
}
function buildSearchQuery(component: string, branchLike: BranchLike | undefined) {
return {
+ ...DEFAULT_ISSUES_QUERY,
additionalFields: '_all',
componentKeys: component,
- resolved: 'false',
s: 'FILE_LINE',
...getBranchLikeQuery(branchLike),
};
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx
index a1bae5c76ef..75a2bef6d3c 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueMessage.tsx
@@ -27,6 +27,7 @@ import { getComponentIssuesUrl, getIssuesUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import { Issue } from '../../../types/types';
import { useLocation } from '../../hoc/withRouter';
+import { DEFAULT_ISSUES_QUERY } from '../../shared/utils';
export interface IssueMessageProps {
issue: Issue;
@@ -48,7 +49,7 @@ export default function IssueMessage(props: IssueMessageProps) {
...getBranchLikeQuery(branchLike),
files: issue.componentLongName,
open: issue.key,
- resolved: 'false',
+ ...DEFAULT_ISSUES_QUERY,
why: '1',
});
diff --git a/server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts b/server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts
index 495248657e8..f83c2dfa817 100644
--- a/server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/components/shared/__tests__/utils-test.ts
@@ -23,13 +23,13 @@ import { propsToIssueParams } from '../utils';
describe('propsToIssueParams', () => {
it('should render correct default parameters', () => {
- expect(propsToIssueParams('other')).toEqual({ resolved: 'false' });
+ expect(propsToIssueParams('other')).toEqual({ simpleStatuses: 'OPEN,CONFIRMED' });
});
it(`should render correct params`, () => {
expect(propsToIssueParams(MetricKey.false_positive_issues, true)).toEqual({
- resolutions: 'FALSE-POSITIVE',
inNewCodePeriod: true,
+ simpleStatuses: 'FALSE_POSITIVE',
});
});
});
diff --git a/server/sonar-web/src/main/js/components/shared/utils.ts b/server/sonar-web/src/main/js/components/shared/utils.ts
index a073929ddf3..12358551c9e 100644
--- a/server/sonar-web/src/main/js/components/shared/utils.ts
+++ b/server/sonar-web/src/main/js/components/shared/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 { IssueSimpleStatus } from '../../types/issues';
import { MetricKey } from '../../types/metrics';
import { Dict } from '../../types/types';
@@ -46,27 +46,31 @@ const ISSUE_MEASURES = [
MetricKey.new_vulnerabilities,
];
+export const DEFAULT_ISSUES_QUERY = {
+ simpleStatuses: `${IssueSimpleStatus.Open},${IssueSimpleStatus.Confirmed}`,
+};
+
const issueParamsPerMetric: Dict<Dict<string>> = {
- [MetricKey.blocker_violations]: { resolved: 'false', severities: 'BLOCKER' },
- [MetricKey.new_blocker_violations]: { resolved: 'false', severities: 'BLOCKER' },
- [MetricKey.critical_violations]: { resolved: 'false', severities: 'CRITICAL' },
- [MetricKey.new_critical_violations]: { resolved: 'false', severities: 'CRITICAL' },
- [MetricKey.major_violations]: { resolved: 'false', severities: 'MAJOR' },
- [MetricKey.new_major_violations]: { resolved: 'false', severities: 'MAJOR' },
- [MetricKey.minor_violations]: { resolved: 'false', severities: 'MINOR' },
- [MetricKey.new_minor_violations]: { resolved: 'false', severities: 'MINOR' },
- [MetricKey.info_violations]: { resolved: 'false', severities: 'INFO' },
- [MetricKey.new_info_violations]: { resolved: 'false', severities: 'INFO' },
- [MetricKey.open_issues]: { resolved: 'false', statuses: 'OPEN' },
- [MetricKey.reopened_issues]: { resolved: 'false', statuses: 'REOPENED' },
- [MetricKey.confirmed_issues]: { resolved: 'false', statuses: 'CONFIRMED' },
- [MetricKey.false_positive_issues]: { resolutions: 'FALSE-POSITIVE' },
- [MetricKey.code_smells]: { resolved: 'false', types: 'CODE_SMELL' },
- [MetricKey.new_code_smells]: { resolved: 'false', types: 'CODE_SMELL' },
- [MetricKey.bugs]: { resolved: 'false', types: 'BUG' },
- [MetricKey.new_bugs]: { resolved: 'false', types: 'BUG' },
- [MetricKey.vulnerabilities]: { resolved: 'false', types: 'VULNERABILITY' },
- [MetricKey.new_vulnerabilities]: { resolved: 'false', types: 'VULNERABILITY' },
+ [MetricKey.blocker_violations]: { severities: 'BLOCKER' },
+ [MetricKey.new_blocker_violations]: { severities: 'BLOCKER' },
+ [MetricKey.critical_violations]: { severities: 'CRITICAL' },
+ [MetricKey.new_critical_violations]: { severities: 'CRITICAL' },
+ [MetricKey.major_violations]: { severities: 'MAJOR' },
+ [MetricKey.new_major_violations]: { severities: 'MAJOR' },
+ [MetricKey.minor_violations]: { severities: 'MINOR' },
+ [MetricKey.new_minor_violations]: { severities: 'MINOR' },
+ [MetricKey.info_violations]: { severities: 'INFO' },
+ [MetricKey.new_info_violations]: { severities: 'INFO' },
+ [MetricKey.open_issues]: { simpleStatuses: IssueSimpleStatus.Open },
+ [MetricKey.reopened_issues]: { simpleStatuses: IssueSimpleStatus.Open },
+ [MetricKey.confirmed_issues]: { simpleStatuses: IssueSimpleStatus.Confirmed },
+ [MetricKey.false_positive_issues]: { simpleStatuses: IssueSimpleStatus.FalsePositive },
+ [MetricKey.code_smells]: { types: 'CODE_SMELL' },
+ [MetricKey.new_code_smells]: { types: 'CODE_SMELL' },
+ [MetricKey.bugs]: { types: 'BUG' },
+ [MetricKey.new_bugs]: { types: 'BUG' },
+ [MetricKey.vulnerabilities]: { types: 'VULNERABILITY' },
+ [MetricKey.new_vulnerabilities]: { types: 'VULNERABILITY' },
};
export function isIssueMeasure(metric: string) {
@@ -75,7 +79,8 @@ export function isIssueMeasure(metric: string) {
export function propsToIssueParams(metric: string, inNewCodePeriod = false) {
const params: Dict<string | boolean> = {
- ...(issueParamsPerMetric[metric] || { resolved: 'false' }),
+ ...DEFAULT_ISSUES_QUERY,
+ ...issueParamsPerMetric[metric],
};
if (inNewCodePeriod) {
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
index 6e19414632b..57948fc3a0b 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
+++ b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
@@ -17,6 +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 { DEFAULT_ISSUES_QUERY } from '../../components/shared/utils';
import { AlmKeys } from '../../types/alm-settings';
import { ComponentQualifier } from '../../types/component';
import { IssueType } from '../../types/issues';
@@ -102,10 +103,10 @@ describe('#getComponentIssuesUrl', () => {
});
it('should work with parameters', () => {
- expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, { resolved: 'false' })).toEqual(
+ expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, DEFAULT_ISSUES_QUERY)).toEqual(
expect.objectContaining({
pathname: '/project/issues',
- search: queryToSearch({ resolved: 'false', id: SIMPLE_COMPONENT_KEY }),
+ search: queryToSearch({ ...DEFAULT_ISSUES_QUERY, id: SIMPLE_COMPONENT_KEY }),
}),
);
});
diff --git a/server/sonar-web/src/main/js/helpers/constants.ts b/server/sonar-web/src/main/js/helpers/constants.ts
index b1d98632e55..20d4cab5b02 100644
--- a/server/sonar-web/src/main/js/helpers/constants.ts
+++ b/server/sonar-web/src/main/js/helpers/constants.ts
@@ -25,7 +25,13 @@ import {
SoftwareQuality,
} from '../types/clean-code-taxonomy';
import { ComponentQualifier } from '../types/component';
-import { IssueResolution, IssueScope, IssueSeverity, IssueType } from '../types/issues';
+import {
+ IssueResolution,
+ IssueScope,
+ IssueSeverity,
+ IssueSimpleStatus,
+ IssueType,
+} from '../types/issues';
import { RuleType } from '../types/types';
export const SEVERITIES = Object.values(IssueSeverity);
@@ -38,6 +44,14 @@ export const SOFTWARE_QUALITIES = Object.values(SoftwareQuality);
export const STATUSES = ['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED', 'CLOSED'];
+export const SIMPLE_STATUSES = [
+ IssueSimpleStatus.Open,
+ IssueSimpleStatus.Accepted,
+ IssueSimpleStatus.FalsePositive,
+ IssueSimpleStatus.Confirmed,
+ IssueSimpleStatus.Fixed,
+];
+
export const ISSUE_TYPES: IssueType[] = [
IssueType.Bug,
IssueType.Vulnerability,
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 96d005d574c..dedc2f1ff1d 100644
--- a/server/sonar-web/src/main/js/helpers/mocks/issues.ts
+++ b/server/sonar-web/src/main/js/helpers/mocks/issues.ts
@@ -75,8 +75,6 @@ export function mockQuery(overrides: Partial<Query> = {}): Query {
'owaspAsvs-4.0': [],
owaspAsvsLevel: '',
projects: [],
- resolutions: [],
- resolved: false,
rules: [],
scopes: [],
severities: [],
@@ -84,8 +82,8 @@ export function mockQuery(overrides: Partial<Query> = {}): Query {
impactSoftwareQualities: [],
inNewCodePeriod: false,
sonarsourceSecurity: [],
+ simpleStatuses: [],
sort: '',
- statuses: [],
tags: [],
types: [],
...overrides,
diff --git a/server/sonar-web/src/main/js/helpers/query.ts b/server/sonar-web/src/main/js/helpers/query.ts
index f5c5e426dce..66f319a44e8 100644
--- a/server/sonar-web/src/main/js/helpers/query.ts
+++ b/server/sonar-web/src/main/js/helpers/query.ts
@@ -29,7 +29,12 @@ export function queriesEqual(a: RawQuery, b: RawQuery): boolean {
return false;
}
- return keysA.every((key) => isEqual(a[key], b[key]));
+ return keysA.every((key) =>
+ isEqual(
+ Array.isArray(a[key]) ? a[key].sort() : a[key],
+ Array.isArray(b[key]) ? b[key].sort() : b[key],
+ ),
+ );
}
export function cleanQuery(query: RawQuery): RawQuery {
diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts
index 702d92e2759..44c433f0bd6 100644
--- a/server/sonar-web/src/main/js/helpers/testMocks.ts
+++ b/server/sonar-web/src/main/js/helpers/testMocks.ts
@@ -310,10 +310,10 @@ export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue>
project: 'myproject',
rule: 'javascript:S1067',
severity: IssueSeverity.Major,
- status: IssueStatus.Open,
- simpleStatus: IssueSimpleStatus.Open,
textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
type: IssueType.CodeSmell,
+ status: IssueStatus.Open,
+ simpleStatus: IssueSimpleStatus.Open,
transitions: [],
scope: IssueScope.Main,
cleanCodeAttributeCategory: CleanCodeAttributeCategory.Responsible,
diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts
index 3b302c6fb82..aa9a3061748 100644
--- a/server/sonar-web/src/main/js/helpers/urls.ts
+++ b/server/sonar-web/src/main/js/helpers/urls.ts
@@ -20,6 +20,7 @@
import { isArray, mapValues, omitBy, pick } from 'lodash';
import { Path, To } from 'react-router-dom';
import { getProfilePath } from '../apps/quality-profiles/utils';
+import { DEFAULT_ISSUES_QUERY } from '../components/shared/utils';
import { BranchLike, BranchParameters } from '../types/branch-like';
import { ComponentQualifier, isApplication, isPortfolioLike } from '../types/component';
import { MeasurePageView } from '../types/measures';
@@ -423,7 +424,7 @@ export function getHomePageUrl(homepage: HomePage) {
return '/projects';
case 'ISSUES':
case 'MY_ISSUES':
- return { pathname: '/issues', query: { resolved: 'false' } };
+ return { pathname: '/issues', query: DEFAULT_ISSUES_QUERY };
}
// should never happen, but just in case...
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 d0942e614b0..f22fa371396 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1069,6 +1069,12 @@ issue.status.TO_REVIEW=To Review
issue.status.IN_REVIEW=In Review
issue.status.REVIEWED=Reviewed
+issue.simple_status.OPEN=Open
+issue.simple_status.ACCEPTED=Accepted
+issue.simple_status.CONFIRMED=Confirmed
+issue.simple_status.FIXED=Fixed
+issue.simple_status.FALSE_POSITIVE=False Positive
+
issue.scope.MAIN=Main code
issue.scope.TEST=Test code
@@ -1173,7 +1179,7 @@ issues.facet.types=Type
issues.facet.severities=Severity
issues.facet.scopes=Scope
issues.facet.projects=Project
-issues.facet.statuses=Status
+issues.facet.simpleStatuses=Status
issues.facet.hotspotStatuses=Hotspot Status
issues.facet.assignees=Assignee
issues.facet.files=File
@@ -1181,7 +1187,6 @@ issues.facet.modules=Module
issues.facet.directories=Directory
issues.facet.tags=Tag
issues.facet.rules=Rule
-issues.facet.resolutions=Resolution
issues.facet.languages=Language
issues.facet.cleanCodeAttributeCategories=Clean Code Attribute
issues.facet.impactSoftwareQualities=Software Quality