ソースを参照

SONAR-12723 remove hotspots from issues page

tags/8.2.0.32929
Sébastien Lesaint 4年前
コミット
8f7315c0ca

+ 0
- 27
server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts ファイルの表示

@@ -20,7 +20,6 @@
import { scrollToElement } from 'sonar-ui-common/helpers/scrolling';
import {
scrollToIssue,
shouldOpenSeverityFacet,
shouldOpenSonarSourceSecurityFacet,
shouldOpenStandardsChildFacet,
shouldOpenStandardsFacet
@@ -62,34 +61,11 @@ describe('scrollToIssue', () => {
});
});

describe('shouldOpenSeverityFacet', () => {
it('should open severity facet', () => {
expect(shouldOpenSeverityFacet({ severities: true }, { types: [] })).toBe(true);
expect(shouldOpenSeverityFacet({}, { types: [] })).toBe(true);
expect(shouldOpenSeverityFacet({}, { types: ['VULNERABILITY'] })).toBe(true);
expect(shouldOpenSeverityFacet({ severities: false }, { types: ['VULNERABILITY'] })).toBe(true);
expect(shouldOpenSeverityFacet({ severities: false }, { types: [] })).toBe(true);
expect(shouldOpenSeverityFacet({}, { types: ['BUGS', 'SECURITY_HOTSPOT'] })).toBe(true);
expect(shouldOpenSeverityFacet({ severities: true }, { types: ['SECURITY_HOTSPOT'] })).toBe(
true
);
});

it('should NOT open severity facet', () => {
expect(shouldOpenSeverityFacet({}, { types: ['SECURITY_HOTSPOT'] })).toBe(false);
});
});

describe('shouldOpenStandardsFacet', () => {
it('should open standard facet', () => {
expect(shouldOpenStandardsFacet({ standards: true }, { types: [] })).toBe(true);
expect(shouldOpenStandardsFacet({ owaspTop10: true }, { types: [] })).toBe(true);
expect(shouldOpenStandardsFacet({}, { types: ['VULNERABILITY'] })).toBe(true);
expect(shouldOpenStandardsFacet({}, { types: ['SECURITY_HOTSPOT'] })).toBe(true);
expect(shouldOpenStandardsFacet({}, { types: ['VULNERABILITY', 'SECURITY_HOTSPOT'] })).toBe(
true
);
expect(shouldOpenStandardsFacet({}, { types: ['BUGS', 'SECURITY_HOTSPOT'] })).toBe(true);
expect(shouldOpenStandardsFacet({ standards: false }, { types: ['VULNERABILITY'] })).toBe(true);
});

@@ -130,9 +106,6 @@ describe('shouldOpenStandardsChildFacet', () => {
expect(shouldOpenStandardsChildFacet({}, { types: ['VULNERABILITY'] }, 'sansTop25')).toBe(
false
);
expect(
shouldOpenStandardsChildFacet({}, { types: ['SECURITY_HOTSPOT'] }, 'sonarsourceSecurity')
).toBe(false);
expect(
shouldOpenStandardsChildFacet(
{},

+ 1
- 3
server/sonar-web/src/main/js/apps/issues/components/App.tsx ファイルの表示

@@ -73,7 +73,6 @@ import {
saveMyIssues,
scrollToIssue,
serializeQuery,
shouldOpenSeverityFacet,
shouldOpenSonarSourceSecurityFacet,
shouldOpenStandardsChildFacet,
shouldOpenStandardsFacet,
@@ -164,7 +163,7 @@ export class App extends React.PureComponent<Props, State> {
openFacets: {
owaspTop10: shouldOpenStandardsChildFacet({}, query, 'owaspTop10'),
sansTop25: shouldOpenStandardsChildFacet({}, query, 'sansTop25'),
severities: shouldOpenSeverityFacet({}, query),
severities: true,
sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet({}, query),
standards: shouldOpenStandardsFacet({}, query),
types: true
@@ -680,7 +679,6 @@ export class App extends React.PureComponent<Props, State> {
this.setState(({ openFacets }) => ({
openFacets: {
...openFacets,
severities: shouldOpenSeverityFacet(openFacets, changes),
sonarsourceSecurity: shouldOpenSonarSourceSecurityFacet(openFacets, changes),
standards: shouldOpenStandardsFacet(openFacets, changes)
}

+ 3
- 25
server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx ファイルの表示

@@ -238,26 +238,14 @@ it('should fetch issues for component', async () => {
it('should display the right facets open', () => {
expect(
shallowRender({
location: mockLocation({ query: { types: 'SECURITY_HOTSPOT' } })
}).state('openFacets')
).toEqual({
owaspTop10: false,
sansTop25: false,
severities: false,
standards: true,
sonarsourceSecurity: true,
types: true
});
expect(
shallowRender({
location: mockLocation({ query: { types: 'BUGS,SECURITY_HOTSPOT' } })
location: mockLocation({ query: { types: 'BUGS' } })
}).state('openFacets')
).toEqual({
owaspTop10: false,
sansTop25: false,
severities: true,
standards: true,
sonarsourceSecurity: true,
standards: false,
sonarsourceSecurity: false,
types: true
});
expect(
@@ -281,7 +269,6 @@ it('should correctly handle filter changes', () => {
instance.handleFilterChange({ types: ['VULNERABILITY'] });
expect(instance.state.openFacets).toEqual({
types: true,
severities: true,
sonarsourceSecurity: true,
standards: true
});
@@ -289,15 +276,6 @@ it('should correctly handle filter changes', () => {
instance.handleFilterChange({ types: ['BUGS'] });
expect(instance.state.openFacets).toEqual({
types: true,
severities: true,
sonarsourceSecurity: true,
standards: true
});
instance.setState({ openFacets: { types: true } });
instance.handleFilterChange({ types: ['SECURITY_HOTSPOT'] });
expect(instance.state.openFacets).toEqual({
types: true,
severities: false,
sonarsourceSecurity: true,
standards: true
});

+ 1
- 9
server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx ファイルの表示

@@ -37,9 +37,7 @@ interface Props {
statuses: string[];
}

const STATUSES = ['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED'];
const HOTSPOT_STATUSES = ['TO_REVIEW', 'REVIEWED', 'IN_REVIEW'];
const COMMON_STATUSES = ['CLOSED'];
const STATUSES = ['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED', 'CLOSED'];

export default class StatusFacet extends React.PureComponent<Props> {
property = 'statuses';
@@ -112,12 +110,6 @@ export default class StatusFacet extends React.PureComponent<Props> {
<FacetItemsList title={translate('issues')}>
{STATUSES.map(this.renderItem)}
</FacetItemsList>
<FacetItemsList title={translate('issue.type.SECURITY_HOTSPOT.plural')}>
{HOTSPOT_STATUSES.map(this.renderItem)}
</FacetItemsList>
<FacetItemsList title={translate('issues.issues_and_hotspots')}>
{COMMON_STATUSES.map(this.renderItem)}
</FacetItemsList>
<MultipleSelectionHint options={Object.keys(stats).length} values={statuses.length} />
</>
)}

+ 5
- 63
server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx ファイルの表示

@@ -19,11 +19,7 @@
*/
import { orderBy, without } from 'lodash';
import * as React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip';
import IssueTypeIcon from 'sonar-ui-common/components/icons/IssueTypeIcon';
import NewsBox from 'sonar-ui-common/components/ui/NewsBox';
import { translate } from 'sonar-ui-common/helpers/l10n';
import FacetBox from '../../../components/facet/FacetBox';
import FacetHeader from '../../../components/facet/FacetHeader';
@@ -31,22 +27,18 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint';
import { ISSUE_TYPES } from '../../../helpers/constants';
import { getCurrentUser, getCurrentUserSetting, Store } from '../../../store/rootReducer';
import { setCurrentUserSetting } from '../../../store/users';
import { formatFacetStat, Query } from '../utils';

interface Props {
fetching: boolean;
newsBoxDismissHotspots?: boolean;
onChange: (changes: Partial<Query>) => void;
onToggle: (property: string) => void;
open: boolean;
setCurrentUserSetting: (setting: T.CurrentUserSetting) => void;
stats: T.Dict<number> | undefined;
types: string[];
}

export class TypeFacet extends React.PureComponent<Props> {
export default class TypeFacet extends React.PureComponent<Props> {
property = 'types';

static defaultProps = {
@@ -75,10 +67,6 @@ export class TypeFacet extends React.PureComponent<Props> {
this.props.onChange({ [this.property]: [] });
};

handleDismiss = () => {
this.props.setCurrentUserSetting({ key: 'newsbox.dismiss.hotspots', value: 'true' });
};

getStat(type: string) {
const { stats } = this.props;
return stats ? stats[type] : undefined;
@@ -88,10 +76,6 @@ export class TypeFacet extends React.PureComponent<Props> {
return this.props.types.includes(type);
}

stopPropagation = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.stopPropagation();
};

renderItem = (type: string) => {
const active = this.isFacetItemActive(type);
const stat = this.getStat(type);
@@ -105,23 +89,6 @@ export class TypeFacet extends React.PureComponent<Props> {
<span className="display-flex-center">
<IssueTypeIcon className="little-spacer-right" query={type} />{' '}
{translate('issue.type', type)}
{type === 'SECURITY_HOTSPOT' && this.props.newsBoxDismissHotspots && (
<HelpTooltip
className="little-spacer-left"
overlay={
<>
<p>{translate('issues.hotspots.helper')}</p>
<hr className="spacer-top spacer-bottom" />
<Link
onClick={this.stopPropagation}
target="_blank"
to="/documentation/user-guide/security-hotspots/">
{translate('learn_more')}
</Link>
</>
}
/>
)}
</span>
}
onClick={this.handleItemClick}
@@ -132,12 +99,9 @@ export class TypeFacet extends React.PureComponent<Props> {
};

render() {
const { newsBoxDismissHotspots, types, stats = {} } = this.props;
const { types, stats = {} } = this.props;
const values = types.map(type => translate('issue.type', type));

const showHotspotNewsBox =
types.includes('SECURITY_HOTSPOT') || (types.length === 0 && stats['SECURITY_HOTSPOT'] > 0);

return (
<FacetBox property={this.property}>
<FacetHeader
@@ -151,19 +115,9 @@ export class TypeFacet extends React.PureComponent<Props> {

{this.props.open && (
<>
<FacetItemsList>{ISSUE_TYPES.map(this.renderItem)}</FacetItemsList>
{!newsBoxDismissHotspots && showHotspotNewsBox && (
<NewsBox
onClose={this.handleDismiss}
title={translate('issue.type.SECURITY_HOTSPOT.plural')}>
<p>{translate('issues.hotspots.helper')}</p>
<p className="text-right spacer-top">
<Link target="_blank" to="/documentation/user-guide/security-hotspots/">
{translate('learn_more')}
</Link>
</p>
</NewsBox>
)}
<FacetItemsList>
{ISSUE_TYPES.filter(t => t !== 'SECURITY_HOTSPOT').map(this.renderItem)}
</FacetItemsList>
<MultipleSelectionHint options={Object.keys(stats).length} values={types.length} />
</>
)}
@@ -171,15 +125,3 @@ export class TypeFacet extends React.PureComponent<Props> {
);
}
}

const mapStateToProps = (state: Store) => ({
newsBoxDismissHotspots:
!getCurrentUser(state).isLoggedIn ||
getCurrentUserSetting(state, 'newsbox.dismiss.hotspots') === 'true'
});

const mapDispatchToProps = {
setCurrentUserSetting
};

export default connect(mapStateToProps, mapDispatchToProps)(TypeFacet);

+ 5
- 8
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx ファイルの表示

@@ -35,7 +35,7 @@ it('should toggle status facet', () => {

it('should clear status facet', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange, statuses: ['TO_REVIEW'] });
const wrapper = shallowRender({ onChange, statuses: ['CONFIRMED'] });
wrapper.children('FacetHeader').prop<Function>('onClear')();
expect(onChange).toBeCalledWith({ statuses: [] });
});
@@ -43,9 +43,9 @@ it('should clear status facet', () => {
it('should select a status', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange });
clickAndCheck('TO_REVIEW');
clickAndCheck('OPEN', true, ['OPEN', 'TO_REVIEW']);
clickAndCheck('CONFIRMED');
clickAndCheck('OPEN');
clickAndCheck('CONFIRMED', true, ['CONFIRMED', 'OPEN']);
clickAndCheck('CLOSED');

function clickAndCheck(status: string, multiple = false, expected = [status]) {
wrapper
@@ -69,10 +69,7 @@ function shallowRender(props: Partial<StatusFacet['props']> = {}) {
CONFIRMED: 8,
REOPENED: 0,
RESOLVED: 0,
CLOSED: 8,
TO_REVIEW: 150,
IN_REVIEW: 7,
REVIEWED: 1105
CLOSED: 8
}}
statuses={[]}
{...props}

+ 1
- 32
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/TypeFacet-test.tsx ファイルの表示

@@ -20,7 +20,7 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { click } from 'sonar-ui-common/helpers/testUtils';
import { TypeFacet } from '../TypeFacet';
import TypeFacet from '../TypeFacet';

it('should render open by default', () => {
expect(shallowRender({ types: ['VULNERABILITY', 'CODE_SMELL'] })).toMatchSnapshot();
@@ -45,7 +45,6 @@ it('should select a type', () => {
const wrapper = shallowRender({ onChange });
clickAndCheck('CODE_SMELL');
clickAndCheck('VULNERABILITY', true, ['CODE_SMELL', 'VULNERABILITY']);
clickAndCheck('SECURITY_HOTSPOT');

function clickAndCheck(type: string, multiple = false, expected = [type]) {
wrapper
@@ -57,42 +56,12 @@ it('should select a type', () => {
}
});

it('should display the hotspot newsbox', () => {
expect(shallowRender({ types: ['SECURITY_HOTSPOT'] }).find('NewsBox')).toMatchSnapshot();
expect(
shallowRender({ types: [] })
.find('NewsBox')
.exists()
).toBe(true);
});

it('should display the hotspot tooltip helper only', () => {
let wrapper = shallowRender({ types: ['SECURITY_HOTSPOT'], newsBoxDismissHotspots: true });
expect(wrapper.find('NewsBox').exists()).toBe(false);
expect(
wrapper
.find(`FacetItemsList`)
.find(`FacetItem[value="SECURITY_HOTSPOT"]`)
.prop('name')
).toMatchSnapshot();

wrapper = shallowRender({ types: ['BUGS'], newsBoxDismissHotspots: true });
expect(wrapper.find('NewsBox').exists()).toBe(false);
expect(
wrapper
.find(`FacetItemsList`)
.find(`FacetItem[value="SECURITY_HOTSPOT"]`)
.prop('name')
).toMatchSnapshot();
});

function shallowRender(props: Partial<TypeFacet['props']> = {}) {
return shallow(
<TypeFacet
fetching={false}
onChange={jest.fn()}
onToggle={jest.fn()}
setCurrentUserSetting={jest.fn()}
stats={{ BUG: 0, VULNERABILITY: 2, CODE_SMELL: 5, SECURITY_HOTSPOT: 1 }}
types={[]}
{...props}

+ 6
- 6
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap ファイルの表示

@@ -2,7 +2,7 @@

exports[`should render facets for developer 1`] = `
Array [
"Connect(TypeFacet)",
"TypeFacet",
"SeverityFacet",
"ResolutionFacet",
"StatusFacet",
@@ -20,7 +20,7 @@ Array [

exports[`should render facets for directory 1`] = `
Array [
"Connect(TypeFacet)",
"TypeFacet",
"SeverityFacet",
"ResolutionFacet",
"StatusFacet",
@@ -37,7 +37,7 @@ Array [

exports[`should render facets for global page 1`] = `
Array [
"Connect(TypeFacet)",
"TypeFacet",
"SeverityFacet",
"ResolutionFacet",
"StatusFacet",
@@ -54,7 +54,7 @@ Array [

exports[`should render facets for module 1`] = `
Array [
"Connect(TypeFacet)",
"TypeFacet",
"SeverityFacet",
"ResolutionFacet",
"StatusFacet",
@@ -72,7 +72,7 @@ Array [

exports[`should render facets for project 1`] = `
Array [
"Connect(TypeFacet)",
"TypeFacet",
"SeverityFacet",
"ResolutionFacet",
"StatusFacet",
@@ -90,7 +90,7 @@ Array [

exports[`should render facets when my issues are selected 1`] = `
Array [
"Connect(TypeFacet)",
"TypeFacet",
"SeverityFacet",
"ResolutionFacet",
"StatusFacet",

+ 1
- 57
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap ファイルの表示

@@ -79,62 +79,6 @@ exports[`should render correctly 1`] = `
tooltip="issue.status.RESOLVED"
value="RESOLVED"
/>
</FacetItemsList>
<FacetItemsList
title="issue.type.SECURITY_HOTSPOT.plural"
>
<FacetItem
active={false}
disabled={false}
halfWidth={true}
key="TO_REVIEW"
loading={false}
name={
<StatusHelper
status="TO_REVIEW"
/>
}
onClick={[Function]}
stat="150"
tooltip="issue.status.TO_REVIEW"
value="TO_REVIEW"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={true}
key="REVIEWED"
loading={false}
name={
<StatusHelper
status="REVIEWED"
/>
}
onClick={[Function]}
stat="1.1short_number_suffix.k"
tooltip="issue.status.REVIEWED"
value="REVIEWED"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={true}
key="IN_REVIEW"
loading={false}
name={
<StatusHelper
status="IN_REVIEW"
/>
}
onClick={[Function]}
stat="7"
tooltip="issue.status.IN_REVIEW"
value="IN_REVIEW"
/>
</FacetItemsList>
<FacetItemsList
title="issues.issues_and_hotspots"
>
<FacetItem
active={false}
disabled={false}
@@ -153,7 +97,7 @@ exports[`should render correctly 1`] = `
/>
</FacetItemsList>
<MultipleSelectionHint
options={8}
options={5}
values={0}
/>
</FacetBox>

+ 0
- 115
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/TypeFacet-test.tsx.snap ファイルの表示

@@ -1,98 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should display the hotspot newsbox 1`] = `
<NewsBox
onClose={[Function]}
title="issue.type.SECURITY_HOTSPOT.plural"
>
<p>
issues.hotspots.helper
</p>
<p
className="text-right spacer-top"
>
<Link
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/user-guide/security-hotspots/"
>
learn_more
</Link>
</p>
</NewsBox>
`;

exports[`should display the hotspot tooltip helper only 1`] = `
<span
className="display-flex-center"
>
<IssueTypeIcon
className="little-spacer-right"
query="SECURITY_HOTSPOT"
/>
issue.type.SECURITY_HOTSPOT
<HelpTooltip
className="little-spacer-left"
overlay={
<React.Fragment>
<p>
issues.hotspots.helper
</p>
<hr
className="spacer-top spacer-bottom"
/>
<Link
onClick={[Function]}
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/user-guide/security-hotspots/"
>
learn_more
</Link>
</React.Fragment>
}
/>
</span>
`;

exports[`should display the hotspot tooltip helper only 2`] = `
<span
className="display-flex-center"
>
<IssueTypeIcon
className="little-spacer-right"
query="SECURITY_HOTSPOT"
/>
issue.type.SECURITY_HOTSPOT
<HelpTooltip
className="little-spacer-left"
overlay={
<React.Fragment>
<p>
issues.hotspots.helper
</p>
<hr
className="spacer-top spacer-bottom"
/>
<Link
onClick={[Function]}
onlyActiveOnIndex={false}
style={Object {}}
target="_blank"
to="/documentation/user-guide/security-hotspots/"
>
learn_more
</Link>
</React.Fragment>
}
/>
</span>
`;

exports[`should render open by default 1`] = `
<FacetBox
property="types"
@@ -177,28 +84,6 @@ exports[`should render open by default 1`] = `
stat="5"
value="CODE_SMELL"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={false}
key="SECURITY_HOTSPOT"
loading={false}
name={
<span
className="display-flex-center"
>
<IssueTypeIcon
className="little-spacer-right"
query="SECURITY_HOTSPOT"
/>
issue.type.SECURITY_HOTSPOT
</span>
}
onClick={[Function]}
stat="1"
value="SECURITY_HOTSPOT"
/>
</FacetItemsList>
<MultipleSelectionHint
options={4}

+ 1
- 11
server/sonar-web/src/main/js/apps/issues/utils.ts ファイルの表示

@@ -273,13 +273,6 @@ export function scrollToIssue(issue: string, smooth = true) {
}
}

export function shouldOpenSeverityFacet(openFacets: T.Dict<boolean>, query: Partial<Query>) {
return (
openFacets.severities ||
!(query.types && query.types.length === 1 && query.types[0] === 'SECURITY_HOTSPOT')
);
}

export function shouldOpenStandardsFacet(
openFacets: T.Dict<boolean>,
query: Partial<Query>
@@ -316,10 +309,7 @@ export function shouldOpenSonarSourceSecurityFacet(
}

function isFilteredBySecurityIssueTypes(query: Partial<Query>): boolean {
return (
query.types !== undefined &&
(query.types.includes('SECURITY_HOTSPOT') || query.types.includes('VULNERABILITY'))
);
return query.types !== undefined && query.types.includes('VULNERABILITY');
}

function isOneStandardChildFacetOpen(openFacets: T.Dict<boolean>, query: Partial<Query>): boolean {

+ 1
- 4
server/sonar-web/src/main/js/types/types.d.ts ファイルの表示

@@ -218,10 +218,7 @@ declare namespace T {
value: string;
}

type CurrentUserSettingNames =
| 'notifications.optOut'
| 'notifications.readDate'
| 'newsbox.dismiss.hotspots';
type CurrentUserSettingNames = 'notifications.optOut' | 'notifications.readDate';

export interface CustomMeasure {
createdAt?: string;

+ 0
- 3
sonar-core/src/main/resources/org/sonar/l10n/core.properties ファイルの表示

@@ -798,9 +798,6 @@ issues.my_issues=My Issues
issues.no_my_issues=There are no issues assigned to you.
issues.no_issues=No Issues. Hooray!
issues.x_more_locations=+ {0} more location(s)
issues.issues_and_hotspots=Issues & Security Hotspots
issues.hotspots.helper=Security Hotspots aren't necessarily issues, but they need to be reviewed to make sure they aren't vulnerabilities.


#------------------------------------------------------------------------------
#

読み込み中…
キャンセル
保存