]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9403 Change style of metrics with no historical data in the custom graph legend
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 12 Jul 2017 12:34:59 +0000 (14:34 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 13 Jul 2017 12:34:17 +0000 (14:34 +0200)
server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js
server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendItem-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendItem-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
server/sonar-web/src/main/js/apps/projectActivity/utils.js
server/sonar-web/src/main/js/components/icons-components/AlertWarnIcon.js [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index e0236cb4e2f15f9e5a93528dad09a7d67f5d5100..540bb65961aab6724dbe368bfb017fd507e69336 100644 (file)
@@ -19,6 +19,9 @@
  */
 import React from 'react';
 import GraphsLegendItem from './GraphsLegendItem';
+import Tooltip from '../../../components/controls/Tooltip';
+import { hasDataValues } from '../utils';
+import { translate } from '../../../helpers/l10n';
 import type { Metric } from '../types';
 
 type Props = {
@@ -32,14 +35,31 @@ export default function GraphsLegendCustom({ metrics, removeMetric, series }: Pr
     <div className="project-activity-graph-legends">
       {series.map(serie => {
         const metric = metrics.find(metric => metric.key === serie.name);
+        const hasData = hasDataValues(serie);
+        const legendItem = (
+          <GraphsLegendItem
+            metric={serie.name}
+            name={metric && metric.custom ? metric.name : serie.translatedName}
+            showWarning={!hasData}
+            style={serie.style}
+            removeMetric={removeMetric}
+          />
+        );
+        if (!hasData) {
+          return (
+            <Tooltip
+              key={serie.name}
+              overlay={translate('project_activity.graphs.custom.metric_no_history')}
+              placement="bottom">
+              <span className="spacer-left spacer-right">
+                {legendItem}
+              </span>
+            </Tooltip>
+          );
+        }
         return (
           <span className="spacer-left spacer-right" key={serie.name}>
-            <GraphsLegendItem
-              metric={serie.name}
-              name={metric && metric.custom ? metric.name : serie.translatedName}
-              style={serie.style}
-              removeMetric={removeMetric}
-            />
+            {legendItem}
           </span>
         );
       })}
index 79d294505c062565e6f5cfd44df77ca9438d538e..a458455a85cf56d8676da049a07db76481a727dc 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 classNames from 'classnames';
-import CloseIcon from '../../../components/icons-components/CloseIcon';
+import AlertWarnIcon from '../../../components/icons-components/AlertWarnIcon';
 import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
+import CloseIcon from '../../../components/icons-components/CloseIcon';
 
 type Props = {
   className?: string,
   metric: string,
   name: string,
+  showWarning?: boolean,
   style: string,
   removeMetric?: string => void
 };
@@ -35,26 +38,30 @@ export default class GraphsLegendItem extends React.PureComponent {
 
   handleClick = (e: Event) => {
     e.preventDefault();
-    this.props.removeMetric(this.props.metric);
+    if (this.props.removeMetric) {
+      this.props.removeMetric(this.props.metric);
+    }
   };
 
   render() {
     const isActionable = this.props.removeMetric != null;
     const legendClass = classNames(
       {
-        'project-activity-graph-legend-actionable': isActionable
+        'project-activity-graph-legend-actionable': isActionable,
+        'alert-warning': this.props.showWarning
       },
       this.props.className
     );
-
     return (
       <span className={legendClass}>
-        <ChartLegendIcon
-          className={classNames(
-            'spacer-right line-chart-legend',
-            'line-chart-legend-' + this.props.style
-          )}
-        />
+        {this.props.showWarning
+          ? <AlertWarnIcon className="spacer-right" />
+          : <ChartLegendIcon
+              className={classNames(
+                'spacer-right line-chart-legend',
+                'line-chart-legend-' + this.props.style
+              )}
+            />}
         {this.props.name}
         {isActionable &&
           <a className="spacer-left button-clean text-text-top" href="#" onClick={this.handleClick}>
index 9b785f2213be336cbc9df4790b3e351f3eb6d655..c115f0b5f13c601074ddfe872437538c703a4081 100644 (file)
@@ -22,8 +22,13 @@ import { shallow } from 'enzyme';
 import GraphsLegendCustom from '../GraphsLegendCustom';
 
 const SERIES = [
-  { name: 'bugs', translatedName: 'Bugs', style: '2', data: [] },
-  { name: 'my_metric', translatedName: 'metric.my_metric.name', style: '1', data: [] },
+  { name: 'bugs', translatedName: 'Bugs', style: '2', data: [{ x: 1, y: 1 }] },
+  {
+    name: 'my_metric',
+    translatedName: 'metric.my_metric.name',
+    style: '1',
+    data: [{ x: 1, y: 1 }]
+  },
   { name: 'foo', translatedName: 'Foo', style: '0', data: [] }
 ];
 
index a000a1dec049b74b61481877ea5c355308fbbfa2..5a0af033764a71dc9d1d531d4edcab5c8a66d175 100644 (file)
@@ -38,3 +38,11 @@ it('should render correctly an actionable legend', () => {
     )
   ).toMatchSnapshot();
 });
+
+it('should render correctly legends with warning', () => {
+  expect(
+    shallow(
+      <GraphsLegendItem className="myclass" metric="foo" name="Foo" showWarning={true} style="1" />
+    )
+  ).toMatchSnapshot();
+});
index b19b8e8e654f29ddec3b0405f0880e1f998b5896..d90f7a6843d1bf455b4450de95ce7d1bb8bebc3c 100644 (file)
@@ -11,6 +11,7 @@ exports[`should render correctly the list of series 1`] = `
       metric="bugs"
       name="Bugs"
       removeMetric={[Function]}
+      showWarning={false}
       style="2"
     />
   </span>
@@ -21,18 +22,25 @@ exports[`should render correctly the list of series 1`] = `
       metric="my_metric"
       name="My Metric"
       removeMetric={[Function]}
+      showWarning={false}
       style="1"
     />
   </span>
-  <span
-    className="spacer-left spacer-right"
+  <Tooltip
+    overlay="project_activity.graphs.custom.metric_no_history"
+    placement="bottom"
   >
-    <GraphsLegendItem
-      metric="foo"
-      name="Foo"
-      removeMetric={[Function]}
-      style="0"
-    />
-  </span>
+    <span
+      className="spacer-left spacer-right"
+    >
+      <GraphsLegendItem
+        metric="foo"
+        name="Foo"
+        removeMetric={[Function]}
+        showWarning={true}
+        style="0"
+      />
+    </span>
+  </Tooltip>
 </div>
 `;
index 84dc737ae03645c6cb795aa1d788c6097608aa65..1c660cd406e745ed848c7da5d9b106b7bcf44998 100644 (file)
@@ -30,3 +30,14 @@ exports[`should render correctly an actionable legend 1`] = `
   </a>
 </span>
 `;
+
+exports[`should render correctly legends with warning 1`] = `
+<span
+  className="alert-warning myclass"
+>
+  <AlertWarnIcon
+    className="spacer-right"
+  />
+  Foo
+</span>
+`;
index 4bec19f63e2207edfaef367ee59b6e1ed627f46b..a42cbc13f2e87c58cba906167597ed81d992ec50 100644 (file)
 
 .project-activity-graph-legend-actionable {
   padding: 4px 12px;
-  border: 1px solid #e6e6e6;
+  border-width: 1px;
+  border-style: solid;
   border-radius: 12px;
 }
 
+.project-activity-graph-legend-actionable:not(.alert-warning) {
+  border-color: #e6e6e6;
+}
+
 .project-activity-graph-tooltip {
   padding: 8px;
   pointer-events: none;
index 6ea4c782138e9eb397055a33e6eefa6a3d681d80..83f5960b6e77b6a4e135976ca4ea1bf116033e5d 100644 (file)
@@ -65,6 +65,8 @@ export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =
   return previousFrom !== nextFrom || previousTo !== nextTo;
 };
 
+export const hasDataValues = (serie: Serie) => serie.data.some(point => point.y || point.y === 0);
+
 export const hasHistoryData = (series: Array<Serie>) =>
   series.some(serie => serie.data && serie.data.length > 2);
 
diff --git a/server/sonar-web/src/main/js/components/icons-components/AlertWarnIcon.js b/server/sonar-web/src/main/js/components/icons-components/AlertWarnIcon.js
new file mode 100644 (file)
index 0000000..3ecabfa
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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';
+
+type Props = { className?: string, size?: number };
+
+export default function AlertWarnIcon({ className, size = 16 }: Props) {
+  /* eslint-disable max-len */
+  return (
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
+      className={className}
+      height={size}
+      width={size}
+      viewBox="0 0 16 16">
+      <path
+        style={{ fill: '#ed7d20' }}
+        d="M8 1.143q1.866 0 3.442.92t2.496 2.496.92 3.442-.92 3.442-2.496 2.496-3.442.92-3.442-.92-2.496-2.496-.92-3.442.92-3.442 2.496-2.496T8 1.143zm1.143 11.134v-1.696q0-.125-.08-.21t-.196-.085H7.153q-.116 0-.205.089t-.089.205v1.696q0 .116.089.205t.205.089h1.714q.116 0 .196-.085t.08-.21zm-.018-3.072l.161-5.545q0-.107-.089-.161-.089-.071-.214-.071H7.019q-.125 0-.214.071-.089.054-.089.161l.152 5.545q0 .089.089.156t.214.067h1.652q.125 0 .21-.067t.094-.156z"
+      />
+    </svg>
+  );
+}
index 62dedb134193886207ee3f2a618baac478d0284d..6b132ffad972839103bb3de66823a86f07d40ccb 100644 (file)
@@ -1292,7 +1292,8 @@ project_activity.graphs.custom=Custom
 project_activity.graphs.custom.add=Add metric
 project_activity.graphs.custom.add_metric=Add a metric
 project_activity.graphs.custom.add_metric_info=Only 3 metrics of the same type can be displayed on the graph.
-project_activity.graphs.custom.no_history=There is no historical data to show, please add more metrics to your graph.
+project_activity.graphs.custom.no_history=There is no historical data to display, please add more metrics to your graph.
+project_activity.graphs.custom.metric_no_history=This metric has no historical data to display.
 project_activity.graphs.custom.search=Search for a metric by name
 project_activity.graphs.custom.type_x_message=Only "{0}" metrics are available with your current selection.