From: Grégoire Aubert Date: Mon, 6 May 2019 15:12:05 +0000 (+0200) Subject: SONAR-12026 Add new hotspot status facet in issues page X-Git-Tag: 7.8~205 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e861cb3f45bc28434dc59d857c38e414dba2a0bc;p=sonarqube.git SONAR-12026 Add new hotspot status facet in issues page --- 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 index 5e069b27e14..958245f1ad6 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx @@ -38,7 +38,7 @@ interface Props { stats: T.Dict | undefined; } -const RESOLUTIONS = ['', 'FIXED', 'FALSE-POSITIVE', 'WONTFIX', 'REMOVED']; +const RESOLUTIONS = ['', 'FALSE-POSITIVE', 'FIXED', 'REMOVED', 'WONTFIX']; export default class ResolutionFacet extends React.PureComponent { property = 'resolutions'; 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 index 03d6864947a..ea83e7a20c2 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.tsx @@ -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 { 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 { {this.props.open && ( <> - {STATUSES.map(this.renderItem)} + + {STATUSES.map(this.renderItem)} + + + {HOTSPOT_STATUSES.map(this.renderItem)} + + + {COMMON_STATUSES.map(this.renderItem)} + )} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx new file mode 100644 index 00000000000..3b7b1482d35 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx @@ -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('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('onClick')(status, multiple); + expect(onChange).lastCalledWith({ statuses: expected }); + wrapper.setProps({ statuses: expected }); + } +}); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap new file mode 100644 index 00000000000..9d01da1c88e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap @@ -0,0 +1,163 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + + + + + } + onClick={[Function]} + stat="104" + tooltip="issue.status.OPEN" + value="OPEN" + /> + + } + onClick={[Function]} + stat="8" + tooltip="issue.status.CONFIRMED" + value="CONFIRMED" + /> + + } + onClick={[Function]} + stat={0} + tooltip="issue.status.REOPENED" + value="REOPENED" + /> + + } + onClick={[Function]} + stat={0} + tooltip="issue.status.RESOLVED" + value="RESOLVED" + /> + + + + } + onClick={[Function]} + stat="150" + tooltip="issue.status.TO_REVIEW" + value="TO_REVIEW" + /> + + } + onClick={[Function]} + stat="1.1short_number_suffix.k" + tooltip="issue.status.REVIEWED" + value="REVIEWED" + /> + + } + onClick={[Function]} + stat="7" + tooltip="issue.status.IN_REVIEW" + value="IN_REVIEW" + /> + + + + } + onClick={[Function]} + stat="8" + tooltip="issue.status.CLOSED" + value="CLOSED" + /> + + + +`; diff --git a/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx b/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx index f72a84915e8..b58c19d585b 100644 --- a/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx +++ b/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx @@ -21,8 +21,14 @@ import * as React from 'react'; interface Props { children?: React.ReactNode; + title?: string; } -export default function FacetItemsList(props: Props) { - return
{props.children}
; +export default function FacetItemsList({ children, title }: Props) { + return ( +
+ {title &&
{title}
} + {children} +
+ ); } diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx b/server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx index 82ebe6d53fd..c8f21ca697b 100644 --- a/server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx +++ b/server/sonar-web/src/main/js/components/facet/__tests__/FacetItemsList-test.tsx @@ -30,3 +30,13 @@ it('should render', () => { ) ).toMatchSnapshot(); }); + +it('should render with title', () => { + expect( + shallow( + +
+ + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap index 9962cfc364e..fe288787b43 100644 --- a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetItemsList-test.tsx.snap @@ -7,3 +7,16 @@ exports[`should render 1`] = `
`; + +exports[`should render with title 1`] = ` +
+
+ title test +
+
+
+`; diff --git a/server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx index a4d0e3bfcb9..74fd63f4bd1 100644 --- a/server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons-components/StatusIcon.tsx @@ -31,7 +31,10 @@ const statusIcons: T.Dict<(props: IconProps) => React.ReactElement> = { confirmed: ConfirmedStatusIcon, reopened: ReopenedStatusIcon, resolved: ResolvedStatusIcon, - closed: ClosedStatusIcon + closed: ClosedStatusIcon, + to_review: OpenStatusIcon, + in_review: ConfirmedStatusIcon, + reviewed: ResolvedStatusIcon }; export default function StatusIcon(props: Props) { diff --git a/server/sonar-web/src/main/js/components/search-navigator.css b/server/sonar-web/src/main/js/components/search-navigator.css index a723606ffb5..75c8d0f5110 100644 --- a/server/sonar-web/src/main/js/components/search-navigator.css +++ b/server/sonar-web/src/main/js/components/search-navigator.css @@ -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; 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 7c359ebaff4..8ed013ff783 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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