]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12987 Improve loading's transition in hotspot page
authorPhilippe Perrin <philippe.perrin@sonarsource.com>
Tue, 23 Feb 2021 17:34:45 +0000 (18:34 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 25 Feb 2021 20:07:33 +0000 (20:07 +0000)
server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx
server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx
server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx

index 5f15f8816dd21ebc9eb6937024ec883573e7c42f..3ac430ec4de13e390063ea3ec5bc27b9828ed96f 100644 (file)
@@ -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;
   }
index 2eb9a5e8928607f7f3b5e4353fdf09c45febb755..4a8b9ab78f2538c1196a3287a2db9009d7435b44 100644 (file)
@@ -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 ? (
index de6de5e30c4cffe0582b1a9293ea3b45fadd8a51..fd0b36779f7f7ca8d9ffe87861972dec52ed63ba 100644 (file)
@@ -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({
index 483ece472725d67f9226bec0622c4c54f812d63b..a5e4b85179817a002c918a56fa3c8320ec4af447 100644 (file)
@@ -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>
 `;
 
index 352423ed2f8feaec31d77c2b6ebcb64a9b042806..b65a32e8cd25f90c365dc4cf4bf8f14069f529a5 100644 (file)
@@ -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);
 
index b3725d4df4f09ab82dbc7c32e91e319d0f8aa2f2..97951b7ffb2f970db9b628762ae4e4fa0bef6a53 100644 (file)
@@ -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>
-  );
+    );
+  }
 }
index d1960aed00091933b3ee65fca7d2888bf6d6eeb1..e1c741bafd6a3fe84003c1eb84c2324bff859b79 100644 (file)
  */
 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();
 });
index 024a4a720b5571b906a37f43e4f4c58793d3abb6..930f45edbcf554c795130d877360f525e098fe20 100644 (file)
  */
 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' })];