]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9245 Create the leak view on projects page
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 22 May 2017 15:14:37 +0000 (17:14 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 9 Jun 2017 06:26:48 +0000 (08:26 +0200)
15 files changed:
server/sonar-web/src/main/js/apps/component-measures/components/Measure.js
server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCard.js
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js [deleted file]
server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js
server/sonar-web/src/main/js/apps/projects/store/actions.js
server/sonar-web/src/main/js/apps/projects/store/utils.js
server/sonar-web/src/main/js/apps/projects/styles.css
server/sonar-web/src/main/js/apps/projects/utils.js
server/sonar-web/src/main/js/components/ui/BugIcon.js
server/sonar-web/src/main/js/components/ui/CodeSmellIcon.js
server/sonar-web/src/main/js/components/ui/VulnerabilityIcon.js
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 520852b4367fae2ec4afcaabfe926c55990b1fbd..3b3672ed4f15e1c7a4aa92f5cb82825a340ee7d0 100644 (file)
@@ -26,6 +26,7 @@ import { formatLeak, isDiffMetric, getRatingTooltip } from '../utils';
 
 export default class Measure extends React.PureComponent {
   static propTypes = {
+    className: React.PropTypes.string,
     measure: React.PropTypes.object,
     metric: React.PropTypes.object,
     decimals: React.PropTypes.number
@@ -52,7 +53,7 @@ export default class Measure extends React.PureComponent {
   }
 
   render() {
-    const { measure, metric, decimals } = this.props;
+    const { measure, metric, decimals, className } = this.props;
     const finalMetric = metric || measure.metric;
 
     if (finalMetric.type === 'RATING') {
@@ -66,9 +67,8 @@ export default class Measure extends React.PureComponent {
     const formattedValue = isDiffMetric(finalMetric)
       ? formatLeak(measure.leak, finalMetric, { decimals })
       : formatMeasure(measure.value, finalMetric.type, { decimals });
-
     return (
-      <span>
+      <span className={className}>
         {formattedValue != null ? formattedValue : '–'}
       </span>
     );
index 55e16b34019cc1b2f0b8bf6536fe2e0d57a7f79f..2dcc57205628ee0bdd4daf23b8914056837d016e 100644 (file)
@@ -131,13 +131,14 @@ export default class AllProjects extends React.PureComponent {
           <div className="layout-page-main">
             <div className="layout-page-main-inner">
               <PageHeaderContainer onOpenOptionBar={this.openOptionBar} />
-              {view === 'overall' &&
+              {view !== 'visualizations' &&
                 <ProjectsListContainer
                   isFavorite={this.props.isFavorite}
                   isFiltered={isFiltered}
                   organization={this.props.organization}
+                  cardType={view}
                 />}
-              {view === 'overall' &&
+              {view !== 'visualizations' &&
                 <ProjectsListFooterContainer
                   query={query}
                   isFavorite={this.props.isFavorite}
index 40a74de7747e4a2955ccdd045740b5da043b498c..f08708da24dc43390e0fa572cb12413665826d86 100644 (file)
@@ -23,90 +23,94 @@ import classNames from 'classnames';
 import moment from 'moment';
 import { Link } from 'react-router';
 import ProjectCardQualityGate from './ProjectCardQualityGate';
-import ProjectCardMeasures from './ProjectCardMeasures';
+import ProjectCardLeakMeasures from './ProjectCardLeakMeasures';
+import ProjectCardOverallMeasures from './ProjectCardOverallMeasures';
 import FavoriteContainer from '../../../components/controls/FavoriteContainer';
 import Organization from '../../../components/shared/Organization';
 import TagsList from '../../../components/tags/TagsList';
 import PrivateBadge from '../../../components/common/PrivateBadge';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 
-export default class ProjectCard extends React.PureComponent {
-  props: {
-    measures: { [string]: string },
-    organization?: {},
-    project?: {
-      analysisDate?: string,
-      key: string,
-      name: string,
-      tags: Array<string>,
-      isFavorite?: boolean,
-      organization?: string
-    }
-  };
+type Props = {
+  measures: { [string]: string },
+  organization?: { key: string },
+  project?: {
+    analysisDate?: string,
+    key: string,
+    name: string,
+    tags: Array<string>,
+    isFavorite?: boolean,
+    organization?: string
+  },
+  type?: string
+};
 
-  render() {
-    const { project } = this.props;
+export default function ProjectCard({ measures, organization, project, type }: Props) {
+  if (project == null) {
+    return null;
+  }
 
-    if (project == null) {
-      return null;
-    }
+  const isProjectAnalyzed = project.analysisDate != null;
+  const isLeakView = type === 'leak';
 
-    const isProjectAnalyzed = project.analysisDate != null;
-    // check reliability_rating because only some measures can be loaded
-    // if coming from visualizations tab
-    const areProjectMeasuresLoaded =
-      !isProjectAnalyzed ||
-      (this.props.measures != null &&
-        this.props.measures['reliability_rating'] != null &&
-        this.props.measures['sqale_rating'] != null);
-    const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed;
+  let areProjectMeasuresLoaded;
+  // check for particular measures because only some measures can be loaded
+  // if coming from visualizations tab
+  if (isLeakView) {
+    areProjectMeasuresLoaded = measures != null && measures['new_bugs'];
+  } else {
+    areProjectMeasuresLoaded =
+      measures != null &&
+      measures['reliability_rating'] != null &&
+      measures['sqale_rating'] != null;
+  }
 
-    const className = classNames('boxed-group', 'project-card', {
-      'boxed-group-loading': !areProjectMeasuresLoaded
-    });
+  const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed;
+  const className = classNames('boxed-group', 'project-card', {
+    'boxed-group-loading': isProjectAnalyzed && !areProjectMeasuresLoaded
+  });
 
-    return (
-      <div data-key={project.key} className={className}>
-        {displayQualityGate &&
-          <div className="boxed-group-actions">
-            <ProjectCardQualityGate status={this.props.measures['alert_status']} />
-          </div>}
+  return (
+    <div data-key={project.key} className={className}>
+      {displayQualityGate &&
+        <div className="boxed-group-actions">
+          <ProjectCardQualityGate status={measures['alert_status']} />
+        </div>}
 
-        <div className="boxed-group-header">
-          {project.isFavorite != null &&
-            <FavoriteContainer className="spacer-right" componentKey={project.key} />}
-          <h2 className="project-card-name">
-            {this.props.organization == null &&
-              project.organization != null &&
-              <span className="text-normal">
-                <Organization organizationKey={project.organization} />
-              </span>}
-            <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>
-              {project.name}
-            </Link>
-          </h2>
-          {project.visibility === 'private' && <PrivateBadge className="spacer-left" />}
-          {project.tags.length > 0 && <TagsList tags={project.tags} customClass="spacer-left" />}
-        </div>
+      <div className="boxed-group-header">
+        {project.isFavorite != null &&
+          <FavoriteContainer className="spacer-right" componentKey={project.key} />}
+        <h2 className="project-card-name">
+          {organization == null &&
+            project.organization != null &&
+            <span className="text-normal">
+              <Organization organizationKey={project.organization} />
+            </span>}
+          <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
+        </h2>
+        {project.visibility === 'private' && <PrivateBadge className="spacer-left" />}
+        {project.tags.length > 0 && <TagsList tags={project.tags} customClass="spacer-left" />}
+      </div>
 
-        {isProjectAnalyzed
-          ? <div className="boxed-group-inner">
-              {areProjectMeasuresLoaded && <ProjectCardMeasures measures={this.props.measures} />}
+      {isProjectAnalyzed
+        ? <div className="boxed-group-inner">
+            {areProjectMeasuresLoaded && isLeakView
+              ? <ProjectCardLeakMeasures measures={measures} />
+              : <ProjectCardOverallMeasures measures={measures} />}
+          </div>
+        : <div className="boxed-group-inner">
+            <div className="note project-card-not-analyzed">
+              {translate('projects.not_analyzed')}
             </div>
-          : <div className="boxed-group-inner">
-              <div className="note project-card-not-analyzed">
-                {translate('projects.not_analyzed')}
-              </div>
-            </div>}
-
-        {isProjectAnalyzed &&
-          <div className="project-card-analysis-date note">
-            {translateWithParameters(
-              'overview.last_analysis_on_x',
-              moment(project.analysisDate).format('LLL')
-            )}
           </div>}
-      </div>
-    );
-  }
+
+      {isProjectAnalyzed &&
+        <div className="project-card-analysis-date note">
+          {translateWithParameters(
+            'overview.last_analysis_on_x',
+            moment(project.analysisDate).format('LLL')
+          )}
+        </div>}
+    </div>
+  );
 }
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js
new file mode 100644 (file)
index 0000000..b149044
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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 Measure from '../../component-measures/components/Measure';
+import BugIcon from '../../../components/ui/BugIcon';
+import CodeSmellIcon from '../../../components/ui/CodeSmellIcon';
+import Rating from '../../../components/ui/Rating';
+import VulnerabilityIcon from '../../../components/ui/VulnerabilityIcon';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {
+  measures?: { [string]: string }
+};
+
+export default function ProjectCardLeakMeasures({ measures }: Props) {
+  if (measures == null) {
+    return null;
+  }
+
+  return (
+    <div className="project-card-leak-measures">
+      <div className="project-card-measure smaller-card" data-key="new_reliability_rating">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            <Measure
+              className="spacer-right"
+              measure={{ leak: measures['new_bugs'] }}
+              metric={{ key: 'new_bugs', type: 'SHORT_INT' }}
+            />
+            <Rating value={measures['new_reliability_rating']} />
+          </div>
+          <div className="project-card-measure-label">
+            <BugIcon className="little-spacer-right vertical-bottom" />
+            {translate('metric.new_bugs.name')}
+          </div>
+        </div>
+      </div>
+
+      <div className="project-card-measure" data-key="new_security_rating">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            <Measure
+              className="spacer-right"
+              measure={{ leak: measures['new_vulnerabilities'] }}
+              metric={{ key: 'new_vulnerabilities', type: 'SHORT_INT' }}
+            />
+            <Rating value={measures['new_security_rating']} />
+          </div>
+          <div className="project-card-measure-label">
+            <VulnerabilityIcon className="little-spacer-right vertical-bottom" />
+            {translate('metric.new_vulnerabilities.name')}
+          </div>
+        </div>
+      </div>
+
+      <div className="project-card-measure" data-key="new_maintainability_rating">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            <Measure
+              className="spacer-right"
+              measure={{ leak: measures['new_code_smells'] }}
+              metric={{ key: 'new_code_smells', type: 'SHORT_INT' }}
+            />
+            <Rating value={measures['new_maintainability_rating']} />
+          </div>
+          <div className="project-card-measure-label">
+            <CodeSmellIcon className="little-spacer-right vertical-bottom" />
+            {translate('metric.new_code_smells.name')}
+          </div>
+        </div>
+      </div>
+
+      <div className="project-card-measure" data-key="new_coverage">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            <Measure
+              measure={{ leak: measures['new_coverage'] }}
+              metric={{ key: 'new_coverage', type: 'PERCENT' }}
+            />
+          </div>
+          <div className="project-card-measure-label">
+            {translate('metric.new_coverage.name')}
+          </div>
+        </div>
+      </div>
+
+      <div className="project-card-measure" data-key="new_duplicated_lines_density">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            <Measure
+              measure={{ leak: measures['new_duplicated_lines_density'] }}
+              metric={{ key: 'new_duplicated_lines_density', type: 'PERCENT' }}
+            />
+          </div>
+          <div className="project-card-measure-label">
+            {translate('metric.new_duplicated_lines_density.short_name')}
+          </div>
+        </div>
+      </div>
+
+      {measures['new_lines'] != null &&
+        <div className="project-card-measure smaller-card" data-key="new_lines">
+          <div className="project-card-measure-inner">
+            <div className="project-card-measure-number">
+              <Measure
+                measure={{ leak: measures['new_lines'] }}
+                metric={{ key: 'new_lines', type: 'SHORT_INT' }}
+              />
+            </div>
+            <div className="project-card-measure-label">
+              {translate('metric.new_lines.short_name')}
+            </div>
+          </div>
+        </div>}
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardMeasures.js
deleted file mode 100644 (file)
index 6d08f16..0000000
+++ /dev/null
@@ -1,132 +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.
- */
-import React from 'react';
-import ProjectCardLanguages from './ProjectCardLanguages';
-import Measure from '../../component-measures/components/Measure';
-import Rating from '../../../components/ui/Rating';
-import CoverageRating from '../../../components/ui/CoverageRating';
-import DuplicationsRating from '../../../components/ui/DuplicationsRating';
-import SizeRating from '../../../components/ui/SizeRating';
-import { translate } from '../../../helpers/l10n';
-
-export default class ProjectCardMeasures extends React.PureComponent {
-  static propTypes = {
-    measures: React.PropTypes.object
-  };
-
-  render() {
-    const { measures } = this.props;
-
-    if (measures == null) {
-      return null;
-    }
-
-    return (
-      <div className="project-card-measures">
-        <div className="project-card-measure" data-key="reliability_rating">
-          <div className="project-card-measure-inner">
-            <div className="project-card-measure-number">
-              <Rating value={measures['reliability_rating']} />
-            </div>
-            <div className="project-card-measure-label">
-              {translate('metric_domain.Reliability')}
-            </div>
-          </div>
-        </div>
-
-        <div className="project-card-measure" data-key="security_rating">
-          <div className="project-card-measure-inner">
-            <div className="project-card-measure-number">
-              <Rating value={measures['security_rating']} />
-            </div>
-            <div className="project-card-measure-label">
-              {translate('metric_domain.Security')}
-            </div>
-          </div>
-        </div>
-
-        <div className="project-card-measure" data-key="sqale_rating">
-          <div className="project-card-measure-inner">
-            <div className="project-card-measure-number">
-              <Rating value={measures['sqale_rating']} />
-            </div>
-            <div className="project-card-measure-label">
-              {translate('metric_domain.Maintainability')}
-            </div>
-          </div>
-        </div>
-
-        <div className="project-card-measure" data-key="coverage">
-          <div className="project-card-measure-inner">
-            <div className="project-card-measure-number">
-              {measures['coverage'] != null &&
-                <span className="spacer-right">
-                  <CoverageRating value={measures['coverage']} />
-                </span>}
-              <Measure
-                measure={{ value: measures['coverage'] }}
-                metric={{ key: 'coverage', type: 'PERCENT' }}
-              />
-            </div>
-            <div className="project-card-measure-label">
-              {translate('metric.coverage.name')}
-            </div>
-          </div>
-        </div>
-
-        <div className="project-card-measure" data-key="duplicated_lines_density">
-          <div className="project-card-measure-inner">
-            <div className="project-card-measure-number">
-              {measures['duplicated_lines_density'] != null &&
-                <span className="spacer-right">
-                  <DuplicationsRating value={measures['duplicated_lines_density']} />
-                </span>}
-              <Measure
-                measure={{ value: measures['duplicated_lines_density'] }}
-                metric={{ key: 'duplicated_lines_density', type: 'PERCENT' }}
-              />
-            </div>
-            <div className="project-card-measure-label">
-              {translate('metric.duplicated_lines_density.short_name')}
-            </div>
-          </div>
-        </div>
-
-        {measures['ncloc'] != null &&
-          <div className="project-card-measure" data-key="ncloc">
-            <div className="project-card-measure-inner">
-              <div className="project-card-measure-number">
-                <span className="spacer-right">
-                  <SizeRating value={measures['ncloc']} />
-                </span>
-                <Measure
-                  measure={{ value: measures['ncloc'] }}
-                  metric={{ key: 'ncloc', type: 'SHORT_INT' }}
-                />
-              </div>
-              <div className="project-card-measure-label">
-                <ProjectCardLanguages distribution={measures['ncloc_language_distribution']} />
-              </div>
-            </div>
-          </div>}
-      </div>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js
new file mode 100644 (file)
index 0000000..883b803
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * 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 ProjectCardLanguages from './ProjectCardLanguages';
+import Measure from '../../component-measures/components/Measure';
+import Rating from '../../../components/ui/Rating';
+import CoverageRating from '../../../components/ui/CoverageRating';
+import DuplicationsRating from '../../../components/ui/DuplicationsRating';
+import SizeRating from '../../../components/ui/SizeRating';
+import { translate } from '../../../helpers/l10n';
+import { formatMeasure } from '../../../helpers/measures';
+
+type Props = {
+  measures?: { [string]: string }
+};
+
+export default function ProjectCardMeasures({ measures }: Props) {
+  if (measures == null) {
+    return null;
+  }
+
+  return (
+    <div className="project-card-measures">
+      <div className="project-card-measure smaller-card" data-key="reliability_rating">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            <Rating value={measures['reliability_rating']} />
+          </div>
+          <div className="project-card-measure-label">
+            {translate('metric_domain.Reliability')}
+          </div>
+        </div>
+      </div>
+
+      <div className="project-card-measure smaller-card" data-key="security_rating">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            <Rating value={measures['security_rating']} />
+          </div>
+          <div className="project-card-measure-label">
+            {translate('metric_domain.Security')}
+          </div>
+        </div>
+      </div>
+
+      <div className="project-card-measure" data-key="sqale_rating">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            <Rating value={measures['sqale_rating']} />
+          </div>
+          <div className="project-card-measure-label">
+            {translate('metric_domain.Maintainability')}
+          </div>
+        </div>
+      </div>
+
+      <div className="project-card-measure" data-key="coverage">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            {measures['coverage'] != null &&
+              <span className="spacer-right">
+                <CoverageRating value={measures['coverage']} />
+              </span>}
+            <Measure
+              measure={{ value: measures['coverage'] }}
+              metric={{ key: 'coverage', type: 'PERCENT' }}
+            />
+          </div>
+          <div className="project-card-measure-label">
+            {translate('metric.coverage.name')}
+          </div>
+        </div>
+      </div>
+
+      <div className="project-card-measure" data-key="duplicated_lines_density">
+        <div className="project-card-measure-inner">
+          <div className="project-card-measure-number">
+            {measures['duplicated_lines_density'] != null &&
+              <span className="spacer-right">
+                <DuplicationsRating
+                  value={formatMeasure(measures['duplicated_lines_density'], 'FLOAT')}
+                />
+              </span>}
+            <Measure
+              measure={{ value: measures['duplicated_lines_density'] }}
+              metric={{ key: 'duplicated_lines_density', type: 'PERCENT' }}
+            />
+          </div>
+          <div className="project-card-measure-label">
+            {translate('metric.duplicated_lines_density.short_name')}
+          </div>
+        </div>
+      </div>
+
+      {measures['ncloc'] != null &&
+        <div className="project-card-measure" data-key="ncloc">
+          <div className="project-card-measure-inner">
+            <div className="project-card-measure-number">
+              <span className="spacer-right">
+                <SizeRating value={formatMeasure(measures['ncloc'], 'INT')} />
+              </span>
+              <Measure
+                measure={{ value: measures['ncloc'] }}
+                metric={{ key: 'ncloc', type: 'SHORT_INT' }}
+              />
+            </div>
+            <div className="project-card-measure-label">
+              <ProjectCardLanguages distribution={measures['ncloc_language_distribution']} />
+            </div>
+          </div>
+        </div>}
+    </div>
+  );
+}
index d692e2ca9cee74989b6305c03ac44173f549d2ea..e9eea0c0a88f4b519cdd1a62759f472ee2593f72 100644 (file)
  * 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 ProjectCardContainer from './ProjectCardContainer';
 import NoFavoriteProjects from './NoFavoriteProjects';
 import EmptyInstance from './EmptyInstance';
 import EmptySearch from '../../../components/common/EmptySearch';
 
+type Props = {
+  projects?: Array<string>,
+  isFavorite: boolean,
+  isFiltered: boolean,
+  organization?: { key: string },
+  cardType?: string
+};
+
 export default class ProjectsList extends React.PureComponent {
-  static propTypes = {
-    projects: React.PropTypes.arrayOf(React.PropTypes.string),
-    isFavorite: React.PropTypes.bool.isRequired,
-    isFiltered: React.PropTypes.bool.isRequired,
-    organization: React.PropTypes.object
-  };
+  props: Props;
 
   renderNoProjects() {
     if (this.props.isFavorite && !this.props.isFiltered) {
@@ -56,6 +60,7 @@ export default class ProjectsList extends React.PureComponent {
                 key={projectKey}
                 projectKey={projectKey}
                 organization={this.props.organization}
+                type={this.props.cardType}
               />
             ))
           : this.renderNoProjects()}
index 1bd9eacd08767c5c490629aa84aadf497755a4f1..7b5d11e2e4710ca3e7b98422e5d39d3b91fb1d40 100644 (file)
@@ -31,6 +31,7 @@ import { convertToQueryData } from './utils';
 import { receiveFavorites } from '../../../store/favorites/duck';
 import { getOrganizations } from '../../../api/organizations';
 import { receiveOrganizations } from '../../../store/organizations/duck';
+import { isDiffMetric, getPeriodValue } from '../../../helpers/measures';
 
 const PAGE_SIZE = 50;
 const PAGE_SIZE_VISUALIZATIONS = 99;
@@ -46,6 +47,19 @@ const METRICS = [
   'ncloc_language_distribution'
 ];
 
+const LEAK_METRICS = [
+  'alert_status',
+  'new_bugs',
+  'new_reliability_rating',
+  'new_vulnerabilities',
+  'new_security_rating',
+  'new_code_smells',
+  'new_maintainability_rating',
+  'new_coverage',
+  'new_duplicated_lines_density',
+  'new_lines'
+];
+
 const METRICS_BY_VISUALIZATION = {
   risk: ['reliability_rating', 'security_rating', 'coverage', 'ncloc', 'sqale_index'],
   // x, y, size, color
@@ -85,7 +99,9 @@ const onReceiveMeasures = (dispatch, expectedProjectKeys) => response => {
   Object.keys(byComponentKey).forEach(componentKey => {
     const measures = {};
     byComponentKey[componentKey].forEach(measure => {
-      measures[measure.metric] = measure.value;
+      measures[measure.metric] = isDiffMetric(measure.metric)
+        ? getPeriodValue(measure, 1)
+        : measure.value;
     });
     toStore[componentKey] = measures;
   });
@@ -98,10 +114,13 @@ const onReceiveOrganizations = dispatch => response => {
 };
 
 const defineMetrics = query => {
-  if (query.view === 'visualizations') {
-    return METRICS_BY_VISUALIZATION[query.visualization || 'risk'];
-  } else {
-    return METRICS;
+  switch (query.view) {
+    case 'visualizations':
+      return METRICS_BY_VISUALIZATION[query.visualization || 'risk'];
+    case 'leak':
+      return LEAK_METRICS;
+    default:
+      return METRICS;
   }
 };
 
index 8cba5d71531a9bad6c48049e0820e5ced5d67e97..0316f71ab7fa1ba726b6e4932065c21a86229457 100644 (file)
@@ -48,7 +48,7 @@ const getAsArray = (values, elementGetter) => {
   return values.split(',').map(elementGetter);
 };
 
-const getView = rawValue => (rawValue === 'visualizations' ? rawValue : undefined);
+const getView = rawValue => (rawValue === 'overall' ? undefined : rawValue);
 
 const getVisualization = value => {
   return VISUALIZATIONS.includes(value) ? value : null;
index d413f3d0cb619a33de77bdd515dc10d60e7082df..5a94f6006a758dcd1e3297660ae2574093c5b01b 100644 (file)
   margin: 0 -15px;
 }
 
+.project-card-leak-measures {
+  padding: 4px 0;
+  margin: 0 -4px 4px;
+  background-color: #fbf3d5;
+  border: 1px solid #eae3c7;
+}
+
 .project-card-measures .project-card-measure {
   width: 120px;
   box-sizing: border-box;
   padding: 0 15px;
 }
 
-.project-card-measures .project-card-measure:nth-child(-n+2) {
+.project-card-leak-measures .project-card-measure {
+  width: 140px;
+  box-sizing: border-box;
+  padding: 0 5px;
+}
+
+.project-card-measure.smaller-card {
   width: 90px;
 }
 
+@media (max-width: 1130px) {
+  .project-card-leak-measures .project-card-measure {
+    width: 134px;
+    padding: 0 2px;
+  }
+  .project-card-measure.smaller-card {
+    width: 81px;
+  }
+}
+
 .project-card-measure {
   position: relative;
   display: inline-block;
index 9fe7815798c8e51eaf7b747211d6d3e35ed19419..a81d6258c14a6299cdf69d82249f669898a95848 100644 (file)
@@ -47,7 +47,7 @@ export const saveAll = () => save(LOCALSTORAGE_ALL);
 
 export const saveFavorite = () => save(LOCALSTORAGE_FAVORITE);
 
-export const VIEWS = ['overall'];
+export const VIEWS = ['overall', 'leak'];
 
 export const VISUALIZATIONS = [
   'risk',
index ada2170bc5b9f3c8db1c4bf2ea9c9d577b0cd2a5..5c794e2e080a79104efe26c63a456d0721a770e4 100644 (file)
 // @flow
 import React from 'react';
 
-export default function BugIcon() {
+export default function BugIcon({ className }: { className?: string }) {
   /* eslint-disable max-len */
   return (
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
+    <svg
+      className={className}
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 16 16"
+      width="16"
+      height="16">
       <path
         style={{ fill: 'currentColor' }}
         d="M11 9h1.3l.5.8.8-.5-.8-1.3H11v-.3l2-2.3V3h-1v2l-1 1.2V5c-.1-.8-.7-1.5-1.4-1.9L11 1.8l-.7-.7-1.8 1.6-1.8-1.6-.7.7 1.5 1.3C6.7 3.5 6.1 4.2 6 5v1.1L5 5V3H4v2.3l2 2.3V8H4.2l-.7 1.2.8.5.4-.7H6v.3l-2 1.9V14h1v-2.4l1-1C6 12 7.1 13 8.4 13h.8c.7 0 1.4-.3 1.8-.9.3-.4.3-.9.2-1.4l.9.9V14h1v-2.8l-2-1.9V9zm-2 2H8V6h1v5z"
index 9673f988b24ae7c627563d51b7496326e9976760..5a788b1066c17d3581240e9fff62ee43c42e8ab4 100644 (file)
 // @flow
 import React from 'react';
 
-export default function CodeSmellIcon() {
+export default function CodeSmellIcon({ className }: { className?: string }) {
   /* eslint-disable max-len */
   return (
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
+    <svg
+      className={className}
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 16 16"
+      width="16"
+      height="16">
       <path
         style={{ fill: 'currentColor' }}
         d="M8 2C4.7 2 2 4.7 2 8s2.7 6 6 6 6-2.7 6-6-2.7-6-6-6zm-.5 5.5h.9v.9h-.9v-.9zm-3.8.2c-.1 0-.2-.1-.2-.2 0-.4.1-1.2.6-2S5.3 4.2 5.6 4c.2 0 .3 0 .3.1l1.3 2.3c0 .1 0 .2-.1.2-.1.2-.2.3-.3.5-.1.2-.2.4-.2.5 0 .1-.1.2-.2.2l-2.7-.1zM9.9 12c-.3.2-1.1.5-2 .5-.9 0-1.7-.3-2-.5-.1 0-.1-.2-.1-.3l1.3-2.3c0-.1.1-.1.2-.1.2.1.3.1.5.1s.4 0 .5-.1c.1 0 .2 0 .2.1l1.3 2.3c.2.2.2.3.1.3zm2.5-4.1L9.7 8c-.1 0-.2-.1-.2-.2 0-.2-.1-.4-.2-.5 0-.1-.2-.3-.3-.4-.1 0-.1-.1-.1-.2l1.3-2.3c.1-.1.2-.1.3-.1.3.2 1 .7 1.5 1.5s.6 1.6.6 2c0 0-.1.1-.2.1z"
index b3987cb443268c37e274d73d6de47c58235028d2..248eae3d8444efc3c2075129874b61f2c40e99dd 100644 (file)
 // @flow
 import React from 'react';
 
-export default function VulnerabilityIcon() {
+export default function VulnerabilityIcon({ className }: { className?: string }) {
   /* eslint-disable max-len */
   return (
-    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
+    <svg
+      className={className}
+      xmlns="http://www.w3.org/2000/svg"
+      viewBox="0 0 16 16"
+      width="16"
+      height="16">
       <path
         style={{ fill: 'currentColor' }}
         d="M10.8 5H6V3.9a2.28 2.28 0 0 1 2-2.5 2.22 2.22 0 0 1 1.8 1.2.48.48 0 0 0 .7.2.48.48 0 0 0 .2-.7A3 3 0 0 0 8 .4a3.34 3.34 0 0 0-3 3.5v1.2a2.16 2.16 0 0 0-2 2.1v4.4a2.22 2.22 0 0 0 2.2 2.2h5.6a2.22 2.22 0 0 0 2.2-2.2V7.2A2.22 2.22 0 0 0 10.8 5zm-2.2 5.5v1.2H7.4v-1.2a1.66 1.66 0 0 1-1.1-1.6A1.75 1.75 0 0 1 8 7.2a1.71 1.71 0 0 1 .6 3.3z"
index 3da60b5a01272946bae844fe8a4c84186d6e2762..5e7fc312ee8d60c55fbba3f6a935648380d31fb9 100644 (file)
@@ -865,6 +865,7 @@ projects.sort_list=Sort list by
 projects.perspective=Perspective
 projects.view_settings=Settings
 projects.view.overall=Overall Status
+projects.view.leak=Leak
 projects.worse_of_reliablity_and_security=Worse of Reliability and Security
 projects.visualization.risk=Risk
 projects.visualization.risk.description=Get quick insights into the operational risks in your projects. Any color but green indicates immediate risks: Bugs or Vulnerabilities that should be examined. A position at the top or right of the graph means that the longer-term health of the project may be at risk. Green bubbles at the bottom-left are best.
@@ -2177,6 +2178,7 @@ metric.new_duplicated_lines.name=Duplicated Lines on New Code
 metric.new_duplicated_lines.description=Duplicated Lines on New Code
 metric.new_duplicated_lines_density.description=Duplicated lines on new code balanced by statements
 metric.new_duplicated_lines_density.name=Duplicated Lines on New Code (%)
+metric.new_duplicated_lines_density.short_name=New Lines Duplication
 metric.new_info_violations.description=New Info issues
 metric.new_info_violations.name=New Info Issues
 metric.new_it_branch_coverage.description=Integration tests condition coverage of new/changed code
@@ -2205,6 +2207,7 @@ metric.new_minor_violations.description=New Minor issues
 metric.new_minor_violations.name=New Minor Issues
 metric.new_lines.name=Lines on New Code
 metric.new_lines.description=Non commenting lines on new code
+metric.new_lines.short_name=New Lines
 metric.new_overall_branch_coverage.description=Condition coverage of new/changed code by all tests
 metric.new_overall_branch_coverage.name=Overall Condition Coverage on New Code
 metric.new_overall_conditions_to_cover.description=New conditions to cover by all tests