]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10440 Remove useless tooltips on project overview measures
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 22 Feb 2018 13:29:30 +0000 (14:29 +0100)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 23 Feb 2018 15:34:13 +0000 (16:34 +0100)
17 files changed:
server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js [deleted file]
server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.js [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap [deleted file]
server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js [deleted file]
server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js [deleted file]
server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/main/Coverage.js [deleted file]
server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/main/Duplications.js [deleted file]
server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/overview/main/enhance.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js b/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.js
deleted file mode 100644 (file)
index bc0704b..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 Tooltip from '../../../components/controls/Tooltip';
-import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter';
-import { getApplicationLeak } from '../../../api/application';
-import { translate } from '../../../helpers/l10n';
-
-/*::
-type Props = {
-  component: { key: string }
-};
-*/
-
-/*::
-type State = {
-  leaks: ?Array<{ date: string, project: string, projectName: string }>
-};
-*/
-
-export default class ApplicationLeakPeriodLegend extends React.Component {
-  /*:: mounted: boolean; */
-  /*:: props: Props; */
-  state /*: State */ = {
-    leaks: null
-  };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillReceiveProps(nextProps /*: Props */) {
-    if (nextProps.component.key !== this.props.component.key) {
-      this.setState({ leaks: null });
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  fetchLeaks = (visible /*: boolean */) => {
-    if (visible && this.state.leaks == null) {
-      getApplicationLeak(this.props.component.key).then(
-        leaks => {
-          if (this.mounted) {
-            this.setState({ leaks });
-          }
-        },
-        () => {
-          if (this.mounted) {
-            this.setState({ leaks: [] });
-          }
-        }
-      );
-    }
-  };
-
-  renderOverlay = () =>
-    this.state.leaks != null ? (
-      <ul className="text-left">
-        {this.state.leaks.map(leak => (
-          <li key={leak.project}>
-            {leak.projectName}: <DateTooltipFormatter date={leak.date} />
-          </li>
-        ))}
-      </ul>
-    ) : (
-      <i className="spinner spinner-margin" />
-    );
-
-  render() {
-    return (
-      <Tooltip onVisibleChange={this.fetchLeaks} overlay={this.renderOverlay()}>
-        <div className="overview-legend  overview-legend-spaced-line">
-          {translate('issues.leak_period')}
-        </div>
-      </Tooltip>
-    );
-  }
-}
diff --git a/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/overview/components/ApplicationLeakPeriodLegend.tsx
new file mode 100644 (file)
index 0000000..55d8c50
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import Tooltip from '../../../components/controls/Tooltip';
+import DateTooltipFormatter from '../../../components/intl/DateTooltipFormatter';
+import { getApplicationLeak } from '../../../api/application';
+import { translate } from '../../../helpers/l10n';
+
+interface Props {
+  component: string;
+}
+
+interface State {
+  leaks?: Array<{ date: string; project: string; projectName: string }>;
+}
+
+export default class ApplicationLeakPeriodLegend extends React.Component<Props, State> {
+  mounted = false;
+  state: State = {};
+
+  componentDidMount() {
+    this.mounted = true;
+  }
+
+  componentWillReceiveProps(nextProps: Props) {
+    if (nextProps.component !== this.props.component) {
+      this.setState({ leaks: undefined });
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchLeaks = (visible: boolean) => {
+    if (visible && this.state.leaks) {
+      getApplicationLeak(this.props.component).then(
+        leaks => {
+          if (this.mounted) {
+            this.setState({ leaks });
+          }
+        },
+        () => {
+          if (this.mounted) {
+            this.setState({ leaks: [] });
+          }
+        }
+      );
+    }
+  };
+
+  renderOverlay = () =>
+    this.state.leaks != null ? (
+      <ul className="text-left">
+        {this.state.leaks.map(leak => (
+          <li key={leak.project}>
+            {leak.projectName}: <DateTooltipFormatter date={leak.date} />
+          </li>
+        ))}
+      </ul>
+    ) : (
+      <i className="spinner spinner-margin" />
+    );
+
+  render() {
+    return (
+      <Tooltip onVisibleChange={this.fetchLeaks} overlay={this.renderOverlay()}>
+        <div className="overview-legend  overview-legend-spaced-line">
+          {translate('issues.leak_period')}
+        </div>
+      </Tooltip>
+    );
+  }
+}
index a9bba4a81bc432bdd2b3ec74788e39fe8ca88630..5bc3679f9dfae6604b51c956d8941c1af5df510f 100644 (file)
@@ -143,7 +143,9 @@ export class OverviewApp extends React.PureComponent<Props, State> {
   };
 
   getApplicationLeakPeriod = () =>
-    this.state.measures.find(measure => measure.metric.key === 'new_bugs') ? { index: 1 } : null;
+    this.state.measures.find(measure => measure.metric.key === 'new_bugs')
+      ? { index: 1 }
+      : undefined;
 
   renderLoading() {
     return (
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.js b/server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.js
deleted file mode 100644 (file)
index cd50422..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
-import ApplicationLeakPeriodLegend from '../ApplicationLeakPeriodLegend';
-
-it('renders', () => {
-  const wrapper = shallow(<ApplicationLeakPeriodLegend component={{ key: 'foo' }} />);
-  expect(wrapper).toMatchSnapshot();
-  wrapper.setState({
-    leaks: [
-      { date: '2017-01-01T11:39:03+0100', project: 'foo', projectName: 'Foo' },
-      { date: '2017-02-01T11:39:03+0100', project: 'bar', projectName: 'Bar' }
-    ]
-  });
-  expect(wrapper).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/ApplicationLeakPeriodLegend-test.tsx
new file mode 100644 (file)
index 0000000..ee95828
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import { shallow } from 'enzyme';
+import ApplicationLeakPeriodLegend from '../ApplicationLeakPeriodLegend';
+
+it('renders', () => {
+  const wrapper = shallow(<ApplicationLeakPeriodLegend component="foo" />);
+  expect(wrapper).toMatchSnapshot();
+  wrapper.setState({
+    leaks: [
+      { date: '2017-01-01T11:39:03+0100', project: 'foo', projectName: 'Foo' },
+      { date: '2017-02-01T11:39:03+0100', project: 'bar', projectName: 'Bar' }
+    ]
+  });
+  expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.js.snap
deleted file mode 100644 (file)
index cb84259..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<Tooltip
-  onVisibleChange={[Function]}
-  overlay={
-    <i
-      className="spinner spinner-margin"
-    />
-  }
-  placement="bottom"
->
-  <div
-    className="overview-legend  overview-legend-spaced-line"
-  >
-    issues.leak_period
-  </div>
-</Tooltip>
-`;
-
-exports[`renders 2`] = `
-<Tooltip
-  onVisibleChange={[Function]}
-  overlay={
-    <ul
-      className="text-left"
-    >
-      <li>
-        Foo
-        : 
-        <DateTooltipFormatter
-          date="2017-01-01T11:39:03+0100"
-        />
-      </li>
-      <li>
-        Bar
-        : 
-        <DateTooltipFormatter
-          date="2017-02-01T11:39:03+0100"
-        />
-      </li>
-    </ul>
-  }
-  placement="bottom"
->
-  <div
-    className="overview-legend  overview-legend-spaced-line"
-  >
-    issues.leak_period
-  </div>
-</Tooltip>
-`;
diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/ApplicationLeakPeriodLegend-test.tsx.snap
new file mode 100644 (file)
index 0000000..cb84259
--- /dev/null
@@ -0,0 +1,52 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders 1`] = `
+<Tooltip
+  onVisibleChange={[Function]}
+  overlay={
+    <i
+      className="spinner spinner-margin"
+    />
+  }
+  placement="bottom"
+>
+  <div
+    className="overview-legend  overview-legend-spaced-line"
+  >
+    issues.leak_period
+  </div>
+</Tooltip>
+`;
+
+exports[`renders 2`] = `
+<Tooltip
+  onVisibleChange={[Function]}
+  overlay={
+    <ul
+      className="text-left"
+    >
+      <li>
+        Foo
+        : 
+        <DateTooltipFormatter
+          date="2017-01-01T11:39:03+0100"
+        />
+      </li>
+      <li>
+        Bar
+        : 
+        <DateTooltipFormatter
+          date="2017-02-01T11:39:03+0100"
+        />
+      </li>
+    </ul>
+  }
+  placement="bottom"
+>
+  <div
+    className="overview-legend  overview-legend-spaced-line"
+  >
+    issues.leak_period
+  </div>
+</Tooltip>
+`;
diff --git a/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js b/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js
deleted file mode 100644 (file)
index d7a1db6..0000000
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 { Link } from 'react-router';
-import enhance from './enhance';
-import ApplicationLeakPeriodLegend from '../components/ApplicationLeakPeriodLegend';
-import BubblesIcon from '../../../components/icons-components/BubblesIcon';
-import BugIcon from '../../../components/icons-components/BugIcon';
-import LeakPeriodLegend from '../components/LeakPeriodLegend';
-import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon';
-import { getMetricName } from '../helpers/metrics';
-import { getComponentDrilldownUrl } from '../../../helpers/urls';
-import { translate } from '../../../helpers/l10n';
-
-class BugsAndVulnerabilities extends React.PureComponent {
-  renderHeader() {
-    const { branch, component } = this.props;
-
-    return (
-      <div className="overview-card-header">
-        <div className="overview-title">
-          <span>{translate('metric.bugs.name')}</span>
-          <Link
-            className="button button-small spacer-left text-text-bottom"
-            to={getComponentDrilldownUrl(component.key, 'Reliability', branch)}>
-            <BubblesIcon size={14} />
-          </Link>
-          <span className="big-spacer-left">{translate('metric.vulnerabilities.name')}</span>
-          <Link
-            className="button button-small spacer-left text-text-bottom"
-            to={getComponentDrilldownUrl(component.key, 'Security', branch)}>
-            <BubblesIcon size={14} />
-          </Link>
-        </div>
-      </div>
-    );
-  }
-
-  renderLeak() {
-    const { component, leakPeriod } = this.props;
-
-    if (leakPeriod == null) {
-      return null;
-    }
-
-    return (
-      <div className="overview-domain-leak">
-        {component.qualifier === 'APP' ? (
-          <ApplicationLeakPeriodLegend component={component} />
-        ) : (
-          <LeakPeriodLegend period={leakPeriod} />
-        )}
-
-        <div className="overview-domain-measures">
-          <div className="overview-domain-measure">
-            <div className="overview-domain-measure-value">
-              <span style={{ marginLeft: 30 }}>{this.props.renderIssues('new_bugs', 'BUG')}</span>
-              {this.props.renderRating('new_reliability_rating')}
-            </div>
-            <div className="overview-domain-measure-label">
-              <BugIcon className="little-spacer-right" />
-              {getMetricName('new_bugs')}
-            </div>
-          </div>
-          <div className="overview-domain-measure">
-            <div className="overview-domain-measure-value">
-              <span style={{ marginLeft: 30 }}>
-                {this.props.renderIssues('new_vulnerabilities', 'VULNERABILITY')}
-              </span>
-              {this.props.renderRating('new_security_rating')}
-            </div>
-            <div className="overview-domain-measure-label">
-              <VulnerabilityIcon className="little-spacer-right" />
-              {getMetricName('new_vulnerabilities')}
-            </div>
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-  renderNutshell() {
-    return (
-      <div className="overview-domain-nutshell">
-        <div className="overview-domain-measures">
-          <div className="overview-domain-measure">
-            <div className="overview-domain-measure-value">
-              {this.props.renderIssues('bugs', 'BUG')}
-              {this.props.renderRating('reliability_rating')}
-            </div>
-            <div className="overview-domain-measure-label">
-              <BugIcon className="little-spacer-right " />
-              {getMetricName('bugs')}
-              {this.props.renderHistoryLink('bugs')}
-            </div>
-          </div>
-          <div className="overview-domain-measure">
-            <div className="overview-domain-measure-value">
-              {this.props.renderIssues('vulnerabilities', 'VULNERABILITY')}
-              {this.props.renderRating('security_rating')}
-            </div>
-            <div className="overview-domain-measure-label">
-              <VulnerabilityIcon className="little-spacer-right " />
-              {getMetricName('vulnerabilities')}
-              {this.props.renderHistoryLink('vulnerabilities')}
-            </div>
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-  render() {
-    const { measures } = this.props;
-    const bugsMeasure = measures.find(measure => measure.metric.key === 'bugs');
-    if (bugsMeasure == null) {
-      return null;
-    }
-    return (
-      <div className="overview-card overview-card-special">
-        {this.renderHeader()}
-
-        <div className="overview-domain-panel">
-          {this.renderNutshell()}
-          {this.renderLeak()}
-        </div>
-      </div>
-    );
-  }
-}
-
-export default enhance(BugsAndVulnerabilities);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx b/server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.tsx
new file mode 100644 (file)
index 0000000..d6433a1
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import { Link } from 'react-router';
+import enhance, { ComposedProps } from './enhance';
+import ApplicationLeakPeriodLegend from '../components/ApplicationLeakPeriodLegend';
+import BubblesIcon from '../../../components/icons-components/BubblesIcon';
+import BugIcon from '../../../components/icons-components/BugIcon';
+import LeakPeriodLegend from '../components/LeakPeriodLegend';
+import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon';
+import { getMetricName } from '../helpers/metrics';
+import { getComponentDrilldownUrl } from '../../../helpers/urls';
+import { translate } from '../../../helpers/l10n';
+
+export class BugsAndVulnerabilities extends React.PureComponent<ComposedProps> {
+  renderHeader() {
+    const { branch, component } = this.props;
+
+    return (
+      <div className="overview-card-header">
+        <div className="overview-title">
+          <span>{translate('metric.bugs.name')}</span>
+          <Link
+            className="button button-small spacer-left text-text-bottom"
+            to={getComponentDrilldownUrl(component.key, 'Reliability', branch)}>
+            <BubblesIcon size={14} />
+          </Link>
+          <span className="big-spacer-left">{translate('metric.vulnerabilities.name')}</span>
+          <Link
+            className="button button-small spacer-left text-text-bottom"
+            to={getComponentDrilldownUrl(component.key, 'Security', branch)}>
+            <BubblesIcon size={14} />
+          </Link>
+        </div>
+      </div>
+    );
+  }
+
+  renderLeak() {
+    const { component, leakPeriod } = this.props;
+    if (!leakPeriod) {
+      return null;
+    }
+
+    return (
+      <div className="overview-domain-leak">
+        {component.qualifier === 'APP' ? (
+          <ApplicationLeakPeriodLegend component={component.key} />
+        ) : (
+          <LeakPeriodLegend period={leakPeriod} />
+        )}
+
+        <div className="overview-domain-measures">
+          <div className="overview-domain-measure">
+            <div className="overview-domain-measure-value">
+              <span style={{ marginLeft: 30 }}>{this.props.renderIssues('new_bugs', 'BUG')}</span>
+              {this.props.renderRating('new_reliability_rating')}
+            </div>
+            <div className="overview-domain-measure-label">
+              <BugIcon className="little-spacer-right" />
+              {getMetricName('new_bugs')}
+            </div>
+          </div>
+          <div className="overview-domain-measure">
+            <div className="overview-domain-measure-value">
+              <span style={{ marginLeft: 30 }}>
+                {this.props.renderIssues('new_vulnerabilities', 'VULNERABILITY')}
+              </span>
+              {this.props.renderRating('new_security_rating')}
+            </div>
+            <div className="overview-domain-measure-label">
+              <VulnerabilityIcon className="little-spacer-right" />
+              {getMetricName('new_vulnerabilities')}
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderNutshell() {
+    return (
+      <div className="overview-domain-nutshell">
+        <div className="overview-domain-measures">
+          <div className="overview-domain-measure">
+            <div className="overview-domain-measure-value">
+              {this.props.renderIssues('bugs', 'BUG')}
+              {this.props.renderRating('reliability_rating')}
+            </div>
+            <div className="overview-domain-measure-label">
+              <BugIcon className="little-spacer-right " />
+              {getMetricName('bugs')}
+              {this.props.renderHistoryLink('bugs')}
+            </div>
+          </div>
+          <div className="overview-domain-measure">
+            <div className="overview-domain-measure-value">
+              {this.props.renderIssues('vulnerabilities', 'VULNERABILITY')}
+              {this.props.renderRating('security_rating')}
+            </div>
+            <div className="overview-domain-measure-label">
+              <VulnerabilityIcon className="little-spacer-right " />
+              {getMetricName('vulnerabilities')}
+              {this.props.renderHistoryLink('vulnerabilities')}
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  render() {
+    const { measures } = this.props;
+    const bugsMeasure = measures.find(measure => measure.metric.key === 'bugs');
+    if (!bugsMeasure) {
+      return null;
+    }
+    return (
+      <div className="overview-card overview-card-special">
+        {this.renderHeader()}
+
+        <div className="overview-domain-panel">
+          {this.renderNutshell()}
+          {this.renderLeak()}
+        </div>
+      </div>
+    );
+  }
+}
+
+export default enhance(BugsAndVulnerabilities);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
deleted file mode 100644 (file)
index 94786fe..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 { Link } from 'react-router';
-import enhance from './enhance';
-import Tooltip from '../../../components/controls/Tooltip';
-import DateFromNow from '../../../components/intl/DateFromNow';
-import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
-import { getMetricName } from '../helpers/metrics';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
-import { getComponentIssuesUrl } from '../../../helpers/urls';
-import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
-
-class CodeSmells extends React.PureComponent {
-  renderHeader() {
-    return this.props.renderHeader('Maintainability', translate('metric.code_smells.name'));
-  }
-
-  renderDebt(metric, type) {
-    const { branch, measures, component } = this.props;
-    const measure = measures.find(measure => measure.metric.key === metric);
-    const value = this.props.getValue(measure);
-    const params = { branch, resolved: 'false', facetMode: 'effort', types: type };
-
-    if (isDiffMetric(metric)) {
-      Object.assign(params, { sinceLeakPeriod: 'true' });
-    }
-
-    const tooltip = (
-      <DateTimeFormatter date={component.analysisDate}>
-        {formattedAnalysisDate => (
-          <span>{translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate)}</span>
-        )}
-      </DateTimeFormatter>
-    );
-    return (
-      <Tooltip overlay={tooltip} placement="top">
-        <Link to={getComponentIssuesUrl(component.key, params)}>
-          {formatMeasure(value, 'SHORT_WORK_DUR')}
-        </Link>
-      </Tooltip>
-    );
-  }
-
-  renderTimelineStartDate() {
-    if (!this.props.historyStartDate) {
-      return null;
-    }
-    return (
-      <DateFromNow date={this.props.historyStartDate}>
-        {fromNow => (
-          <span className="overview-domain-timeline-date">
-            {translateWithParameters('overview.started_x', fromNow)}
-          </span>
-        )}
-      </DateFromNow>
-    );
-  }
-
-  renderTimeline(range, displayDate) {
-    return this.props.renderTimeline(
-      'sqale_index',
-      range,
-      displayDate ? this.renderTimelineStartDate() : null
-    );
-  }
-
-  renderLeak() {
-    const { leakPeriod } = this.props;
-
-    if (leakPeriod == null) {
-      return null;
-    }
-
-    return (
-      <div className="overview-domain-leak">
-        <div className="overview-domain-measures">
-          <div className="overview-domain-measure">
-            <div className="overview-domain-measure-value">
-              <span style={{ marginLeft: 30 }}>
-                {this.renderDebt('new_technical_debt', 'CODE_SMELL')}
-              </span>
-              {this.props.renderRating('new_maintainability_rating')}
-            </div>
-            <div className="overview-domain-measure-label">{getMetricName('new_effort')}</div>
-          </div>
-          <div className="overview-domain-measure">
-            <div className="overview-domain-measure-value">
-              {this.props.renderIssues('new_code_smells', 'CODE_SMELL')}
-            </div>
-            <div className="overview-domain-measure-label">
-              <CodeSmellIcon className="little-spacer-right" />
-              {getMetricName('new_code_smells')}
-            </div>
-          </div>
-        </div>
-
-        {this.renderTimeline('after')}
-      </div>
-    );
-  }
-
-  renderNutshell() {
-    return (
-      <div className="overview-domain-nutshell">
-        <div className="overview-domain-measures">
-          <div className="overview-domain-measure">
-            <div className="overview-domain-measure-value">
-              {this.renderDebt('sqale_index', 'CODE_SMELL')}
-              {this.props.renderRating('sqale_rating')}
-            </div>
-            <div className="overview-domain-measure-label">
-              {getMetricName('effort')}
-              {this.props.renderHistoryLink('sqale_index')}
-            </div>
-          </div>
-          <div className="overview-domain-measure">
-            <div className="overview-domain-measure-value">
-              {this.props.renderIssues('code_smells', 'CODE_SMELL')}
-            </div>
-            <div className="overview-domain-measure-label offset-left">
-              <CodeSmellIcon className="little-spacer-right " />
-              {getMetricName('code_smells')}
-              {this.props.renderHistoryLink('code_smells')}
-            </div>
-          </div>
-        </div>
-
-        {this.renderTimeline('before', true)}
-      </div>
-    );
-  }
-
-  render() {
-    const { measures } = this.props;
-    const codeSmellsMeasure = measures.find(measure => measure.metric.key === 'code_smells');
-    if (codeSmellsMeasure == null) {
-      return null;
-    }
-    return (
-      <div className="overview-card" id="overview-code-smells">
-        {this.renderHeader()}
-
-        <div className="overview-domain-panel">
-          {this.renderNutshell()}
-          {this.renderLeak()}
-        </div>
-      </div>
-    );
-  }
-}
-
-export default enhance(CodeSmells);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx b/server/sonar-web/src/main/js/apps/overview/main/CodeSmells.tsx
new file mode 100644 (file)
index 0000000..ac7fcd0
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import { Link } from 'react-router';
+import enhance, { ComposedProps } from './enhance';
+import DateFromNow from '../../../components/intl/DateFromNow';
+import { getMetricName } from '../helpers/metrics';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { formatMeasure, isDiffMetric } from '../../../helpers/measures';
+import { getComponentIssuesUrl } from '../../../helpers/urls';
+import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
+
+export class CodeSmells extends React.PureComponent<ComposedProps> {
+  renderHeader() {
+    return this.props.renderHeader('Maintainability', translate('metric.code_smells.name'));
+  }
+
+  renderDebt(metric: string, type: string) {
+    const { branch, measures, component } = this.props;
+    const measure = measures.find(measure => measure.metric.key === metric);
+    const value = measure ? this.props.getValue(measure) : undefined;
+    const params = { branch, resolved: 'false', facetMode: 'effort', types: type };
+
+    if (isDiffMetric(metric)) {
+      Object.assign(params, { sinceLeakPeriod: 'true' });
+    }
+
+    return (
+      <Link to={getComponentIssuesUrl(component.key, params)}>
+        {formatMeasure(value, 'SHORT_WORK_DUR')}
+      </Link>
+    );
+  }
+
+  renderTimelineStartDate() {
+    if (!this.props.historyStartDate) {
+      return null;
+    }
+    return (
+      <DateFromNow date={this.props.historyStartDate}>
+        {fromNow => (
+          <span className="overview-domain-timeline-date">
+            {translateWithParameters('overview.started_x', fromNow)}
+          </span>
+        )}
+      </DateFromNow>
+    );
+  }
+
+  renderTimeline(range: string, displayDate?: boolean) {
+    return this.props.renderTimeline(
+      'sqale_index',
+      range,
+      displayDate ? this.renderTimelineStartDate() : null
+    );
+  }
+
+  renderLeak() {
+    const { leakPeriod } = this.props;
+    if (!leakPeriod) {
+      return null;
+    }
+
+    return (
+      <div className="overview-domain-leak">
+        <div className="overview-domain-measures">
+          <div className="overview-domain-measure">
+            <div className="overview-domain-measure-value">
+              <span style={{ marginLeft: 30 }}>
+                {this.renderDebt('new_technical_debt', 'CODE_SMELL')}
+              </span>
+              {this.props.renderRating('new_maintainability_rating')}
+            </div>
+            <div className="overview-domain-measure-label">{getMetricName('new_effort')}</div>
+          </div>
+          <div className="overview-domain-measure">
+            <div className="overview-domain-measure-value">
+              {this.props.renderIssues('new_code_smells', 'CODE_SMELL')}
+            </div>
+            <div className="overview-domain-measure-label">
+              <CodeSmellIcon className="little-spacer-right" />
+              {getMetricName('new_code_smells')}
+            </div>
+          </div>
+        </div>
+
+        {this.renderTimeline('after')}
+      </div>
+    );
+  }
+
+  renderNutshell() {
+    return (
+      <div className="overview-domain-nutshell">
+        <div className="overview-domain-measures">
+          <div className="overview-domain-measure">
+            <div className="overview-domain-measure-value">
+              {this.renderDebt('sqale_index', 'CODE_SMELL')}
+              {this.props.renderRating('sqale_rating')}
+            </div>
+            <div className="overview-domain-measure-label">
+              {getMetricName('effort')}
+              {this.props.renderHistoryLink('sqale_index')}
+            </div>
+          </div>
+          <div className="overview-domain-measure">
+            <div className="overview-domain-measure-value">
+              {this.props.renderIssues('code_smells', 'CODE_SMELL')}
+            </div>
+            <div className="overview-domain-measure-label offset-left">
+              <CodeSmellIcon className="little-spacer-right " />
+              {getMetricName('code_smells')}
+              {this.props.renderHistoryLink('code_smells')}
+            </div>
+          </div>
+        </div>
+
+        {this.renderTimeline('before', true)}
+      </div>
+    );
+  }
+
+  render() {
+    const { measures } = this.props;
+    const codeSmellsMeasure = measures.find(measure => measure.metric.key === 'code_smells');
+    if (!codeSmellsMeasure) {
+      return null;
+    }
+    return (
+      <div className="overview-card" id="overview-code-smells">
+        {this.renderHeader()}
+
+        <div className="overview-domain-panel">
+          {this.renderNutshell()}
+          {this.renderLeak()}
+        </div>
+      </div>
+    );
+  }
+}
+
+export default enhance(CodeSmells);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Coverage.js b/server/sonar-web/src/main/js/apps/overview/main/Coverage.js
deleted file mode 100644 (file)
index 6d800e2..0000000
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 enhance from './enhance';
-import DrilldownLink from '../../../components/shared/DrilldownLink';
-import { getMetricName } from '../helpers/metrics';
-import { formatMeasure, getPeriodValue } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
-import CoverageRating from '../../../components/ui/CoverageRating';
-
-class Coverage extends React.PureComponent {
-  getCoverage() {
-    const { measures } = this.props;
-    const { value } = measures.find(measure => measure.metric.key === 'coverage');
-    return Number(value);
-  }
-
-  getNewCoverageMeasure() {
-    const { measures } = this.props;
-    return measures.find(measure => measure.metric.key === 'new_coverage');
-  }
-
-  getNewLinesToCover() {
-    const { measures } = this.props;
-    return measures.find(measure => measure.metric.key === 'new_lines_to_cover');
-  }
-
-  renderHeader() {
-    return this.props.renderHeader('Coverage', translate('metric.coverage.name'));
-  }
-
-  renderTimeline(range) {
-    return this.props.renderTimeline('coverage', range);
-  }
-
-  renderTests() {
-    return this.props.renderMeasure('tests');
-  }
-
-  renderCoverage() {
-    const { branch, component } = this.props;
-    const metric = 'coverage';
-    const coverage = this.getCoverage();
-
-    return (
-      <div className="overview-domain-measure">
-        <div className="display-inline-block text-middle big-spacer-right">
-          <CoverageRating value={coverage} size="big" />
-        </div>
-
-        <div className="display-inline-block text-middle">
-          <div className="overview-domain-measure-value">
-            <DrilldownLink branch={branch} component={component.key} metric={metric}>
-              <span className="js-overview-main-coverage">
-                {formatMeasure(coverage, 'PERCENT')}
-              </span>
-            </DrilldownLink>
-          </div>
-
-          <div className="overview-domain-measure-label">
-            {getMetricName('coverage')}
-            {this.props.renderHistoryLink('coverage')}
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-  renderNewCoverage() {
-    const { branch, component, leakPeriod } = this.props;
-    const newCoverageMeasure = this.getNewCoverageMeasure();
-    const newLinesToCover = this.getNewLinesToCover();
-
-    const newCoverageValue = newCoverageMeasure
-      ? getPeriodValue(newCoverageMeasure, leakPeriod.index)
-      : null;
-    const newLinesToCoverValue = newLinesToCover
-      ? getPeriodValue(newLinesToCover, leakPeriod.index)
-      : null;
-
-    const formattedValue =
-      newCoverageValue != null ? (
-        <div>
-          <DrilldownLink
-            branch={branch}
-            component={component.key}
-            metric={newCoverageMeasure.metric.key}>
-            <span className="js-overview-main-new-coverage">
-              {formatMeasure(newCoverageValue, 'PERCENT')}
-            </span>
-          </DrilldownLink>
-        </div>
-      ) : (
-        <span>—</span>
-      );
-    const label =
-      newLinesToCoverValue != null && newLinesToCoverValue > 0 ? (
-        <div className="overview-domain-measure-label">
-          {translate('overview.coverage_on')}
-          <br />
-          <DrilldownLink
-            branch={branch}
-            className="spacer-right overview-domain-secondary-measure-value"
-            component={component.key}
-            metric={newLinesToCover.metric.key}>
-            <span className="js-overview-main-new-coverage">
-              {formatMeasure(newLinesToCoverValue, 'SHORT_INT')}
-            </span>
-          </DrilldownLink>
-          {getMetricName('new_lines_to_cover')}
-        </div>
-      ) : (
-        <div className="overview-domain-measure-label">{getMetricName('new_coverage')}</div>
-      );
-    return (
-      <div className="overview-domain-measure">
-        <div className="overview-domain-measure-value">{formattedValue}</div>
-        {label}
-      </div>
-    );
-  }
-
-  renderNutshell() {
-    return (
-      <div className="overview-domain-nutshell">
-        <div className="overview-domain-measures">
-          {this.renderCoverage()}
-          {this.renderTests()}
-        </div>
-
-        {this.renderTimeline('before')}
-      </div>
-    );
-  }
-
-  renderLeak() {
-    const { leakPeriod } = this.props;
-    if (leakPeriod == null) {
-      return null;
-    }
-    return (
-      <div className="overview-domain-leak">
-        <div className="overview-domain-measures">{this.renderNewCoverage()}</div>
-
-        {this.renderTimeline('after')}
-      </div>
-    );
-  }
-
-  render() {
-    const { measures } = this.props;
-    const coverageMeasure = measures.find(measure => measure.metric.key === 'coverage');
-    if (coverageMeasure == null) {
-      return null;
-    }
-    return (
-      <div className="overview-card">
-        {this.renderHeader()}
-
-        <div className="overview-domain-panel">
-          {this.renderNutshell()}
-          {this.renderLeak()}
-        </div>
-      </div>
-    );
-  }
-}
-
-export default enhance(Coverage);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx b/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx
new file mode 100644 (file)
index 0000000..cd1abc8
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import enhance, { ComposedProps } from './enhance';
+import DrilldownLink from '../../../components/shared/DrilldownLink';
+import { getMetricName } from '../helpers/metrics';
+import { formatMeasure, getPeriodValue } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+import CoverageRating from '../../../components/ui/CoverageRating';
+
+export class Coverage extends React.PureComponent<ComposedProps> {
+  getCoverage() {
+    const measure = this.props.measures.find(measure => measure.metric.key === 'coverage');
+    return Number(measure ? measure.value : undefined);
+  }
+
+  renderHeader() {
+    return this.props.renderHeader('Coverage', translate('metric.coverage.name'));
+  }
+
+  renderTimeline(range: string) {
+    return this.props.renderTimeline('coverage', range);
+  }
+
+  renderTests() {
+    return this.props.renderMeasure('tests');
+  }
+
+  renderCoverage() {
+    const { branch, component } = this.props;
+    const metric = 'coverage';
+    const coverage = this.getCoverage();
+
+    return (
+      <div className="overview-domain-measure">
+        <div className="display-inline-block text-middle big-spacer-right">
+          <CoverageRating size="big" value={coverage} />
+        </div>
+
+        <div className="display-inline-block text-middle">
+          <div className="overview-domain-measure-value">
+            <DrilldownLink branch={branch} component={component.key} metric={metric}>
+              <span className="js-overview-main-coverage">
+                {formatMeasure(coverage, 'PERCENT')}
+              </span>
+            </DrilldownLink>
+          </div>
+
+          <div className="overview-domain-measure-label">
+            {getMetricName('coverage')}
+            {this.props.renderHistoryLink('coverage')}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderNewCoverage() {
+    const { branch, component, leakPeriod, measures } = this.props;
+    if (!leakPeriod) {
+      return null;
+    }
+
+    const newCoverageMeasure = measures.find(measure => measure.metric.key === 'new_coverage');
+    const newCoverageValue =
+      newCoverageMeasure && getPeriodValue(newCoverageMeasure, leakPeriod.index);
+    const formattedValue =
+      newCoverageMeasure && newCoverageValue !== undefined ? (
+        <div>
+          <DrilldownLink
+            branch={branch}
+            component={component.key}
+            metric={newCoverageMeasure.metric.key}>
+            <span className="js-overview-main-new-coverage">
+              {formatMeasure(newCoverageValue, 'PERCENT')}
+            </span>
+          </DrilldownLink>
+        </div>
+      ) : (
+        <span>—</span>
+      );
+
+    const newLinesToCover = measures.find(measure => measure.metric.key === 'new_lines_to_cover');
+    const newLinesToCoverValue =
+      newLinesToCover && getPeriodValue(newLinesToCover, leakPeriod.index);
+    const label =
+      newLinesToCover && newLinesToCoverValue !== undefined && Number(newLinesToCoverValue) > 0 ? (
+        <div className="overview-domain-measure-label">
+          {translate('overview.coverage_on')}
+          <br />
+          <DrilldownLink
+            branch={branch}
+            className="spacer-right overview-domain-secondary-measure-value"
+            component={component.key}
+            metric={newLinesToCover.metric.key}>
+            <span className="js-overview-main-new-coverage">
+              {formatMeasure(newLinesToCoverValue, 'SHORT_INT')}
+            </span>
+          </DrilldownLink>
+          {getMetricName('new_lines_to_cover')}
+        </div>
+      ) : (
+        <div className="overview-domain-measure-label">{getMetricName('new_coverage')}</div>
+      );
+
+    return (
+      <div className="overview-domain-measure">
+        <div className="overview-domain-measure-value">{formattedValue}</div>
+        {label}
+      </div>
+    );
+  }
+
+  renderNutshell() {
+    return (
+      <div className="overview-domain-nutshell">
+        <div className="overview-domain-measures">
+          {this.renderCoverage()}
+          {this.renderTests()}
+        </div>
+
+        {this.renderTimeline('before')}
+      </div>
+    );
+  }
+
+  renderLeak() {
+    const { leakPeriod } = this.props;
+    if (!leakPeriod) {
+      return null;
+    }
+    return (
+      <div className="overview-domain-leak">
+        <div className="overview-domain-measures">{this.renderNewCoverage()}</div>
+
+        {this.renderTimeline('after')}
+      </div>
+    );
+  }
+
+  render() {
+    const { measures } = this.props;
+    const coverageMeasure = measures.find(measure => measure.metric.key === 'coverage');
+    if (!coverageMeasure) {
+      return null;
+    }
+    return (
+      <div className="overview-card">
+        {this.renderHeader()}
+
+        <div className="overview-domain-panel">
+          {this.renderNutshell()}
+          {this.renderLeak()}
+        </div>
+      </div>
+    );
+  }
+}
+
+export default enhance(Coverage);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Duplications.js b/server/sonar-web/src/main/js/apps/overview/main/Duplications.js
deleted file mode 100644 (file)
index 3f94296..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 enhance from './enhance';
-import DrilldownLink from '../../../components/shared/DrilldownLink';
-import { getMetricName } from '../helpers/metrics';
-import { formatMeasure, getPeriodValue } from '../../../helpers/measures';
-import { translate } from '../../../helpers/l10n';
-import DuplicationsRating from '../../../components/ui/DuplicationsRating';
-
-class Duplications extends React.PureComponent {
-  renderHeader() {
-    return this.props.renderHeader('Duplications', translate('overview.domain.duplications'));
-  }
-
-  renderTimeline(range) {
-    return this.props.renderTimeline('duplicated_lines_density', range);
-  }
-
-  renderDuplicatedBlocks() {
-    return this.props.renderMeasure('duplicated_blocks');
-  }
-
-  renderDuplications() {
-    const { branch, component, measures } = this.props;
-    const measure = measures.find(measure => measure.metric.key === 'duplicated_lines_density');
-    const duplications = Number(measure.value);
-
-    return (
-      <div className="overview-domain-measure">
-        <div className="display-inline-block text-middle big-spacer-right">
-          <DuplicationsRating value={duplications} size="big" />
-        </div>
-
-        <div className="display-inline-block text-middle">
-          <div className="overview-domain-measure-value">
-            <DrilldownLink
-              branch={branch}
-              component={component.key}
-              metric="duplicated_lines_density">
-              {formatMeasure(duplications, 'PERCENT')}
-            </DrilldownLink>
-          </div>
-
-          <div className="overview-domain-measure-label offset-left">
-            {getMetricName('duplications')}
-            {this.props.renderHistoryLink('duplicated_lines_density')}
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-  renderNewDuplications() {
-    const { branch, component, measures, leakPeriod } = this.props;
-    const newDuplicationsMeasure = measures.find(
-      measure => measure.metric.key === 'new_duplicated_lines_density'
-    );
-    const newLinesMeasure = measures.find(measure => measure.metric.key === 'new_lines');
-
-    const newDuplicationsValue = newDuplicationsMeasure
-      ? getPeriodValue(newDuplicationsMeasure, leakPeriod.index)
-      : null;
-    const newLinesValue = newLinesMeasure
-      ? getPeriodValue(newLinesMeasure, leakPeriod.index)
-      : null;
-
-    const formattedValue =
-      newDuplicationsValue != null ? (
-        <div>
-          <DrilldownLink
-            branch={branch}
-            component={component.key}
-            metric={newDuplicationsMeasure.metric.key}>
-            <span className="js-overview-main-new-duplications">
-              {formatMeasure(newDuplicationsValue, 'PERCENT')}
-            </span>
-          </DrilldownLink>
-        </div>
-      ) : (
-        <span>—</span>
-      );
-    const label =
-      newLinesValue != null && newLinesValue > 0 ? (
-        <div className="overview-domain-measure-label">
-          {translate('overview.duplications_on')}
-          <br />
-          <DrilldownLink
-            branch={branch}
-            className="spacer-right overview-domain-secondary-measure-value"
-            component={component.key}
-            metric={newLinesMeasure.metric.key}>
-            <span className="js-overview-main-new-lines">
-              {formatMeasure(newLinesValue, 'SHORT_INT')}
-            </span>
-          </DrilldownLink>
-          {getMetricName('new_lines')}
-        </div>
-      ) : (
-        <div className="overview-domain-measure-label">{getMetricName('new_duplications')}</div>
-      );
-    return (
-      <div className="overview-domain-measure">
-        <div className="overview-domain-measure-value">{formattedValue}</div>
-        {label}
-      </div>
-    );
-  }
-
-  renderNutshell() {
-    return (
-      <div className="overview-domain-nutshell">
-        <div className="overview-domain-measures">
-          {this.renderDuplications()}
-          {this.renderDuplicatedBlocks()}
-        </div>
-
-        {this.renderTimeline('before')}
-      </div>
-    );
-  }
-
-  renderLeak() {
-    const { leakPeriod } = this.props;
-    if (leakPeriod == null) {
-      return null;
-    }
-    return (
-      <div className="overview-domain-leak">
-        <div className="overview-domain-measures">{this.renderNewDuplications()}</div>
-
-        {this.renderTimeline('after')}
-      </div>
-    );
-  }
-
-  render() {
-    const { measures } = this.props;
-    const duplications = measures.find(
-      measure => measure.metric.key === 'duplicated_lines_density'
-    );
-    if (duplications == null) {
-      return null;
-    }
-    return (
-      <div className="overview-card">
-        {this.renderHeader()}
-
-        <div className="overview-domain-panel">
-          {this.renderNutshell()}
-          {this.renderLeak()}
-        </div>
-      </div>
-    );
-  }
-}
-
-export default enhance(Duplications);
diff --git a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx b/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx
new file mode 100644 (file)
index 0000000..d8a09ca
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 * as React from 'react';
+import enhance, { ComposedProps } from './enhance';
+import DrilldownLink from '../../../components/shared/DrilldownLink';
+import { getMetricName } from '../helpers/metrics';
+import { formatMeasure, getPeriodValue } from '../../../helpers/measures';
+import { translate } from '../../../helpers/l10n';
+import DuplicationsRating from '../../../components/ui/DuplicationsRating';
+
+export class Duplications extends React.PureComponent<ComposedProps> {
+  renderHeader() {
+    return this.props.renderHeader('Duplications', translate('overview.domain.duplications'));
+  }
+
+  renderTimeline(range: string) {
+    return this.props.renderTimeline('duplicated_lines_density', range);
+  }
+
+  renderDuplicatedBlocks() {
+    return this.props.renderMeasure('duplicated_blocks');
+  }
+
+  renderDuplications() {
+    const { branch, component, measures } = this.props;
+
+    const measure = measures.find(measure => measure.metric.key === 'duplicated_lines_density');
+    if (!measure) {
+      return null;
+    }
+
+    const duplications = Number(measure.value);
+
+    return (
+      <div className="overview-domain-measure">
+        <div className="display-inline-block text-middle big-spacer-right">
+          <DuplicationsRating size="big" value={duplications} />
+        </div>
+
+        <div className="display-inline-block text-middle">
+          <div className="overview-domain-measure-value">
+            <DrilldownLink
+              branch={branch}
+              component={component.key}
+              metric="duplicated_lines_density">
+              {formatMeasure(duplications, 'PERCENT')}
+            </DrilldownLink>
+          </div>
+
+          <div className="overview-domain-measure-label offset-left">
+            {getMetricName('duplications')}
+            {this.props.renderHistoryLink('duplicated_lines_density')}
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+  renderNewDuplications() {
+    const { branch, component, measures, leakPeriod } = this.props;
+    if (!leakPeriod) {
+      return null;
+    }
+
+    const newDuplicationsMeasure = measures.find(
+      measure => measure.metric.key === 'new_duplicated_lines_density'
+    );
+    const newDuplicationsValue =
+      newDuplicationsMeasure && getPeriodValue(newDuplicationsMeasure, leakPeriod.index);
+    const formattedValue =
+      newDuplicationsMeasure && newDuplicationsValue ? (
+        <div>
+          <DrilldownLink
+            branch={branch}
+            component={component.key}
+            metric={newDuplicationsMeasure.metric.key}>
+            <span className="js-overview-main-new-duplications">
+              {formatMeasure(newDuplicationsValue, 'PERCENT')}
+            </span>
+          </DrilldownLink>
+        </div>
+      ) : (
+        <span>—</span>
+      );
+
+    const newLinesMeasure = measures.find(measure => measure.metric.key === 'new_lines');
+    const newLinesValue = newLinesMeasure && getPeriodValue(newLinesMeasure, leakPeriod.index);
+    const label =
+      newLinesMeasure && newLinesValue !== undefined && Number(newLinesValue) > 0 ? (
+        <div className="overview-domain-measure-label">
+          {translate('overview.duplications_on')}
+          <br />
+          <DrilldownLink
+            branch={branch}
+            className="spacer-right overview-domain-secondary-measure-value"
+            component={component.key}
+            metric={newLinesMeasure.metric.key}>
+            <span className="js-overview-main-new-lines">
+              {formatMeasure(newLinesValue, 'SHORT_INT')}
+            </span>
+          </DrilldownLink>
+          {getMetricName('new_lines')}
+        </div>
+      ) : (
+        <div className="overview-domain-measure-label">{getMetricName('new_duplications')}</div>
+      );
+
+    return (
+      <div className="overview-domain-measure">
+        <div className="overview-domain-measure-value">{formattedValue}</div>
+        {label}
+      </div>
+    );
+  }
+
+  renderNutshell() {
+    return (
+      <div className="overview-domain-nutshell">
+        <div className="overview-domain-measures">
+          {this.renderDuplications()}
+          {this.renderDuplicatedBlocks()}
+        </div>
+
+        {this.renderTimeline('before')}
+      </div>
+    );
+  }
+
+  renderLeak() {
+    const { leakPeriod } = this.props;
+    if (leakPeriod == null) {
+      return null;
+    }
+    return (
+      <div className="overview-domain-leak">
+        <div className="overview-domain-measures">{this.renderNewDuplications()}</div>
+
+        {this.renderTimeline('after')}
+      </div>
+    );
+  }
+
+  render() {
+    const { measures } = this.props;
+    const duplications = measures.find(
+      measure => measure.metric.key === 'duplicated_lines_density'
+    );
+    if (duplications == null) {
+      return null;
+    }
+    return (
+      <div className="overview-card">
+        {this.renderHeader()}
+
+        <div className="overview-domain-panel">
+          {this.renderNutshell()}
+          {this.renderLeak()}
+        </div>
+      </div>
+    );
+  }
+}
+
+export default enhance(Duplications);
index 43e23c1715df91883b0ce23a7e5a28655a2336c8..2b07c3c92393a7ab21db9bf4fcdb8f90ea655db6 100644 (file)
@@ -21,7 +21,6 @@ import * as React from 'react';
 import { Link } from 'react-router';
 import DrilldownLink from '../../../components/shared/DrilldownLink';
 import BubblesIcon from '../../../components/icons-components/BubblesIcon';
-import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import HistoryIcon from '../../../components/icons-components/HistoryIcon';
 import Rating from '../../../components/ui/Rating';
 import Timeline from '../components/Timeline';
@@ -34,7 +33,7 @@ import {
   getRatingTooltip,
   MeasureEnhanced
 } from '../../../helpers/measures';
-import { translateWithParameters, getLocalizedMetricName } from '../../../helpers/l10n';
+import { getLocalizedMetricName } from '../../../helpers/l10n';
 import { getPeriodDate } from '../../../helpers/periods';
 import {
   getComponentDrilldownUrl,
@@ -155,22 +154,10 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
         Object.assign(params, { sinceLeakPeriod: 'true' });
       }
 
-      const tooltip = component.analysisDate && (
-        <DateTimeFormatter date={component.analysisDate}>
-          {formattedAnalysisDate => (
-            <span>
-              {translateWithParameters('widget.as_calculated_on_x', formattedAnalysisDate)}
-            </span>
-          )}
-        </DateTimeFormatter>
-      );
-
       return (
-        <Tooltip overlay={tooltip} placement="top">
-          <Link to={getComponentIssuesUrl(component.key, params)}>
-            {formatMeasure(value, 'SHORT_INT')}
-          </Link>
-        </Tooltip>
+        <Link to={getComponentIssuesUrl(component.key, params)}>
+          {formatMeasure(value, 'SHORT_INT')}
+        </Link>
       );
     };
 
@@ -209,9 +196,9 @@ export default function enhance(ComposedComponent: React.ComponentType<ComposedP
           getValue={this.getValue}
           renderHeader={this.renderHeader}
           renderHistoryLink={this.renderHistoryLink}
+          renderIssues={this.renderIssues}
           renderMeasure={this.renderMeasure}
           renderRating={this.renderRating}
-          renderIssues={this.renderIssues}
           renderTimeline={this.renderTimeline}
         />
       );
index 8144c5c5190198d2ab9eb18a73534a4e165cb827..ac534cbda9e2cdce95f6bc211e3845d1d5c58ccf 100644 (file)
@@ -2364,8 +2364,6 @@ overview.badges.marketing.description=This badge lets you advertise that you're
 overview.badges.quality_gate.alt=SonarCloud Quality Gate badge
 overview.badges.quality_gate.description=This badge dynamically displays the current quality gate status of your project.
 
-widget.as_calculated_on_x=As calculated on {0}
-
 
 #------------------------------------------------------------------------------
 #