]> source.dussan.org Git - sonarqube.git/commitdiff
improve web performace of projects page
authorStas Vilchik <vilchiks@gmail.com>
Thu, 9 Mar 2017 15:44:28 +0000 (16:44 +0100)
committerGrégoire Aubert <gregaubert@users.noreply.github.com>
Fri, 10 Mar 2017 13:43:39 +0000 (14:43 +0100)
17 files changed:
server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js
server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js
server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js
server/sonar-web/src/main/js/apps/projects/filters/Filter.js
server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.js
server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js
server/sonar-web/src/main/js/apps/projects/filters/LanguageFilter.js
server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.js
server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.js
server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js
server/sonar-web/src/main/js/apps/projects/filters/SortingFilter.js
server/sonar-web/src/main/js/components/controls/ListFooter.js

index b3540396a2bc4854a6883cfc8f9e4ce376fc52d2..cf9378ad610b89a6b468946613df1435dd1c201d 100644 (file)
@@ -30,7 +30,7 @@ import LanguageFilter from '../filters/LanguageFilter';
 import SearchFilter from '../filters/SearchFilter';
 import { translate } from '../../../helpers/l10n';
 
-export default class PageSidebar extends React.Component {
+export default class PageSidebar extends React.PureComponent {
   static propTypes = {
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool.isRequired,
@@ -58,7 +58,7 @@ export default class PageSidebar extends React.Component {
 
             <h3>{translate('filters')}</h3>
             <SearchFilter
-                query={this.props.query}
+                query={this.props.query.search}
                 isFavorite={this.props.isFavorite}
                 organization={this.props.organization}/>
           </div>
index a9fa6a951093c35e1f698d0185dcc4e0e27f0972..6a63633b480d746002f288e4eef84ce6e3050603 100644 (file)
@@ -28,7 +28,7 @@ import FavoriteContainer from '../../../components/controls/FavoriteContainer';
 import Organization from '../../../components/shared/Organization';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 
-export default class ProjectCard extends React.Component {
+export default class ProjectCard extends React.PureComponent {
   props: {
     measures: { [string]: string },
     organization?: {},
index f03bd24cc5728dda5b81c7df56b5df9cab521ce5..d3908234509d7aa6a078de77fa889f0b386632d1 100644 (file)
@@ -21,9 +21,7 @@ import { connect } from 'react-redux';
 import ProjectCard from './ProjectCard';
 import { getComponent, getComponentMeasures } from '../../../store/rootReducer';
 
-export default connect(
-  (state, ownProps) => ({
-    project: getComponent(state, ownProps.projectKey),
-    measures: getComponentMeasures(state, ownProps.projectKey)
-  })
-)(ProjectCard);
+export default connect((state, ownProps) => ({
+  project: getComponent(state, ownProps.projectKey),
+  measures: getComponentMeasures(state, ownProps.projectKey)
+}))(ProjectCard);
index 56d4acc711414c932acfc99c126b546d0059d3ae..94d8c5fdaad1617b6016d73c2940da0aa65eba25 100644 (file)
@@ -26,7 +26,7 @@ import DuplicationsRating from '../../../components/ui/DuplicationsRating';
 import SizeRating from '../../../components/ui/SizeRating';
 import { translate } from '../../../helpers/l10n';
 
-export default class ProjectCardMeasures extends React.Component {
+export default class ProjectCardMeasures extends React.PureComponent {
   static propTypes = {
     measures: React.PropTypes.object
   };
index 46c46d36dc048a9699cd817420d8fba7dafa92c1..cd12fc07ae5a07cd8d34c3552d9f278fd57351b4 100644 (file)
@@ -21,7 +21,7 @@ import React from 'react';
 import Level from '../../../components/ui/Level';
 import { translate } from '../../../helpers/l10n';
 
-export default class ProjectCardQualityGate extends React.Component {
+export default class ProjectCardQualityGate extends React.PureComponent {
   static propTypes = {
     status: React.PropTypes.string
   };
index 06dc8c2537b1818ba0c1fbc40a76eb2a099466b2..93ba8a35c69da729ed5ff7f1cfc6a867b4ed0e5f 100644 (file)
@@ -23,7 +23,7 @@ import NoProjects from './NoProjects';
 import NoFavoriteProjects from './NoFavoriteProjects';
 import EmptyInstance from './EmptyInstance';
 
-export default class ProjectsList extends React.Component {
+export default class ProjectsList extends React.PureComponent {
   static propTypes = {
     projects: React.PropTypes.arrayOf(React.PropTypes.string),
     isFavorite: React.PropTypes.bool.isRequired,
index f13699a08072bba51adee240f4c5ae302683be12..e6bc17ee881c39bb3cc24882a2c7a2a93727c380 100644 (file)
@@ -23,54 +23,61 @@ import SortingFilter from './SortingFilter';
 import CoverageRating from '../../../components/ui/CoverageRating';
 import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings';
 
-export default class CoverageFilter extends React.Component {
+export default class CoverageFilter extends React.PureComponent {
   static propTypes = {
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool,
     organization: React.PropTypes.object
-  }
+  };
 
   property = 'coverage';
 
   renderOption = (option, selected) => {
     return (
-        <span>
-          <CoverageRating value={getCoverageRatingAverageValue(option)} size="small" muted={!selected}/>
-          <span className="spacer-left">
-            {getCoverageRatingLabel(option)}
-          </span>
+      <span>
+        <CoverageRating
+          value={getCoverageRatingAverageValue(option)}
+          size="small"
+          muted={!selected}/>
+        <span className="spacer-left">
+          {getCoverageRatingLabel(option)}
         </span>
+      </span>
     );
   };
 
   renderSort = () => {
     return (
-        <SortingFilter
-          property={this.property}
-          query={this.props.query}
-          isFavorite={this.props.isFavorite}
-          organization={this.props.organization}
-          sortDesc="right"/>
+      <SortingFilter
+        property={this.property}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}
+        sortDesc="right"/>
     );
-  }
+  };
 
   getFacetValueForOption = (facet, option) => {
     const map = ['80.0-*', '70.0-80.0', '50.0-70.0', '30.0-50.0', '*-30.0'];
     return facet[map[option - 1]];
   };
 
+  getOptions = () => [1, 2, 3, 4, 5];
+
+  renderName = () => 'Coverage';
+
   render () {
     return (
-        <FilterContainer
-            property={this.property}
-            getOptions={() => [1, 2, 3, 4, 5]}
-            renderName={() => 'Coverage'}
-            renderOption={this.renderOption}
-            renderSort={this.renderSort}
-            getFacetValueForOption={this.getFacetValueForOption}
-            query={this.props.query}
-            isFavorite={this.props.isFavorite}
-            organization={this.props.organization}/>
+      <FilterContainer
+        property={this.property}
+        getOptions={this.getOptions}
+        renderName={this.renderName}
+        renderOption={this.renderOption}
+        renderSort={this.renderSort}
+        getFacetValueForOption={this.getFacetValueForOption}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}/>
     );
   }
 }
index 818e80e6eca15d50d46e838f9007d0365502d0fa..d5faaccd712098a1806da2704237cc9e627ba9bb 100644 (file)
@@ -21,55 +21,65 @@ import React from 'react';
 import FilterContainer from './FilterContainer';
 import SortingFilter from './SortingFilter';
 import DuplicationsRating from '../../../components/ui/DuplicationsRating';
-import { getDuplicationsRatingLabel, getDuplicationsRatingAverageValue } from '../../../helpers/ratings';
+import {
+  getDuplicationsRatingLabel,
+  getDuplicationsRatingAverageValue
+} from '../../../helpers/ratings';
 
-export default class DuplicationsFilter extends React.Component {
+export default class DuplicationsFilter extends React.PureComponent {
   static propTypes = {
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool,
     organization: React.PropTypes.object
-  }
+  };
 
   property = 'duplications';
 
   renderOption = (option, selected) => {
     return (
-        <span>
-          <DuplicationsRating value={getDuplicationsRatingAverageValue(option)} size="small" muted={!selected}/>
-          <span className="spacer-left">
-            {getDuplicationsRatingLabel(option)}
-          </span>
+      <span>
+        <DuplicationsRating
+          value={getDuplicationsRatingAverageValue(option)}
+          size="small"
+          muted={!selected}/>
+        <span className="spacer-left">
+          {getDuplicationsRatingLabel(option)}
         </span>
+      </span>
     );
   };
 
   renderSort = () => {
     return (
-        <SortingFilter
-          property={this.property}
-          query={this.props.query}
-          isFavorite={this.props.isFavorite}
-          organization={this.props.organization}/>
+      <SortingFilter
+        property={this.property}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}/>
     );
-  }
+  };
 
   getFacetValueForOption = (facet, option) => {
     const map = ['*-3.0', '3.0-5.0', '5.0-10.0', '10.0-20.0', '20.0-*'];
     return facet[map[option - 1]];
   };
 
+  getOptions = () => [1, 2, 3, 4, 5];
+
+  renderName = () => 'Duplications';
+
   render () {
     return (
-        <FilterContainer
-            property={this.property}
-            getOptions={() => [1, 2, 3, 4, 5]}
-            renderName={() => 'Duplications'}
-            renderOption={this.renderOption}
-            renderSort={this.renderSort}
-            getFacetValueForOption={this.getFacetValueForOption}
-            query={this.props.query}
-            isFavorite={this.props.isFavorite}
-            organization={this.props.organization}/>
+      <FilterContainer
+        property={this.property}
+        getOptions={this.getOptions}
+        renderName={this.renderName}
+        renderOption={this.renderOption}
+        renderSort={this.renderSort}
+        getFacetValueForOption={this.getFacetValueForOption}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}/>
     );
   }
 }
index d8470030565edb88d8d55c54cf44bb0120236b42..0c99e74dfffeea8954217f4fc2a2496855b1fb8a 100644 (file)
@@ -24,7 +24,7 @@ import { getFilterUrl } from './utils';
 import { formatMeasure } from '../../../helpers/measures';
 import { translate } from '../../../helpers/l10n';
 
-export default class Filter extends React.Component {
+export default class Filter extends React.PureComponent {
   static propTypes = {
     value: React.PropTypes.any,
     property: React.PropTypes.string.isRequired,
index d690997ab40c43030200b29029cec7b6863cdade..5a9655c9bfe5113590ae9d39bc1a29d6bd72308b 100644 (file)
@@ -25,7 +25,9 @@ import { getProjectsAppFacetByProperty, getProjectsAppMaxFacetValue } from '../.
 const mapStateToProps = (state, ownProps) => ({
   value: ownProps.query[ownProps.property],
   facet: getProjectsAppFacetByProperty(state, ownProps.property),
-  maxFacetValue: getProjectsAppMaxFacetValue(state)
+  maxFacetValue: getProjectsAppMaxFacetValue(state),
+  // override query value to avoid re-rendering
+  query: undefined
 });
 
 export default connect(mapStateToProps)(withRouter(Filter));
index 5f1c907363423e69ce3522be10f998be4018a3d8..6330edaf39b08da4f34b709cb06ed5de270d83d8 100644 (file)
@@ -22,14 +22,14 @@ import FilterContainer from './FilterContainer';
 import SortingFilter from './SortingFilter';
 import Rating from '../../../components/ui/Rating';
 
-export default class IssuesFilter extends React.Component {
+export default class IssuesFilter extends React.PureComponent {
   static propTypes = {
     property: React.PropTypes.string.isRequired,
     name: React.PropTypes.string.isRequired,
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool,
     organization: React.PropTypes.object
-  }
+  };
 
   renderOption = (option, selected, value) => {
     const isUnderSelectedOption = this.highlightUnder(value) && option > value;
@@ -48,13 +48,13 @@ export default class IssuesFilter extends React.Component {
 
   renderSort = () => {
     return (
-        <SortingFilter
-          property={this.props.property}
-          query={this.props.query}
-          isFavorite={this.props.isFavorite}
-          organization={this.props.organization}/>
+      <SortingFilter
+        property={this.props.property}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}/>
     );
-  }
+  };
 
   highlightUnder (option) {
     return option !== null && option > 1;
@@ -64,12 +64,16 @@ export default class IssuesFilter extends React.Component {
     return facet[option];
   };
 
+  getOptions = () => [1, 2, 3, 4, 5];
+
+  renderName = () => this.props.name;
+
   render () {
     return (
         <FilterContainer
             property={this.props.property}
-            getOptions={() => [1, 2, 3, 4, 5]}
-            renderName={() => this.props.name}
+            getOptions={this.getOptions}
+            renderName={this.renderName}
             renderOption={this.renderOption}
             renderSort={this.renderSort}
             highlightUnder={this.highlightUnder}
index 549d50eb6acea7c9ad43d518bc4b691d0137d0ec..b1f45ee06b0c5958dcccf8342fef54b3ee2bd5ad 100644 (file)
@@ -23,19 +23,17 @@ import FilterContainer from './FilterContainer';
 import LanguageFilterOption from './LanguageFilterOption';
 import LanguageFilterFooter from './LanguageFilterFooter';
 
-export default class LanguageFilter extends React.Component {
+export default class LanguageFilter extends React.PureComponent {
   static propTypes = {
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool,
     organization: React.PropTypes.object
-  }
+  };
 
   property = 'languages';
 
   renderOption = option => {
-    return (
-      <LanguageFilterOption languageKey={option}/>
-    );
+    return <LanguageFilterOption languageKey={option}/>;
   };
 
   getSortedOptions (facet) {
@@ -44,26 +42,30 @@ export default class LanguageFilter extends React.Component {
 
   renderFooter = () => (
     <LanguageFilterFooter
-        property={this.property}
-        query={this.props.query}
-        isFavorite={this.props.isFavorite}
-        organization={this.props.organization}/>
+      property={this.property}
+      query={this.props.query}
+      isFavorite={this.props.isFavorite}
+      organization={this.props.organization}/>
   );
 
   getFacetValueForOption = (facet, option) => facet[option];
 
+  getOptions = facet => facet ? this.getSortedOptions(facet) : [];
+
+  renderName = () => 'Languages';
+
   render () {
     return (
       <FilterContainer
-          property={this.property}
-          getOptions={facet => facet ? this.getSortedOptions(facet) : []}
-          renderName={() => 'Languages'}
-          renderOption={this.renderOption}
-          renderFooter={this.renderFooter}
-          getFacetValueForOption={this.getFacetValueForOption}
-          query={this.props.query}
-          isFavorite={this.props.isFavorite}
-          organization={this.props.organization}/>
+        property={this.property}
+        getOptions={this.getOptions}
+        renderName={this.renderName}
+        renderOption={this.renderOption}
+        renderFooter={this.renderFooter}
+        getFacetValueForOption={this.getFacetValueForOption}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}/>
     );
   }
 }
index 4adf70bb5e8dc940624d401414bfdefb8448b473..62707a385910cfc29ad550902fc889ce9627a941 100644 (file)
@@ -21,28 +21,30 @@ import React from 'react';
 import FilterContainer from './FilterContainer';
 import Level from '../../../components/ui/Level';
 
-export default class QualityGateFilter extends React.Component {
+export default class QualityGateFilter extends React.PureComponent {
   renderOption = (option, selected) => {
-    return (
-        <Level level={option} small={true} muted={!selected}/>
-    );
+    return <Level level={option} small={true} muted={!selected}/>;
   };
 
   getFacetValueForOption = (facet, option) => {
     return facet[option];
   };
 
+  getOptions = () => ['OK', 'WARN', 'ERROR'];
+
+  renderName = () => 'Quality Gate';
+
   render () {
     return (
-        <FilterContainer
-            property="gate"
-            getOptions={() => ['OK', 'WARN', 'ERROR']}
-            renderName={() => 'Quality Gate'}
-            renderOption={this.renderOption}
-            getFacetValueForOption={this.getFacetValueForOption}
-            query={this.props.query}
-            isFavorite={this.props.isFavorite}
-            organization={this.props.organization}/>
+      <FilterContainer
+        property="gate"
+        getOptions={this.getOptions}
+        renderName={this.renderName}
+        renderOption={this.renderOption}
+        getFacetValueForOption={this.getFacetValueForOption}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}/>
     );
   }
 }
index 222bfb86f9e46bd7a1756f896305a9bc28fb5bf0..8fe85b240f08642c6d469e5b85c44ba752ddc231 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+// @flow
 import React from 'react';
 import { withRouter } from 'react-router';
 import classNames from 'classnames';
@@ -24,27 +25,31 @@ import debounce from 'lodash/debounce';
 import { getFilterUrl } from './utils';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 
-class SearchFilter extends React.Component {
-  static propTypes = {
-    query: React.PropTypes.object.isRequired,
-    router: React.PropTypes.object.isRequired,
-    isFavorite: React.PropTypes.bool,
-    organization: React.PropTypes.object
-  }
+type Props = {
+  query?: string,
+  router: { push: (string) => void },
+  isFavorite?: boolean,
+  organization?: {}
+};
+
+type State = {
+  userQuery?: string
+};
+
+class SearchFilter extends React.PureComponent {
+  handleSearch: (string) => void;
+  props: Props;
+  state: State;
 
-  constructor (props) {
+  constructor (props: Props) {
     super(props);
-    this.state = {
-      userQuery: props.query.search
-    };
+    this.state = { userQuery: props.query };
     this.handleSearch = debounce(this.handleSearch.bind(this), 250);
   }
 
-  componentWillReceiveProps (nextProps) {
-    if (this.props.query.search === this.state.userQuery && nextProps.query.search !== this.props.query.search) {
-      this.setState({
-        userQuery: nextProps.query.search || ''
-      });
+  componentWillReceiveProps (nextProps: Props) {
+    if (this.props.query === this.state.userQuery && nextProps.query !== this.props.query) {
+      this.setState({ userQuery: nextProps.query || '' });
     }
   }
 
@@ -63,7 +68,7 @@ class SearchFilter extends React.Component {
   render () {
     const { userQuery } = this.state;
     const inputClassName = classNames('input-super-large', {
-      'touched': userQuery && userQuery.length < 2
+      touched: userQuery && userQuery.length < 2
     });
 
     return (
index 0bc240dd12d6c8536184b0185fe2a7db82f2c621..53e081a4df073cca399df4db8df182f97996e056 100644 (file)
@@ -24,55 +24,65 @@ import SizeRating from '../../../components/ui/SizeRating';
 import { translate } from '../../../helpers/l10n';
 import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings';
 
-export default class SizeFilter extends React.Component {
+export default class SizeFilter extends React.PureComponent {
   static propTypes = {
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool,
     organization: React.PropTypes.object
-  }
+  };
 
   property = 'size';
 
   renderOption = (option, selected) => {
     return (
-        <span>
-          <SizeRating value={getSizeRatingAverageValue(option)} small={true} muted={!selected}/>
-          <span className="spacer-left">
-            {getSizeRatingLabel(option)}
-          </span>
+      <span>
+        <SizeRating value={getSizeRatingAverageValue(option)} small={true} muted={!selected}/>
+        <span className="spacer-left">
+          {getSizeRatingLabel(option)}
         </span>
+      </span>
     );
   };
 
   renderSort = () => {
     return (
-        <SortingFilter
-          property={this.property}
-          query={this.props.query}
-          isFavorite={this.props.isFavorite}
-          organization={this.props.organization}
-          leftText={translate('biggest')}
-          rightText={translate('smallest')}/>
+      <SortingFilter
+        property={this.property}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}
+        leftText={translate('biggest')}
+        rightText={translate('smallest')}/>
     );
-  }
+  };
 
   getFacetValueForOption = (facet, option) => {
-    const map = ['*-1000.0', '1000.0-10000.0', '10000.0-100000.0', '100000.0-500000.0', '500000.0-*'];
+    const map = [
+      '*-1000.0',
+      '1000.0-10000.0',
+      '10000.0-100000.0',
+      '100000.0-500000.0',
+      '500000.0-*'
+    ];
     return facet[map[option - 1]];
   };
 
+  getOptions = () => [1, 2, 3, 4, 5];
+
+  renderName = () => 'Size';
+
   render () {
     return (
-        <FilterContainer
-            property={this.property}
-            getOptions={() => [1, 2, 3, 4, 5]}
-            renderName={() => 'Size'}
-            renderOption={this.renderOption}
-            renderSort={this.renderSort}
-            getFacetValueForOption={this.getFacetValueForOption}
-            query={this.props.query}
-            isFavorite={this.props.isFavorite}
-            organization={this.props.organization}/>
+      <FilterContainer
+        property={this.property}
+        getOptions={this.getOptions}
+        renderName={this.renderName}
+        renderOption={this.renderOption}
+        renderSort={this.renderSort}
+        getFacetValueForOption={this.getFacetValueForOption}
+        query={this.props.query}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}/>
     );
   }
 }
index 842a3596f4f438bad6b16dcf07df19abe5f5f8d9..7bf9cd829f8b005d037ee5655cce485ecc0a3dda 100644 (file)
@@ -23,7 +23,7 @@ import { Link } from 'react-router';
 import { getFilterUrl } from './utils';
 import { translate } from '../../../helpers/l10n';
 
-export default class SortingFilter extends React.Component {
+export default class SortingFilter extends React.PureComponent {
   static propTypes = {
     property: React.PropTypes.string.isRequired,
     query: React.PropTypes.object.isRequired,
index 504bd99accdc16b85305d3ca82fcacc0403dd063..08335b06d601ed232ad30fd0036b4061f47ae203 100644 (file)
@@ -22,7 +22,7 @@ import React from 'react';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { formatMeasure } from '../../helpers/measures';
 
-export default class ListFooter extends React.Component {
+export default class ListFooter extends React.PureComponent {
   static propTypes = {
     count: React.PropTypes.number.isRequired,
     total: React.PropTypes.number.isRequired,