From cddc7ec79336701d2e617ec2c69cfc6972891080 Mon Sep 17 00:00:00 2001 From: Philippe Perrin Date: Tue, 23 Feb 2021 18:34:45 +0100 Subject: [PATCH] SONAR-12987 Improve loading's transition in hotspot page --- .../security-hotspots/SecurityHotspotsApp.tsx | 3 - .../SecurityHotspotsAppRenderer.tsx | 8 +- .../__tests__/SecurityHotspotsApp-test.tsx | 7 -- .../SecurityHotspotsAppRenderer-test.tsx.snap | 14 ++- .../components/HotspotList.tsx | 9 ++ .../components/HotspotSimpleList.tsx | 107 ++++++++++-------- .../components/__tests__/HotspotList-test.tsx | 19 ++++ .../__tests__/HotspotSimpleList-test.tsx | 19 ++++ 8 files changed, 124 insertions(+), 62 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx index 5f15f8816dd..3ac430ec4de 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx @@ -22,7 +22,6 @@ import * as key from 'keymaster'; import { flatMap, range } from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; -import { addSideBarClass, removeSideBarClass } from 'sonar-ui-common/helpers/pages'; import { getMeasures } from '../../api/measures'; import { getSecurityHotspotList, getSecurityHotspots } from '../../api/security-hotspots'; import { withCurrentUser } from '../../components/hoc/withCurrentUser'; @@ -107,7 +106,6 @@ export class SecurityHotspotsApp extends React.PureComponent { componentDidMount() { this.mounted = true; - addSideBarClass(); this.fetchInitialData(); this.registerKeyboardEvents(); } @@ -134,7 +132,6 @@ export class SecurityHotspotsApp extends React.PureComponent { } componentWillUnmount() { - removeSideBarClass(); this.unregisterKeyboardEvents(); this.mounted = false; } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx index 2eb9a5e8928..4a8b9ab78f2 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx @@ -109,7 +109,13 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe onShowAllHotspots={props.onShowAllHotspots} /> - {loading && } + {loading && ( +
+
+ +
+
+ )} {!loading && (hotspots.length === 0 || !selectedHotspot ? ( diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx index de6de5e30c4..fd0b36779f7 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx @@ -19,7 +19,6 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { addSideBarClass } from 'sonar-ui-common/helpers/pages'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { getMeasures } from '../../../api/measures'; import { getSecurityHotspotList, getSecurityHotspots } from '../../../api/security-hotspots'; @@ -44,11 +43,6 @@ import SecurityHotspotsAppRenderer from '../SecurityHotspotsAppRenderer'; beforeEach(() => jest.clearAllMocks()); -jest.mock('sonar-ui-common/helpers/pages', () => ({ - addSideBarClass: jest.fn(), - removeSideBarClass: jest.fn() -})); - jest.mock('../../../api/measures', () => ({ getMeasures: jest.fn().mockResolvedValue([]) })); @@ -83,7 +77,6 @@ it('should load data correctly', async () => { expect(wrapper.state().loading).toBe(true); expect(wrapper.state().loadingMeasure).toBe(true); - expect(addSideBarClass).toBeCalled(); expect(getStandards).toBeCalled(); expect(getSecurityHotspots).toBeCalledWith( expect.objectContaining({ diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap index 483ece47272..a5e4b851798 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap @@ -486,9 +486,17 @@ exports[`should render correctly: loading 1`] = ` onChangeFilters={[MockFunction]} onShowAllHotspots={[MockFunction]} /> - +
+
+ +
+
`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx index 352423ed2f8..b65a32e8cd2 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx @@ -23,6 +23,7 @@ import * as React from 'react'; import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; import SecurityHotspotIcon from 'sonar-ui-common/components/icons/SecurityHotspotIcon'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; +import { addSideBarClass, removeSideBarClass } from 'sonar-ui-common/helpers/pages'; import { HotspotStatusFilter, RawHotspot, RiskExposure } from '../../../types/security-hotspots'; import { groupByCategory, RISK_EXPOSURE_LEVELS } from '../utils'; import HotspotCategory from './HotspotCategory'; @@ -58,6 +59,10 @@ export default class HotspotList extends React.Component { }; } + componentDidMount() { + addSideBarClass(); + } + componentDidUpdate(prevProps: Props) { // Force open the category of selected hotspot if ( @@ -79,6 +84,10 @@ export default class HotspotList extends React.Component { } } + componentWillUnmount() { + removeSideBarClass(); + } + groupHotspots = (hotspots: RawHotspot[], securityCategories: T.StandardSecurityCategories) => { const risks = groupBy(hotspots, h => h.vulnerabilityProbability); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx index b3725d4df4f..97951b7ffb2 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; import SecurityHotspotIcon from 'sonar-ui-common/components/icons/SecurityHotspotIcon'; import { translateWithParameters } from 'sonar-ui-common/helpers/l10n'; +import { addSideBarClass, removeSideBarClass } from 'sonar-ui-common/helpers/pages'; import { SecurityStandard, Standards } from '../../../types/security'; import { RawHotspot } from '../../../types/security-hotspots'; import { SECURITY_STANDARD_RENDERER } from '../utils'; @@ -41,58 +42,68 @@ export interface HotspotSimpleListProps { standards: Standards; } -export default function HotspotSimpleList(props: HotspotSimpleListProps) { - const { - filterByCategory, - filterByCWE, - hotspots, - hotspotsTotal, - loadingMore, - selectedHotspot, - standards - } = props; +export default class HotspotSimpleList extends React.Component { + componentDidMount() { + addSideBarClass(); + } - const categoryLabel = - filterByCategory && - SECURITY_STANDARD_RENDERER[filterByCategory.standard](standards, filterByCategory.category); + componentWillUnmount() { + removeSideBarClass(); + } - const cweLabel = - filterByCWE && SECURITY_STANDARD_RENDERER[SecurityStandard.CWE](standards, filterByCWE); + render() { + const { + filterByCategory, + filterByCWE, + hotspots, + hotspotsTotal, + loadingMore, + selectedHotspot, + standards + } = this.props; - return ( -
-

- - {translateWithParameters('hotspots.list_title', hotspotsTotal)} -

-
-
-
- - {categoryLabel} - {categoryLabel && cweLabel &&
} - {cweLabel} -
+ const categoryLabel = + filterByCategory && + SECURITY_STANDARD_RENDERER[filterByCategory.standard](standards, filterByCategory.category); + + const cweLabel = + filterByCWE && SECURITY_STANDARD_RENDERER[SecurityStandard.CWE](standards, filterByCWE); + + return ( +
+

+ + {translateWithParameters('hotspots.list_title', hotspotsTotal)} +

+
+
+
+ + {categoryLabel} + {categoryLabel && cweLabel &&
} + {cweLabel} +
+
+
    + {hotspots.map(h => ( +
  • + +
  • + ))} +
-
    - {hotspots.map(h => ( -
  • - -
  • - ))} -
+
- -
- ); + ); + } } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx index d1960aed000..e1c741bafd6 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx @@ -19,15 +19,34 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { addSideBarClass, removeSideBarClass } from 'sonar-ui-common/helpers/pages'; +import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots'; import { HotspotStatusFilter, RiskExposure } from '../../../../types/security-hotspots'; import HotspotList from '../HotspotList'; +jest.mock('sonar-ui-common/helpers/pages', () => ({ + addSideBarClass: jest.fn(), + removeSideBarClass: jest.fn() +})); + it('should render correctly', () => { expect(shallowRender()).toMatchSnapshot(); expect(shallowRender({ loadingMore: true })).toMatchSnapshot(); }); +it('should add/remove sidebar classes', async () => { + const wrapper = shallowRender(); + + await waitAndUpdate(wrapper); + + expect(addSideBarClass).toHaveBeenCalled(); + + wrapper.unmount(); + + expect(removeSideBarClass).toHaveBeenCalled(); +}); + it('should render correctly when the list of hotspot is static', () => { expect(shallowRender({ isStaticListOfHotspots: true })).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx index 024a4a720b5..930f45edbcf 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx @@ -19,10 +19,17 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { addSideBarClass, removeSideBarClass } from 'sonar-ui-common/helpers/pages'; +import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots'; import { SecurityStandard } from '../../../../types/security'; import HotspotSimpleList, { HotspotSimpleListProps } from '../HotspotSimpleList'; +jest.mock('sonar-ui-common/helpers/pages', () => ({ + addSideBarClass: jest.fn(), + removeSideBarClass: jest.fn() +})); + it('should render correctly', () => { expect(shallowRender()).toMatchSnapshot('filter by category'); expect(shallowRender({ filterByCategory: undefined, filterByCWE: '327' })).toMatchSnapshot( @@ -31,6 +38,18 @@ it('should render correctly', () => { expect(shallowRender({ filterByCWE: '327' })).toMatchSnapshot('filter by both'); }); +it('should add/remove sidebar classes', async () => { + const wrapper = shallowRender(); + + await waitAndUpdate(wrapper); + + expect(addSideBarClass).toHaveBeenCalled(); + + wrapper.unmount(); + + expect(removeSideBarClass).toHaveBeenCalled(); +}); + function shallowRender(props: Partial = {}) { const hotspots = [mockRawHotspot({ key: 'h1' }), mockRawHotspot({ key: 'h2' })]; -- 2.39.5