]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9245 Add all leak facets
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 26 May 2017 14:25:49 +0000 (16:25 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 9 Jun 2017 06:26:48 +0000 (08:26 +0200)
24 files changed:
server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
server/sonar-web/src/main/js/apps/projects/components/PageSidebarOverall.js [deleted file]
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.js.snap
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.js.snap
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/IssuesFilter.js
server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/filters/NewSizeFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js
server/sonar-web/src/main/js/apps/projects/store/actions.js
server/sonar-web/src/main/js/apps/projects/store/facetsDuck.js
server/sonar-web/src/main/js/apps/projects/store/utils.js
server/sonar-web/src/main/less/components/search-navigator.less
server/sonar-web/src/main/less/variables.less
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index b6ebb70b8e6cc8a6698a954387b0fdcd40b95fac..85d9184784d33b9b5393acde98995f4e7c8d5289 100644 (file)
@@ -22,9 +22,20 @@ import React from 'react';
 import { Link } from 'react-router';
 import FavoriteFilterContainer from './FavoriteFilterContainer';
 import LanguagesFilterContainer from '../filters/LanguagesFilterContainer';
-import PageSidebarOverall from './PageSidebarOverall';
+import CoverageFilter from '../filters/CoverageFilter';
+import DuplicationsFilter from '../filters/DuplicationsFilter';
+import MaintainabilityFilter from '../filters/MaintainabilityFilter';
+import NewCoverageFilter from '../filters/NewCoverageFilter';
+import NewDuplicationsFilter from '../filters/NewDuplicationsFilter';
+import NewMaintainabilityFilter from '../filters/NewMaintainabilityFilter';
+import NewReliabilityFilter from '../filters/NewReliabilityFilter';
+import NewSecurityFilter from '../filters/NewSecurityFilter';
+import NewSizeFilter from '../filters/NewSizeFilter';
 import QualityGateFilter from '../filters/QualityGateFilter';
+import ReliabilityFilter from '../filters/ReliabilityFilter';
+import SecurityFilter from '../filters/SecurityFilter';
 import SearchFilterContainer from '../filters/SearchFilterContainer';
+import SizeFilter from '../filters/SizeFilter';
 import TagsFilterContainer from '../filters/TagsFilterContainer';
 import { translate } from '../../../helpers/l10n';
 
@@ -49,6 +60,7 @@ export default function PageSidebar({
   const isLeakView = view === 'leak';
   const basePathName = organization ? `/organizations/${organization.key}/projects` : '/projects';
   const pathname = basePathName + (isFavorite ? '/favorite' : '');
+  const facetProps = { query, isFavorite, organization };
 
   let linkQuery: ?{ view: string, visualization?: string };
   if (view !== 'overall') {
@@ -72,13 +84,27 @@ export default function PageSidebar({
           </div>}
 
         <h3>{translate('filters')}</h3>
-        <SearchFilterContainer query={query} isFavorite={isFavorite} organization={organization} />
+        <SearchFilterContainer {...facetProps} />
       </div>
-      <QualityGateFilter query={query} isFavorite={isFavorite} organization={organization} />
-      {!isLeakView &&
-        <PageSidebarOverall query={query} isFavorite={isFavorite} organization={organization} />}
-      <LanguagesFilterContainer query={query} isFavorite={isFavorite} organization={organization} />
-      <TagsFilterContainer query={query} isFavorite={isFavorite} organization={organization} />
+      <QualityGateFilter {...facetProps} />
+      {!isLeakView && [
+        <ReliabilityFilter key="reliability" {...facetProps} />,
+        <SecurityFilter key="security" {...facetProps} />,
+        <MaintainabilityFilter key="maintainability" {...facetProps} />,
+        <CoverageFilter key="coverage" {...facetProps} />,
+        <DuplicationsFilter key="duplications" {...facetProps} />,
+        <SizeFilter key="size" {...facetProps} />
+      ]}
+      {isLeakView && [
+        <NewReliabilityFilter key="new_reliability" {...facetProps} />,
+        <NewSecurityFilter key="new_security" {...facetProps} />,
+        <NewMaintainabilityFilter key="new_maintainability" {...facetProps} />,
+        <NewCoverageFilter key="new_coverage" {...facetProps} />,
+        <NewDuplicationsFilter key="new_duplications" {...facetProps} />,
+        <NewSizeFilter key="new_size" {...facetProps} />
+      ]}
+      <LanguagesFilterContainer {...facetProps} />
+      <TagsFilterContainer {...facetProps} />
     </div>
   );
 }
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageSidebarOverall.js b/server/sonar-web/src/main/js/apps/projects/components/PageSidebarOverall.js
deleted file mode 100644 (file)
index 67e782a..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * 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 CoverageFilter from '../filters/CoverageFilter';
-import DuplicationsFilter from '../filters/DuplicationsFilter';
-import SizeFilter from '../filters/SizeFilter';
-import ReliabilityFilter from '../filters/ReliabilityFilter';
-import SecurityFilter from '../filters/SecurityFilter';
-import MaintainabilityFilter from '../filters/MaintainabilityFilter';
-
-type Props = {
-  isFavorite: boolean,
-  organization?: { key: string },
-  query: { [string]: string }
-};
-
-export default function PageSidebarOverall({ query, isFavorite, organization }: Props) {
-  return (
-    <div>
-      <ReliabilityFilter query={query} isFavorite={isFavorite} organization={organization} />
-      <SecurityFilter query={query} isFavorite={isFavorite} organization={organization} />
-      <MaintainabilityFilter query={query} isFavorite={isFavorite} organization={organization} />
-      <CoverageFilter query={query} isFavorite={isFavorite} organization={organization} />
-      <DuplicationsFilter query={query} isFavorite={isFavorite} organization={organization} />
-      <SizeFilter query={query} isFavorite={isFavorite} organization={organization} />
-    </div>
-  );
-}
index e55404cfe42682d926bfe5f3222c4896c926e6bc..2b9698e7c191a57101f638252396ffe4a9802862 100644 (file)
@@ -47,7 +47,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
             />
             <Rating value={measures['new_reliability_rating']} />
           </div>
-          <div className="project-card-measure-label">
+          <div className="project-card-measure-label-with-icon">
             <BugIcon className="little-spacer-right vertical-bottom" />
             {translate('metric.new_bugs.name')}
           </div>
@@ -64,7 +64,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
             />
             <Rating value={measures['new_security_rating']} />
           </div>
-          <div className="project-card-measure-label">
+          <div className="project-card-measure-label-with-icon">
             <VulnerabilityIcon className="little-spacer-right vertical-bottom" />
             {translate('metric.new_vulnerabilities.name')}
           </div>
@@ -81,7 +81,7 @@ export default function ProjectCardLeakMeasures({ measures }: Props) {
             />
             <Rating value={measures['new_maintainability_rating']} />
           </div>
-          <div className="project-card-measure-label">
+          <div className="project-card-measure-label-with-icon">
             <CodeSmellIcon className="little-spacer-right vertical-bottom" />
             {translate('metric.new_code_smells.name')}
           </div>
index 219a6650903b74432b672e74f274279f8c921567..7a1c8a31c6c9d0514f225952e14cb513344c17a5 100644 (file)
@@ -109,7 +109,7 @@ export default function ProjectCardOverallMeasures({ measures }: Props) {
 
       {measures['ncloc'] != null &&
         <div className="project-card-measure pull-right" data-key="ncloc">
-          <div className="project-card-measure-inner">
+          <div className="project-card-measure-inner pull-right">
             <div className="project-card-measure-number">
               <span className="spacer-right">
                 <SizeRating value={Number(measures['ncloc'])} />
index 3bfd6952c23afbb62452811a7671664033a3cab7..fdbeb7ed11b9813e1fc3d19cac47ed4a05bf2b70 100644 (file)
@@ -57,6 +57,55 @@ exports[`should render \`leak\` view correctly 1`] = `
       }
     }
   />
+  <NewReliabilityFilter
+    isFavorite={false}
+    query={
+      Object {
+        "view": "leak",
+      }
+    }
+  />
+  <NewSecurityFilter
+    isFavorite={false}
+    query={
+      Object {
+        "view": "leak",
+      }
+    }
+  />
+  <NewMaintainabilityFilter
+    isFavorite={false}
+    query={
+      Object {
+        "view": "leak",
+      }
+    }
+  />
+  <NewCoverageFilter
+    isFavorite={false}
+    query={
+      Object {
+        "view": "leak",
+      }
+    }
+  />
+  <NewDuplicationsFilter
+    isFavorite={false}
+    query={
+      Object {
+        "view": "leak",
+      }
+    }
+  />
+  <NewSizeFilter
+    isFavorite={false}
+    property="new_lines"
+    query={
+      Object {
+        "view": "leak",
+      }
+    }
+  />
   <Connect(withRouter(LanguagesFilter))
     isFavorite={false}
     query={
@@ -119,8 +168,51 @@ exports[`should render correctly 1`] = `
       }
     }
   />
-  <PageSidebarOverall
+  <ReliabilityFilter
+    isFavorite={true}
+    query={
+      Object {
+        "size": "3",
+      }
+    }
+  />
+  <SecurityFilter
+    isFavorite={true}
+    query={
+      Object {
+        "size": "3",
+      }
+    }
+  />
+  <MaintainabilityFilter
+    isFavorite={true}
+    query={
+      Object {
+        "size": "3",
+      }
+    }
+  />
+  <CoverageFilter
+    isFavorite={true}
+    property="coverage"
+    query={
+      Object {
+        "size": "3",
+      }
+    }
+  />
+  <DuplicationsFilter
+    isFavorite={true}
+    property="duplications"
+    query={
+      Object {
+        "size": "3",
+      }
+    }
+  />
+  <SizeFilter
     isFavorite={true}
+    property="size"
     query={
       Object {
         "size": "3",
index 9171745b0da6469b5de9edf11bbd3604dd770ced..964ff49ff6838e0a83888a6769d3a048c677f3ed 100644 (file)
@@ -105,7 +105,7 @@ exports[`should render correctly with all data 1`] = `
         />
       </div>
       <div
-        className="project-card-measure-label"
+        className="project-card-measure-label-with-icon"
       >
         <BugIcon
           className="little-spacer-right vertical-bottom"
@@ -145,7 +145,7 @@ exports[`should render correctly with all data 1`] = `
         />
       </div>
       <div
-        className="project-card-measure-label"
+        className="project-card-measure-label-with-icon"
       >
         <VulnerabilityIcon
           className="little-spacer-right vertical-bottom"
@@ -185,7 +185,7 @@ exports[`should render correctly with all data 1`] = `
         />
       </div>
       <div
-        className="project-card-measure-label"
+        className="project-card-measure-label-with-icon"
       >
         <CodeSmellIcon
           className="little-spacer-right vertical-bottom"
index b5431a1454a1abb2e12fc2c4ee509c55f53ee87d..797b56431adec1ba4dcdba2cb8ad8a027fb6ad18 100644 (file)
@@ -226,7 +226,7 @@ exports[`should render correctly with all data 1`] = `
     data-key="ncloc"
   >
     <div
-      className="project-card-measure-inner"
+      className="project-card-measure-inner pull-right"
     >
       <div
         className="project-card-measure-number"
@@ -270,7 +270,7 @@ exports[`should render ncloc correctly 1`] = `
   data-key="ncloc"
 >
   <div
-    className="project-card-measure-inner"
+    className="project-card-measure-inner pull-right"
   >
     <div
       className="project-card-measure-number"
index c29c7f26ec6e0f2b77fb7f6328d07f3ca19816cf..827e5e482f8606c3c9b1cfd9d8670e2b179e13c1 100644 (file)
@@ -27,12 +27,16 @@ import { translate } from '../../../helpers/l10n';
 
 export default class CoverageFilter extends React.PureComponent {
   static propTypes = {
+    className: React.PropTypes.string,
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool,
-    organization: React.PropTypes.object
+    organization: React.PropTypes.object,
+    property: React.PropTypes.string
   };
 
-  property = 'coverage';
+  static defaultProps = {
+    property: 'coverage'
+  };
 
   getFacetValueForOption(facet, option) {
     const map = ['80.0-*', '70.0-80.0', '50.0-70.0', '30.0-50.0', '*-30.0'];
@@ -57,7 +61,8 @@ export default class CoverageFilter extends React.PureComponent {
   render() {
     return (
       <FilterContainer
-        property={this.property}
+        property={this.props.property}
+        className={this.props.className}
         options={[1, 2, 3, 4, 5]}
         query={this.props.query}
         renderOption={this.renderOption}
@@ -68,7 +73,7 @@ export default class CoverageFilter extends React.PureComponent {
         header={
           <FilterHeader name={translate('metric_domain.Coverage')}>
             <SortingFilter
-              property={this.property}
+              property={this.props.property}
               query={this.props.query}
               isFavorite={this.props.isFavorite}
               organization={this.props.organization}
index e6f2304bc2fe4604696dbb61215474a3b9b40613..dcda2e9bccf060cad5700ab5adc804e3c5399dae 100644 (file)
@@ -30,12 +30,16 @@ import { translate } from '../../../helpers/l10n';
 
 export default class DuplicationsFilter extends React.PureComponent {
   static propTypes = {
+    className: React.PropTypes.string,
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool,
-    organization: React.PropTypes.object
+    organization: React.PropTypes.object,
+    property: React.PropTypes.string
   };
 
-  property = 'duplications';
+  static defaultProps = {
+    property: 'duplications'
+  };
 
   getFacetValueForOption(facet, option) {
     const map = ['*-3.0', '3.0-5.0', '5.0-10.0', '10.0-20.0', '20.0-*'];
@@ -60,7 +64,8 @@ export default class DuplicationsFilter extends React.PureComponent {
   render() {
     return (
       <FilterContainer
-        property={this.property}
+        property={this.props.property}
+        className={this.props.className}
         options={[1, 2, 3, 4, 5]}
         query={this.props.query}
         renderOption={this.renderOption}
@@ -71,7 +76,7 @@ export default class DuplicationsFilter extends React.PureComponent {
         header={
           <FilterHeader name={translate('metric_domain.Duplications')}>
             <SortingFilter
-              property={this.property}
+              property={this.props.property}
               query={this.props.query}
               isFavorite={this.props.isFavorite}
               organization={this.props.organization}
index 1f8677e3ee7f42fed9a00601f23cc5641d9ed438..68073fa082326e5450eb0e28d1ba23b3a701aa6f 100644 (file)
@@ -27,6 +27,7 @@ import { translate } from '../../../helpers/l10n';
 export default class Filter extends React.PureComponent {
   static propTypes = {
     property: React.PropTypes.string.isRequired,
+    className: React.PropTypes.string,
     options: React.PropTypes.array.isRequired,
     query: React.PropTypes.object.isRequired,
     renderOption: React.PropTypes.func.isRequired,
@@ -146,7 +147,9 @@ export default class Filter extends React.PureComponent {
 
   render() {
     return (
-      <div className="search-navigator-facet-box" data-key={this.props.property}>
+      <div
+        className={classNames('search-navigator-facet-box', this.props.className)}
+        data-key={this.props.property}>
         {this.props.header}
         {this.renderOptions()}
         {this.props.footer}
index a287639d59cd1809d1b6a26ce972c4f9f340c170..52e52bc92a24bc3bf840445475f0d57d4ece374e 100644 (file)
 import React from 'react';
 import FilterContainer from './FilterContainer';
 import FilterHeader from './FilterHeader';
-import SortingFilter from './SortingFilter';
 import Rating from '../../../components/ui/Rating';
 import { translate } from '../../../helpers/l10n';
 
 export default class IssuesFilter extends React.PureComponent {
   static propTypes = {
-    property: React.PropTypes.string.isRequired,
-    name: React.PropTypes.string.isRequired,
-    query: React.PropTypes.object.isRequired,
+    className: React.PropTypes.string,
+    headerDetail: React.PropTypes.element,
     isFavorite: React.PropTypes.bool,
-    organization: React.PropTypes.object
+    organization: React.PropTypes.object,
+    name: React.PropTypes.string.isRequired,
+    property: React.PropTypes.string.isRequired,
+    query: React.PropTypes.object.isRequired
   };
 
   getFacetValueForOption(facet, option) {
@@ -52,6 +53,7 @@ export default class IssuesFilter extends React.PureComponent {
     return (
       <FilterContainer
         property={this.props.property}
+        className={this.props.className}
         options={[1, 2, 3, 4, 5]}
         query={this.props.query}
         renderOption={this.renderOption}
@@ -61,12 +63,7 @@ export default class IssuesFilter extends React.PureComponent {
         highlightUnder={1}
         header={
           <FilterHeader name={translate('metric_domain', this.props.name)}>
-            <SortingFilter
-              property={this.props.property}
-              query={this.props.query}
-              isFavorite={this.props.isFavorite}
-              organization={this.props.organization}
-            />
+            {this.props.headerDetail}
           </FilterHeader>
         }
       />
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.js
new file mode 100644 (file)
index 0000000..24f24ce
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import CoverageFilter from './CoverageFilter';
+
+export default function NewCoverageFilter(props) {
+  return <CoverageFilter {...props} property="new_coverage" className="leak-facet-box" />;
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.js
new file mode 100644 (file)
index 0000000..98754c9
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import DuplicationsFilter from './DuplicationsFilter';
+
+export default function NewDuplicationsFilter(props) {
+  return <DuplicationsFilter {...props} property="new_duplications" className="leak-facet-box" />;
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.js
new file mode 100644 (file)
index 0000000..b501476
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
+import IssuesFilter from './IssuesFilter';
+import { translate } from '../../../helpers/l10n';
+
+export default function NewMaintainabilityFilter(props) {
+  return (
+    <IssuesFilter
+      {...props}
+      className="leak-facet-box"
+      headerDetail={
+        <span className="note little-spacer-left">
+          {'('}
+          <CodeSmellIcon className="little-spacer-right" />
+          {translate('metric.code_smells.name')}
+          {' )'}
+        </span>
+      }
+      name="Maintainability"
+      property="new_maintainability"
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.js
new file mode 100644 (file)
index 0000000..0dbb948
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import BugIcon from '../../../components/icons-components/BugIcon';
+import IssuesFilter from './IssuesFilter';
+import { translate } from '../../../helpers/l10n';
+
+export default function NewReliabilityFilter(props) {
+  return (
+    <IssuesFilter
+      {...props}
+      className="leak-facet-box"
+      headerDetail={
+        <span className="note little-spacer-left">
+          {'('}<BugIcon className="little-spacer-right" />{translate('metric.bugs.name')}{' )'}
+        </span>
+      }
+      name="Reliability"
+      property="new_reliability"
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.js
new file mode 100644 (file)
index 0000000..7d2e64e
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon';
+import IssuesFilter from './IssuesFilter';
+import { translate } from '../../../helpers/l10n';
+
+export default function NewSecurityFilter(props) {
+  return (
+    <IssuesFilter
+      {...props}
+      className="leak-facet-box"
+      headerDetail={
+        <span className="note little-spacer-left">
+          {'('}
+          <VulnerabilityIcon className="little-spacer-right" />
+          {translate('metric.vulnerabilities.name')}
+          {' )'}
+        </span>
+      }
+      name="Security"
+      property="new_security"
+    />
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewSizeFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewSizeFilter.js
new file mode 100644 (file)
index 0000000..c672d6c
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import FilterContainer from './FilterContainer';
+import FilterHeader from './FilterHeader';
+import SortingFilter from './SortingFilter';
+import { translate } from '../../../helpers/l10n';
+import { getSizeRatingLabel } from '../../../helpers/ratings';
+
+export default class NewSizeFilter extends React.PureComponent {
+  static propTypes = {
+    className: React.PropTypes.string,
+    query: React.PropTypes.object.isRequired,
+    isFavorite: React.PropTypes.bool,
+    organization: React.PropTypes.object,
+    property: React.PropTypes.string
+  };
+
+  static defaultProps = {
+    property: 'new_lines'
+  };
+
+  getFacetValueForOption(facet, option) {
+    const map = [
+      '*-1000.0',
+      '1000.0-10000.0',
+      '10000.0-100000.0',
+      '100000.0-500000.0',
+      '500000.0-*'
+    ];
+    return facet[map[option - 1]];
+  }
+
+  renderOption(option) {
+    return (
+      <span>
+        {getSizeRatingLabel(option)}
+      </span>
+    );
+  }
+
+  render() {
+    return (
+      <FilterContainer
+        property={this.props.property}
+        className="leak-facet-box"
+        options={[1, 2, 3, 4, 5]}
+        query={this.props.query}
+        renderOption={this.renderOption}
+        isFavorite={this.props.isFavorite}
+        organization={this.props.organization}
+        getFacetValueForOption={this.getFacetValueForOption}
+        highlightUnder={1}
+        header={
+          <FilterHeader name={translate('metric_domain.new_size')}>
+            <SortingFilter
+              property={this.props.property}
+              query={this.props.query}
+              isFavorite={this.props.isFavorite}
+              organization={this.props.organization}
+              leftText={translate('biggest')}
+              rightText={translate('smallest')}
+            />
+          </FilterHeader>
+        }
+      />
+    );
+  }
+}
index c9cb96b15bc168390966edc1e925fec676638870..aedd2d966cd9068736f5ba6d655034d99223982d 100644 (file)
@@ -27,12 +27,16 @@ import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/
 
 export default class SizeFilter extends React.PureComponent {
   static propTypes = {
+    className: React.PropTypes.string,
     query: React.PropTypes.object.isRequired,
     isFavorite: React.PropTypes.bool,
-    organization: React.PropTypes.object
+    organization: React.PropTypes.object,
+    property: React.PropTypes.string
   };
 
-  property = 'size';
+  static defaultProps = {
+    property: 'size'
+  };
 
   getFacetValueForOption(facet, option) {
     const map = [
@@ -56,23 +60,11 @@ export default class SizeFilter extends React.PureComponent {
     );
   }
 
-  renderSort = () => {
-    return (
-      <SortingFilter
-        property={this.property}
-        query={this.props.query}
-        isFavorite={this.props.isFavorite}
-        organization={this.props.organization}
-        leftText={translate('biggest')}
-        rightText={translate('smallest')}
-      />
-    );
-  };
-
   render() {
     return (
       <FilterContainer
-        property={this.property}
+        property={this.props.property}
+        className={this.props.className}
         options={[1, 2, 3, 4, 5]}
         query={this.props.query}
         renderOption={this.renderOption}
@@ -83,7 +75,7 @@ export default class SizeFilter extends React.PureComponent {
         header={
           <FilterHeader name={translate('metric_domain.Size')}>
             <SortingFilter
-              property={this.property}
+              property={this.props.property}
               query={this.props.query}
               isFavorite={this.props.isFavorite}
               organization={this.props.organization}
index 38a2d21cacaf8ca5713836cffcb707c644eaa9be..46dc1f670a12f1aaba5c774202ad22d45095dc23 100644 (file)
@@ -82,6 +82,18 @@ const FACETS = [
   'tags'
 ];
 
+const LEAK_FACETS = [
+  'new_reliability_rating',
+  'new_security_rating',
+  'new_maintainability_rating',
+  'new_coverage',
+  'new_duplicated_lines_density',
+  'new_lines',
+  'alert_status',
+  'languages',
+  'tags'
+];
+
 const onFail = dispatch => error => {
   parseError(error).then(message => dispatch(addGlobalErrorMessage(message)));
   dispatch(updateState({ loading: false }));
@@ -124,6 +136,13 @@ const defineMetrics = query => {
   }
 };
 
+const defineFacets = query => {
+  if (query.view === 'leak') {
+    return LEAK_FACETS;
+  }
+  return FACETS;
+};
+
 const fetchProjectMeasures = (projects, query) => dispatch => {
   if (!projects.length) {
     return Promise.resolve();
@@ -193,7 +212,7 @@ export const fetchProjects = (query, isFavorite, organization) => dispatch => {
   const ps = query.view === 'visualizations' ? PAGE_SIZE_VISUALIZATIONS : PAGE_SIZE;
   const data = convertToQueryData(query, isFavorite, organization, {
     ps,
-    facets: FACETS.join(),
+    facets: defineFacets(query).join(),
     f: 'analysisDate,leakPeriodDate'
   });
   return searchProjects(data).then(onReceiveProjects(dispatch, query), onFail(dispatch));
index 953fcaf410148a59544ca2339029d0f90e079606..f47b8d674710005d2d79e7ef7eab273f0e9c9dc1 100644 (file)
@@ -24,14 +24,20 @@ import { mapMetricToProperty } from './utils';
 
 const CUMULATIVE_FACETS = [
   'reliability',
+  'new_reliability',
   'security',
+  'new_security',
   'maintainability',
+  'new_maintainability',
   'coverage',
+  'new_coverage',
   'duplications',
-  'size'
+  'new_duplications',
+  'size',
+  'new_lines'
 ];
 
-const REVERSED_FACETS = ['coverage'];
+const REVERSED_FACETS = ['coverage', 'new_coverage'];
 
 const mapFacetValues = values => {
   const map = {};
index 0316f71ab7fa1ba726b6e4932065c21a86229457..13db6e4c7cb1a378cce37d6cbef5aeea2bb260a0 100644 (file)
@@ -57,11 +57,17 @@ const getVisualization = value => {
 export const parseUrlQuery = urlQuery => ({
   gate: getAsLevel(urlQuery['gate']),
   reliability: getAsNumericRating(urlQuery['reliability']),
+  new_reliability: getAsNumericRating(urlQuery['new_reliability']),
   security: getAsNumericRating(urlQuery['security']),
+  new_security: getAsNumericRating(urlQuery['new_security']),
   maintainability: getAsNumericRating(urlQuery['maintainability']),
+  new_maintainability: getAsNumericRating(urlQuery['new_maintainability']),
   coverage: getAsNumericRating(urlQuery['coverage']),
+  new_coverage: getAsNumericRating(urlQuery['new_coverage']),
   duplications: getAsNumericRating(urlQuery['duplications']),
+  new_duplications: getAsNumericRating(urlQuery['new_duplications']),
   size: getAsNumericRating(urlQuery['size']),
+  new_lines: getAsNumericRating(urlQuery['new_lines']),
   languages: getAsArray(urlQuery['languages'], getAsString),
   tags: getAsArray(urlQuery['tags'], getAsString),
   search: getAsString(urlQuery['search']),
@@ -73,11 +79,17 @@ export const parseUrlQuery = urlQuery => ({
 export const mapMetricToProperty = metricKey => {
   const map = {
     reliability_rating: 'reliability',
+    new_reliability_rating: 'new_reliability',
     security_rating: 'security',
+    new_security_rating: 'new_security',
     sqale_rating: 'maintainability',
+    new_maintainability_rating: 'new_maintainability',
     coverage: 'coverage',
+    new_coverage: 'new_coverage',
     duplicated_lines_density: 'duplications',
+    new_duplicated_lines_density: 'new_duplications',
     ncloc: 'size',
+    new_lines: 'new_lines',
     alert_status: 'gate',
     languages: 'languages',
     tags: 'tags',
@@ -89,11 +101,17 @@ export const mapMetricToProperty = metricKey => {
 export const mapPropertyToMetric = property => {
   const map = {
     reliability: 'reliability_rating',
+    new_reliability: 'new_reliability_rating',
     security: 'security_rating',
+    new_security: 'new_security_rating',
     maintainability: 'sqale_rating',
+    new_maintainability: 'new_maintainability_rating',
     coverage: 'coverage',
+    new_coverage: 'new_coverage',
     duplications: 'duplicated_lines_density',
+    new_duplications: 'new_duplicated_lines_density',
     size: 'ncloc',
+    new_lines: 'new_lines',
     gate: 'alert_status',
     languages: 'languages',
     tags: 'tags',
@@ -110,57 +128,70 @@ const convertIssuesRating = (metric, rating) => {
   }
 };
 
-const convertCoverage = coverage => {
+const convertCoverage = (metric, coverage) => {
   switch (coverage) {
     case 1:
-      return mapPropertyToMetric('coverage') + ' >= 80';
+      return metric + ' >= 80';
     case 2:
-      return mapPropertyToMetric('coverage') + ' < 80';
+      return metric + ' < 80';
     case 3:
-      return mapPropertyToMetric('coverage') + ' < 70';
+      return metric + ' < 70';
     case 4:
-      return mapPropertyToMetric('coverage') + ' < 50';
+      return metric + ' < 50';
     case 5:
-      return mapPropertyToMetric('coverage') + ' < 30';
+      return metric + ' < 30';
     default:
       return '';
   }
 };
 
-const convertDuplications = duplications => {
+const convertDuplications = (metric, duplications) => {
   switch (duplications) {
     case 1:
-      return mapPropertyToMetric('duplications') + ' < 3';
+      return metric + ' < 3';
     case 2:
-      return mapPropertyToMetric('duplications') + ' >= 3';
+      return metric + ' >= 3';
     case 3:
-      return mapPropertyToMetric('duplications') + ' >= 5';
+      return metric + ' >= 5';
     case 4:
-      return mapPropertyToMetric('duplications') + ' >= 10';
+      return metric + ' >= 10';
     case 5:
-      return mapPropertyToMetric('duplications') + ' >= 20';
+      return metric + ' >= 20';
     default:
       return '';
   }
 };
 
-const convertSize = size => {
+const convertSize = (metric, size) => {
   switch (size) {
     case 1:
-      return mapPropertyToMetric('size') + ' < 1000';
+      return metric + ' < 1000';
     case 2:
-      return mapPropertyToMetric('size') + ' >= 1000';
+      return metric + ' >= 1000';
     case 3:
-      return mapPropertyToMetric('size') + ' >= 10000';
+      return metric + ' >= 10000';
     case 4:
-      return mapPropertyToMetric('size') + ' >= 100000';
+      return metric + ' >= 100000';
     case 5:
-      return mapPropertyToMetric('size') + ' >= 500000';
+      return metric + ' >= 500000';
     default:
       return '';
   }
 };
 
+const convertArrayMetric = (metric, items) => {
+  if (!Array.isArray(items) || items.length < 2) {
+    return metric + ' = ' + items;
+  }
+  return `${metric} IN (${items.join(', ')})`;
+};
+
+const pushMetricToArray = (query, property, conditionsArray, convertFunction) => {
+  if (query[property] != null) {
+    conditionsArray.push(convertFunction(mapPropertyToMetric(property), query[property]));
+  }
+};
+
 export const convertToFilter = (query, isFavorite) => {
   const conditions = [];
 
@@ -172,34 +203,30 @@ export const convertToFilter = (query, isFavorite) => {
     conditions.push(mapPropertyToMetric('gate') + ' = ' + query['gate']);
   }
 
-  if (query['coverage'] != null) {
-    conditions.push(convertCoverage(query['coverage']));
-  }
+  ['coverage', 'new_coverage'].forEach(property =>
+    pushMetricToArray(query, property, conditions, convertCoverage)
+  );
 
-  if (query['duplications'] != null) {
-    conditions.push(convertDuplications(query['duplications']));
-  }
+  ['duplications', 'new_duplications'].forEach(property =>
+    pushMetricToArray(query, property, conditions, convertDuplications)
+  );
 
-  if (query['size'] != null) {
-    conditions.push(convertSize(query['size']));
-  }
+  ['size', 'new_lines'].forEach(property =>
+    pushMetricToArray(query, property, conditions, convertSize)
+  );
 
-  ['reliability', 'security', 'maintainability'].forEach(property => {
-    if (query[property] != null) {
-      conditions.push(convertIssuesRating(mapPropertyToMetric(property), query[property]));
-    }
-  });
+  [
+    'reliability',
+    'security',
+    'maintainability',
+    'new_reliability',
+    'new_security',
+    'new_maintainability'
+  ].forEach(property => pushMetricToArray(query, property, conditions, convertIssuesRating));
 
-  ['languages', 'tags'].forEach(property => {
-    const items = query[property];
-    if (items != null) {
-      if (!Array.isArray(items) || items.length < 2) {
-        conditions.push(mapPropertyToMetric(property) + ' = ' + items);
-      } else {
-        conditions.push(`${mapPropertyToMetric(property)} IN (${items.join(', ')})`);
-      }
-    }
-  });
+  ['languages', 'tags'].forEach(property =>
+    pushMetricToArray(query, property, conditions, convertArrayMetric)
+  );
 
   if (query['search'] != null) {
     conditions.push(`${mapPropertyToMetric('search')} = "${query['search']}"`);
index e01e2dc742ad599c0fc261d463a104bffa0925b2..23c8a7b3ff4be8d455a272c506afa27c4360899f 100644 (file)
 .search-navigator-facet-box {
   background-color: @barBackgroundColor;
   font-size: @baseFontSize;
+
+  &.leak-facet-box {
+    background-color: @leakBackgroundColor;
+    border: 1px solid @leakBorderColor;
+  }
 }
 
-.search-navigator-facet-box:not(.hidden) + .search-navigator-facet-box {
+.search-navigator-facet-box:not(.hidden):not(.leak-facet-box) + .search-navigator-facet-box:not(.leak-facet-box) {
   border-top: 1px solid @barBorderColor;
 }
 
+.leak-facet-box:not(.hidden) + .leak-facet-box {
+  border-top: none;
+}
+
 .search-navigator-facet-box-collapsed {
   background-color: transparent;
 
     background-color: @red;
     color: #fff;
   }
+}
 
-  &.active {
-    border: 1px solid @blue;
-    padding: 3px 5px;
-    background-color: @lightBlue;
-    text-decoration: none;
+.leak-facet-box .search-navigator-facet {
+  .facet-name {
+    background-color: @leakBackgroundColor;
+  }
 
-    .facet-name {
-      background-color: @lightBlue;
+  .facet-stat {
+    background-color: @leakBackgroundColor;
+
+    &:before {
+      background-image: linear-gradient(to right, fade(@leakBackgroundColor, 0%), @leakBackgroundColor 75%);
     }
+  }
+}
 
-    .facet-stat {
-      border-color: @blue;
-      background-color: @lightBlue;
-      top: -1px;
-      right: -1px;
+.search-navigator-facet.active {
+  border: 1px solid @blue;
+  padding: 3px 5px;
+  background-color: @lightBlue;
+  text-decoration: none;
 
-      &:before {
-        background-image: linear-gradient(to right, fade(@lightBlue, 0%), @lightBlue 75%);
-      }
-    }
+  .facet-name {
+    background-color: @lightBlue;
+  }
 
-    .facet-toggle {
-      display: inline;
+  .facet-stat {
+    border-color: @blue;
+    background-color: @lightBlue;
+    top: -1px;
+    right: -1px;
+
+    &:before {
+      background-image: linear-gradient(to right, fade(@lightBlue, 0%), @lightBlue 75%);
     }
   }
+
+  .facet-toggle {
+    display: inline;
+  }
 }
 
 .search-navigator-facet-indent {
   border-bottom: none;
   color: @baseFontColor;
   font-weight: 600;
+
+  & > .note {
+    font-weight: 400;
+  }
 }
 
 .search-navigator-facet-header-button {
index 7f0a91caf8e3d2c4ef7ce0fd9651379c4a705bc3..ca3a1a87f0dbe6fecaedac6fc23d85efe9cdd869 100644 (file)
@@ -89,7 +89,8 @@
 @issueBackgroundColor: #ffeaea;
 @issueBorderColor: desaturate(darken(@issueBackgroundColor, 40%), 30%);
 
-
+@leakBackgroundColor: #fbf3d5;
+@leakBorderColor: #eae3c7;
 
 /*
  * Sizes
 
 
 
-
 /*
  * Page
  */
index 047896dc5dd6c1c81b38711ff48ec043429a030d..3099a7c1931bbdd260f270fba26804e2fe87d2c5 100644 (file)
@@ -1939,6 +1939,7 @@ severity.INFO.description=Neither a bug nor a quality flaw. Just a finding.
 #------------------------------------------------------------------------------
 
 metric_domain.Size=Size
+metric_domain.new_size=New Lines
 metric_domain.Tests=Tests
 metric_domain.Integration Tests=Integration Tests
 metric_domain.Complexity=Complexity