Sfoglia il codice sorgente

SONAR-12026 Add new hotspot status facet in issues page

tags/7.8
Grégoire Aubert 5 anni fa
parent
commit
e861cb3f45

+ 1
- 1
server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx Vedi File

@@ -38,7 +38,7 @@ interface Props {
stats: T.Dict<number> | undefined;
}

const RESOLUTIONS = ['', 'FIXED', 'FALSE-POSITIVE', 'WONTFIX', 'REMOVED'];
const RESOLUTIONS = ['', 'FALSE-POSITIVE', 'FIXED', 'REMOVED', 'WONTFIX'];

export default class ResolutionFacet extends React.PureComponent<Props> {
property = 'resolutions';

+ 13
- 5
server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx Vedi File

@@ -38,14 +38,14 @@ interface Props {
statuses: string[];
}

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

export default class StatusFacet extends React.PureComponent<Props> {
property = 'statuses';

static defaultProps = {
open: true
};
static defaultProps = { open: true };

handleItemClick = (itemValue: string, multiple: boolean) => {
const { statuses } = this.props;
@@ -110,7 +110,15 @@ export default class StatusFacet extends React.PureComponent<Props> {
<DeferredSpinner loading={this.props.fetching} />
{this.props.open && (
<>
<FacetItemsList>{STATUSES.map(this.renderItem)}</FacetItemsList>
<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} />
</>
)}

+ 81
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx Vedi File

@@ -0,0 +1,81 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme';
import StatusFacet from '../StatusFacet';
import { click } from '../../../../helpers/testUtils';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should toggle status facet', () => {
const onToggle = jest.fn();
const wrapper = shallowRender({ onToggle });
click(wrapper.children('FacetHeader'));
expect(onToggle).toBeCalledWith('statuses');
});

it('should clear status facet', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange, statuses: ['TO_REVIEW'] });
wrapper.children('FacetHeader').prop<Function>('onClear')();
expect(onChange).toBeCalledWith({ statuses: [] });
});

it('should select a status', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange });
clickAndCheck('TO_REVIEW');
clickAndCheck('OPEN', true, ['OPEN', 'TO_REVIEW']);
clickAndCheck('CONFIRMED');

function clickAndCheck(status: string, multiple = false, expected = [status]) {
wrapper
.find(`FacetItemsList`)
.find(`FacetItem[value="${status}"]`)
.prop<Function>('onClick')(status, multiple);
expect(onChange).lastCalledWith({ statuses: expected });
wrapper.setProps({ statuses: expected });
}
});

function shallowRender(props: Partial<StatusFacet['props']> = {}) {
return shallow(
<StatusFacet
fetching={false}
onChange={jest.fn()}
onToggle={jest.fn()}
open={true}
stats={{
OPEN: 104,
CONFIRMED: 8,
REOPENED: 0,
RESOLVED: 0,
CLOSED: 8,
TO_REVIEW: 150,
IN_REVIEW: 7,
REVIEWED: 1105
}}
statuses={[]}
{...props}
/>
);
}

+ 163
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap Vedi File

@@ -0,0 +1,163 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<FacetBox
property="statuses"
>
<FacetHeader
name="issues.facet.statuses"
onClear={[Function]}
onClick={[Function]}
open={true}
values={Array []}
/>
<DeferredSpinner
loading={false}
timeout={100}
/>
<FacetItemsList
title="issues"
>
<FacetItem
active={false}
disabled={false}
halfWidth={true}
key="OPEN"
loading={false}
name={
<StatusHelper
status="OPEN"
/>
}
onClick={[Function]}
stat="104"
tooltip="issue.status.OPEN"
value="OPEN"
/>
<FacetItem
active={false}
disabled={false}
halfWidth={true}
key="CONFIRMED"
loading={false}
name={
<StatusHelper
status="CONFIRMED"
/>
}
onClick={[Function]}
stat="8"
tooltip="issue.status.CONFIRMED"
value="CONFIRMED"
/>
<FacetItem
active={false}
disabled={true}
halfWidth={true}
key="REOPENED"
loading={false}
name={
<StatusHelper
status="REOPENED"
/>
}
onClick={[Function]}
stat={0}
tooltip="issue.status.REOPENED"
value="REOPENED"
/>
<FacetItem
active={false}
disabled={true}
halfWidth={true}
key="RESOLVED"
loading={false}
name={
<StatusHelper
status="RESOLVED"
/>
}
onClick={[Function]}
stat={0}
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}
halfWidth={true}
key="CLOSED"
loading={false}
name={
<StatusHelper
status="CLOSED"
/>
}
onClick={[Function]}
stat="8"
tooltip="issue.status.CLOSED"
value="CLOSED"
/>
</FacetItemsList>
<MultipleSelectionHint
options={8}
values={0}
/>
</FacetBox>
`;

+ 8
- 2
server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx Vedi File

@@ -21,8 +21,14 @@ import * as React from 'react';

interface Props {
children?: React.ReactNode;
title?: string;
}

export default function FacetItemsList(props: Props) {
return <div className="search-navigator-facet-list">{props.children}</div>;
export default function FacetItemsList({ children, title }: Props) {
return (
<div className="search-navigator-facet-list">
{title && <div className="search-navigator-facet-list-title">{title}</div>}
{children}
</div>
);
}

+ 10
- 0
server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx Vedi File

@@ -30,3 +30,13 @@ it('should render', () => {
)
).toMatchSnapshot();
});

it('should render with title', () => {
expect(
shallow(
<FacetItemsList title="title test">
<div />
</FacetItemsList>
)
).toMatchSnapshot();
});

+ 13
- 0
server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap Vedi File

@@ -7,3 +7,16 @@ exports[`should render 1`] = `
<div />
</div>
`;

exports[`should render with title 1`] = `
<div
className="search-navigator-facet-list"
>
<div
className="search-navigator-facet-list-title"
>
title test
</div>
<div />
</div>
`;

+ 4
- 1
server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx Vedi File

@@ -31,7 +31,10 @@ const statusIcons: T.Dict<(props: IconProps) => React.ReactElement<any>> = {
confirmed: ConfirmedStatusIcon,
reopened: ReopenedStatusIcon,
resolved: ResolvedStatusIcon,
closed: ClosedStatusIcon
closed: ClosedStatusIcon,
to_review: OpenStatusIcon,
in_review: ConfirmedStatusIcon,
reviewed: ResolvedStatusIcon
};

export default function StatusIcon(props: Props) {

+ 11
- 0
server/sonar-web/src/main/js/components/search-navigator.css Vedi File

@@ -358,6 +358,17 @@ a.search-navigator-facet:focus,
font-size: 0;
}

.search-navigator-facet-list-title {
margin: 0 var(--gridSize) calc(var(--gridSize) / 2);
font-size: var(--smallFontSize);
font-weight: bold;
}

.search-navigator-facet-list + .search-navigator-facet-list > .search-navigator-facet-list-title {
border-top: 1px solid var(--barBorderColor);
padding-top: var(--gridSize);
}

.search-navigator-facet-empty {
margin: 0 0 0 0;
padding: 0 10px 10px;

+ 7
- 9
sonar-core/src/main/resources/org/sonar/l10n/core.properties Vedi File

@@ -645,23 +645,19 @@ issue.type.VULNERABILITY.plural=Vulnerabilities
issue.type.SECURITY_HOTSPOT.plural=Security Hotspots

issue.status.REOPENED=Reopened
issue.status.REOPENED.description=Transitioned to and then back from some other status.
issue.status.RESOLVED=Resolved
issue.status.RESOLVED.description=Manually marked as corrected.
issue.status.OPEN=Open
issue.status.OPEN.description=Untouched. This status is set automatically at issue creation.
issue.status.CONFIRMED=Confirmed
issue.status.CONFIRMED.description=Manually examined and affirmed as an issue that needs attention.
issue.status.CLOSED=Closed
issue.status.CLOSED.description=Non-active and no longer requiring attention.
issue.status.TOREVIEW=To Review
issue.status.TOREVIEW.description=A review is required to check for a vulnerability.
issue.status.TO_REVIEW=To Review
issue.status.IN_REVIEW=In Review
issue.status.REVIEWED=Reviewed

issue.resolution.FALSE-POSITIVE=False Positive
issue.resolution.FALSE-POSITIVE.description=Issues that manual review determined were False Positives. Effort from these issues is ignored.
issue.resolution.FIXED=Fixed
issue.resolution.FIXED.description=Issues that were corrected in code and reanalyzed.
issue.resolution.WONTFIX=Won't fix
issue.resolution.WONTFIX=Won't Fix
issue.resolution.WONTFIX.description=Issues that are accepted in this context. They and their effort will be ignored.
issue.resolution.REMOVED=Removed
issue.resolution.REMOVED.description=Either the rule or the resource was changed (removed, relocated, parameters changed, etc.) so that analysis no longer finds these issues.
@@ -687,6 +683,7 @@ 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.


@@ -723,6 +720,7 @@ issues.facet.types=Type
issues.facet.severities=Severity
issues.facet.projects=Project
issues.facet.statuses=Status
issues.facet.hotspotStatuses=Hotspot Status
issues.facet.assignees=Assignee
issues.facet.files=File
issues.facet.modules=Module
@@ -2100,7 +2098,7 @@ projects_role.admin.desc=Access project settings and perform administration task
projects_role.issueadmin=Administer Issues
projects_role.issueadmin.desc=Change the type and severity of issues, resolve issues as being "won't fix" or "false-positive" (users also need "Browse" permission).
projects_role.securityhotspotadmin=Administer Security Hotspots
projects_role.securityhotspotadmin.desc=Detect a Vulnerability from a Security Hotspot. Reject, clear, accept, reopen a Security Hotspot (users also need "Browse" permissions).
projects_role.securityhotspotadmin.desc=Open a Vulnerability from a Security Hotspot. Resolved a Security Hotspot as reviewed, set it as in review or reset it as to review (users also need Browse permission).
projects_role.user=Browse
projects_role.user.desc=Access a project, browse its measures and issues, confirm or resolve issues as "fixed", change the assignee, comment on issues and change tags.
projects_role.codeviewer=See Source Code

Loading…
Annulla
Salva