From 83331c934bd45f136efb35662e67da61d0509e85 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Mon, 23 Dec 2019 12:00:17 +0100 Subject: [PATCH] SONAR-12721 Add leakperiod filter --- .../src/main/js/api/security-hotspots.ts | 1 + .../VulnerabilitiesAndHotspots-test.tsx.snap | 6 +- .../main/js/apps/overview/main/enhance.tsx | 12 +++ .../securityHotspots/SecurityHotspotsApp.tsx | 26 ++++++- .../SecurityHotspotsAppRenderer.tsx | 2 + .../__tests__/SecurityHotspotsApp-test.tsx | 11 ++- .../SecurityHotspotsAppRenderer-test.tsx | 6 +- .../SecurityHotspotsApp-test.tsx.snap | 1 + .../SecurityHotspotsAppRenderer-test.tsx.snap | 2 + .../securityHotspots/components/FilterBar.tsx | 21 ++++- .../components/__tests__/FilterBar-test.tsx | 29 ++++++- .../__snapshots__/FilterBar-test.tsx.snap | 78 +++++++++++++++++++ .../src/main/js/types/security-hotspots.ts | 1 + .../resources/org/sonar/l10n/core.properties | 2 + 14 files changed, 189 insertions(+), 9 deletions(-) diff --git a/server/sonar-web/src/main/js/api/security-hotspots.ts b/server/sonar-web/src/main/js/api/security-hotspots.ts index ced115b7e49..6f266bb7801 100644 --- a/server/sonar-web/src/main/js/api/security-hotspots.ts +++ b/server/sonar-web/src/main/js/api/security-hotspots.ts @@ -53,6 +53,7 @@ export function getSecurityHotspots( status?: HotspotStatus; resolution?: HotspotResolution; onlyMine?: boolean; + sinceLeakPeriod?: boolean; } & BranchParameters ): Promise { return getJSON('/api/hotspots/search', data).catch(throwGlobalError); diff --git a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/VulnerabilitiesAndHotspots-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/VulnerabilitiesAndHotspots-test.tsx.snap index ec2a9b4e238..7aed1c83259 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/VulnerabilitiesAndHotspots-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/main/__tests__/__snapshots__/VulnerabilitiesAndHotspots-test.tsx.snap @@ -136,8 +136,9 @@ exports[`should render correctly 1`] = ` style={Object {}} to={ Object { - "pathname": "/project/issues", + "pathname": "/security_hotspots", "query": Object { + "assignedToMe": "false", "id": "my-project", "resolved": "false", "types": "SECURITY_HOTSPOT", @@ -269,8 +270,9 @@ exports[`should render correctly 1`] = ` style={Object {}} to={ Object { - "pathname": "/project/issues", + "pathname": "/security_hotspots", "query": Object { + "assignedToMe": "false", "id": "my-project", "resolved": "false", "sinceLeakPeriod": "true", diff --git a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx index 613a4d0ca35..4caf589f566 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/enhance.tsx @@ -160,6 +160,18 @@ export default function enhance(ComposedComponent: React.ComponentType + {formatMeasure(value, 'SHORT_INT')} + + ); + } + return ( {formatMeasure(value, 'SHORT_INT')} diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsApp.tsx index 4856ce425af..2178798ee2d 100644 --- a/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsApp.tsx +++ b/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsApp.tsx @@ -23,7 +23,7 @@ import { addNoFooterPageClass, removeNoFooterPageClass } from 'sonar-ui-common/h import { getSecurityHotspotList, getSecurityHotspots } from '../../api/security-hotspots'; import { withCurrentUser } from '../../components/hoc/withCurrentUser'; import { Router } from '../../components/hoc/withRouter'; -import { getBranchLikeQuery } from '../../helpers/branch-like'; +import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../helpers/branch-like'; import { getStandards } from '../../helpers/security-standard'; import { isLoggedIn } from '../../helpers/users'; import { BranchLike } from '../../types/branch-like'; @@ -71,7 +71,7 @@ export class SecurityHotspotsApp extends React.PureComponent { securityCategories: {}, selectedHotspotKey: undefined, filters: { - assignedToMe: isLoggedIn(this.props.currentUser) ? true : false, + ...this.constructFiltersFromProps(props), status: HotspotStatusFilter.TO_REVIEW } }; @@ -90,6 +90,17 @@ export class SecurityHotspotsApp extends React.PureComponent { ) { this.fetchInitialData(); } + + if ( + !isSameBranchLike(this.props.branchLike, previous.branchLike) || + isLoggedIn(this.props.currentUser) !== isLoggedIn(previous.currentUser) || + this.props.location.query.assignedToMe !== previous.location.query.assignedToMe || + this.props.location.query.newCode !== previous.location.query.newCode + ) { + this.setState(({ filters }) => ({ + filters: { ...this.constructFiltersFromProps, ...filters } + })); + } } componentWillUnmount() { @@ -97,6 +108,16 @@ export class SecurityHotspotsApp extends React.PureComponent { this.mounted = false; } + constructFiltersFromProps(props: Props): Pick { + return { + assignedToMe: + props.location.query.assignedToMe !== undefined + ? props.location.query.assignedToMe === 'true' + : isLoggedIn(props.currentUser), + newCode: isPullRequest(props.branchLike) || props.location.query.newCode === 'true' + }; + } + handleCallFailure = () => { if (this.mounted) { this.setState({ loading: false }); @@ -153,6 +174,7 @@ export class SecurityHotspotsApp extends React.PureComponent { status, resolution, onlyMine: filters.assignedToMe, + sinceLeakPeriod: filters.newCode, ...getBranchLikeQuery(branchLike) }); } diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsAppRenderer.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsAppRenderer.tsx index 593e13b9291..7850fd95f56 100644 --- a/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/securityHotspots/SecurityHotspotsAppRenderer.tsx @@ -26,6 +26,7 @@ import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget'; import Suggestions from '../../app/components/embed-docs-modal/Suggestions'; import ScreenPositionHelper from '../../components/common/ScreenPositionHelper'; +import { isBranch } from '../../helpers/branch-like'; import { BranchLike } from '../../types/branch-like'; import { HotspotFilters, HotspotUpdate, RawHotspot } from '../../types/security-hotspots'; import FilterBar from './components/FilterBar'; @@ -63,6 +64,7 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx index b50b0790cbc..b51cdee698d 100644 --- a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsApp-test.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import { addNoFooterPageClass } from 'sonar-ui-common/helpers/pages'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { getSecurityHotspotList, getSecurityHotspots } from '../../../api/security-hotspots'; -import { mockBranch } from '../../../helpers/mocks/branch-like'; +import { mockBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; import { mockRawHotspot } from '../../../helpers/mocks/security-hotspots'; import { getStandards } from '../../../helpers/security-standard'; import { @@ -140,6 +140,15 @@ it('should load data correctly when hotspot key list is forced', async () => { expect(getSecurityHotspots).toHaveBeenCalled(); }); +it('should set "leakperiod" filter according to context (branchlike & location query)', () => { + expect(shallowRender().state().filters.newCode).toBe(false); + expect(shallowRender({ branchLike: mockPullRequest() }).state().filters.newCode).toBe(true); + expect( + shallowRender({ location: mockLocation({ query: { newCode: 'true' } }) }).state().filters + .newCode + ).toBe(true); +}); + it('should handle hotspot update', async () => { const key = 'hotspotKey'; const hotspots = [mockRawHotspot(), mockRawHotspot({ key })]; diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx index a721ed06963..d57ddc474f1 100644 --- a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx @@ -73,7 +73,11 @@ function shallowRender(props: Partial = {}) { onShowAllHotspots={jest.fn()} onUpdateHotspot={jest.fn()} securityCategories={{}} - filters={{ assignedToMe: false, status: HotspotStatusFilter.TO_REVIEW }} + filters={{ + assignedToMe: false, + newCode: false, + status: HotspotStatusFilter.TO_REVIEW + }} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap index f76e3f7b88c..b297c4c7e6d 100644 --- a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap @@ -13,6 +13,7 @@ exports[`should render correctly 1`] = ` filters={ Object { "assignedToMe": false, + "newCode": false, "status": "TO_REVIEW", } } diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap index cb0a3655f9c..fa151377e03 100644 --- a/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/securityHotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap @@ -8,10 +8,12 @@ exports[`should render correctly 1`] = ` filters={ Object { "assignedToMe": false, + "newCode": false, "status": "TO_REVIEW", } } isStaticListOfHotspots={true} + onBranch={false} onChangeFilters={[MockFunction]} onShowAllHotspots={[MockFunction]} /> diff --git a/server/sonar-web/src/main/js/apps/securityHotspots/components/FilterBar.tsx b/server/sonar-web/src/main/js/apps/securityHotspots/components/FilterBar.tsx index 4f42abac06f..2a44cac2d27 100644 --- a/server/sonar-web/src/main/js/apps/securityHotspots/components/FilterBar.tsx +++ b/server/sonar-web/src/main/js/apps/securityHotspots/components/FilterBar.tsx @@ -29,6 +29,7 @@ export interface FilterBarProps { currentUser: T.CurrentUser; filters: HotspotFilters; isStaticListOfHotspots: boolean; + onBranch: boolean; onChangeFilters: (filters: Partial) => void; onShowAllHotspots: () => void; } @@ -39,6 +40,11 @@ const statusOptions: Array<{ label: string; value: string }> = [ { value: HotspotStatusFilter.SAFE, label: translate('hotspot.filters.status.safe') } ]; +const periodOptions = [ + { value: true, label: translate('hotspot.filters.period.since_leak_period') }, + { value: false, label: translate('hotspot.filters.period.overall') } +]; + export enum AssigneeFilterOption { ALL = 'all', ME = 'me' @@ -50,7 +56,7 @@ const assigneeFilterOptions = [ ]; export function FilterBar(props: FilterBarProps) { - const { currentUser, filters, isStaticListOfHotspots } = props; + const { currentUser, filters, isStaticListOfHotspots, onBranch } = props; return (
@@ -85,6 +91,19 @@ export function FilterBar(props: FilterBarProps) { searchable={false} value={filters.status} /> + + {onBranch && ( +
`; @@ -111,5 +130,64 @@ exports[`should render correctly: logged-in 1`] = ` searchable={false} value="TO_REVIEW" /> + `; diff --git a/server/sonar-web/src/main/js/types/security-hotspots.ts b/server/sonar-web/src/main/js/types/security-hotspots.ts index 29a12a207e3..e6869dff68e 100644 --- a/server/sonar-web/src/main/js/types/security-hotspots.ts +++ b/server/sonar-web/src/main/js/types/security-hotspots.ts @@ -47,6 +47,7 @@ export enum HotspotStatusOption { export interface HotspotFilters { assignedToMe: boolean; + newCode: boolean; status: HotspotStatusFilter; } 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 3c1dc389d22..9b6c2849812 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -676,6 +676,8 @@ hotspot.filters.assignee.assigned_to_me=Assigned to me hotspot.filters.assignee.all=All hotspot.filters.status.to_review=To review hotspot.filters.status.fixed=Reviewed as fixed +hotspot.filters.period.since_leak_period=New code +hotspot.filters.period.overall=Overall code hotspot.filters.status.safe=Reviewed as safe hotspot.filters.show_all=Show all hotspots hotspots.review_hotspot=Review Hotspot -- 2.39.5