diff options
author | Philippe Perrin <philippe.perrin@sonarsource.com> | 2021-02-23 18:34:45 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-02-25 20:07:33 +0000 |
commit | cddc7ec79336701d2e617ec2c69cfc6972891080 (patch) | |
tree | 4c4d33f450c522d954decdc4ba4c52abc10adb7d /server | |
parent | 07ac34f4fdd39b28aeece0241074f8541dd9134f (diff) | |
download | sonarqube-cddc7ec79336701d2e617ec2c69cfc6972891080.tar.gz sonarqube-cddc7ec79336701d2e617ec2c69cfc6972891080.zip |
SONAR-12987 Improve loading's transition in hotspot page
Diffstat (limited to 'server')
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<Props, State> { componentDidMount() { this.mounted = true; - addSideBarClass(); this.fetchInitialData(); this.registerKeyboardEvents(); } @@ -134,7 +132,6 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> { } 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 && <DeferredSpinner className="huge-spacer-left big-spacer-top" />} + {loading && ( + <div className="layout-page"> + <div className="layout-page-side-inner"> + <DeferredSpinner className="big-spacer-top" /> + </div> + </div> + )} {!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]} /> - <DeferredSpinner - className="huge-spacer-left big-spacer-top" - /> + <div + className="layout-page" + > + <div + className="layout-page-side-inner" + > + <DeferredSpinner + className="big-spacer-top" + /> + </div> + </div> </div> `; 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<Props, State> { }; } + componentDidMount() { + addSideBarClass(); + } + componentDidUpdate(prevProps: Props) { // Force open the category of selected hotspot if ( @@ -79,6 +84,10 @@ export default class HotspotList extends React.Component<Props, State> { } } + 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<HotspotSimpleListProps> { + 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 ( - <div className="hotspots-list-single-category huge-spacer-bottom"> - <h1 className="hotspot-list-header bordered-bottom"> - <SecurityHotspotIcon className="spacer-right" /> - {translateWithParameters('hotspots.list_title', hotspotsTotal)} - </h1> - <div className="big-spacer-bottom"> - <div className="hotspot-category"> - <div className="hotspot-category-header"> - <strong className="flex-1 spacer-right break-word"> - {categoryLabel} - {categoryLabel && cweLabel && <hr />} - {cweLabel} - </strong> + const categoryLabel = + filterByCategory && + SECURITY_STANDARD_RENDERER[filterByCategory.standard](standards, filterByCategory.category); + + const cweLabel = + filterByCWE && SECURITY_STANDARD_RENDERER[SecurityStandard.CWE](standards, filterByCWE); + + return ( + <div className="hotspots-list-single-category huge-spacer-bottom"> + <h1 className="hotspot-list-header bordered-bottom"> + <SecurityHotspotIcon className="spacer-right" /> + {translateWithParameters('hotspots.list_title', hotspotsTotal)} + </h1> + <div className="big-spacer-bottom"> + <div className="hotspot-category"> + <div className="hotspot-category-header"> + <strong className="flex-1 spacer-right break-word"> + {categoryLabel} + {categoryLabel && cweLabel && <hr />} + {cweLabel} + </strong> + </div> + <ul> + {hotspots.map(h => ( + <li data-hotspot-key={h.key} key={h.key}> + <HotspotListItem + hotspot={h} + onClick={this.props.onHotspotClick} + selected={h.key === selectedHotspot.key} + /> + </li> + ))} + </ul> </div> - <ul> - {hotspots.map(h => ( - <li data-hotspot-key={h.key} key={h.key}> - <HotspotListItem - hotspot={h} - onClick={props.onHotspotClick} - selected={h.key === selectedHotspot.key} - /> - </li> - ))} - </ul> </div> + <ListFooter + count={hotspots.length} + loadMore={!loadingMore ? this.props.onLoadMore : undefined} + loading={loadingMore} + total={hotspotsTotal} + /> </div> - <ListFooter - count={hotspots.length} - loadMore={!loadingMore ? props.onLoadMore : undefined} - loading={loadingMore} - total={hotspotsTotal} - /> - </div> - ); + ); + } } 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<HotspotSimpleListProps> = {}) { const hotspots = [mockRawHotspot({ key: 'h1' }), mockRawHotspot({ key: 'h2' })]; |