]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9403 Allow to remove a metric fromt the custom graph
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 6 Jul 2017 13:28:53 +0000 (15:28 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 13 Jul 2017 12:34:17 +0000 (14:34 +0200)
18 files changed:
server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js
server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendItem-test.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityGraphs-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendItem-test.js.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js
server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css

index 36ac11b372ec270ca073b966809a339d3a6cf5a4..130bf03e018a94006d6ca9309a9ba3bd2d5702fa 100644 (file)
@@ -23,11 +23,12 @@ import { sortBy } from 'lodash';
 import { AutoSizer } from 'react-virtualized';
 import AdvancedTimeline from '../../../components/charts/AdvancedTimeline';
 import GraphsTooltips from './GraphsTooltips';
+import GraphsLegendCustom from './GraphsLegendCustom';
 import GraphsLegendStatic from './GraphsLegendStatic';
 import { formatMeasure, getShortType } from '../../../helpers/measures';
 import { EVENT_TYPES, hasHistoryData, isCustomGraph } from '../utils';
 import { translate } from '../../../helpers/l10n';
-import type { Analysis, MeasureHistory } from '../types';
+import type { Analysis, MeasureHistory, Metric } from '../types';
 import type { Serie } from '../../../components/charts/AdvancedTimeline';
 
 type Props = {
@@ -39,7 +40,9 @@ type Props = {
   leakPeriodDate: Date,
   loading: boolean,
   measuresHistory: Array<MeasureHistory>,
+  metrics: Array<Metric>,
   metricsType: string,
+  removeCustomMetric: (metric: string) => void,
   selectedDate?: ?Date => void,
   series: Array<Serie>,
   updateGraphZoom: (from: ?Date, to: ?Date) => void,
@@ -105,6 +108,7 @@ export default class GraphsHistory extends React.PureComponent {
   render() {
     const { loading } = this.props;
     const { graph, series } = this.props;
+    const isCustom = isCustomGraph(graph);
 
     if (loading) {
       return (
@@ -121,7 +125,7 @@ export default class GraphsHistory extends React.PureComponent {
         <div className="project-activity-graph-container">
           <div className="note text-center">
             {translate(
-              isCustomGraph(this.props.graph)
+              isCustom
                 ? 'project_activity.graphs.custom.no_history'
                 : 'component_measures.no_history'
             )}
@@ -133,7 +137,13 @@ export default class GraphsHistory extends React.PureComponent {
     const { selectedDate, tooltipIdx, tooltipXPos } = this.state;
     return (
       <div className="project-activity-graph-container">
-        <GraphsLegendStatic series={series} />
+        {isCustom
+          ? <GraphsLegendCustom
+              series={series}
+              metrics={this.props.metrics}
+              removeMetric={this.props.removeCustomMetric}
+            />
+          : <GraphsLegendStatic series={series} />}
         <div className="project-activity-graph">
           <AutoSizer>
             {({ height, width }) => (
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendCustom.js
new file mode 100644 (file)
index 0000000..e0236cb
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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 GraphsLegendItem from './GraphsLegendItem';
+import type { Metric } from '../types';
+
+type Props = {
+  metrics: Array<Metric>,
+  removeMetric: string => void,
+  series: Array<{ name: string, translatedName: string, style: string }>
+};
+
+export default function GraphsLegendCustom({ metrics, removeMetric, series }: Props) {
+  return (
+    <div className="project-activity-graph-legends">
+      {series.map(serie => {
+        const metric = metrics.find(metric => metric.key === serie.name);
+        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}
+            />
+          </span>
+        );
+      })}
+    </div>
+  );
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendItem.js
new file mode 100644 (file)
index 0000000..79d2945
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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 classNames from 'classnames';
+import CloseIcon from '../../../components/icons-components/CloseIcon';
+import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
+
+type Props = {
+  className?: string,
+  metric: string,
+  name: string,
+  style: string,
+  removeMetric?: string => void
+};
+
+export default class GraphsLegendItem extends React.PureComponent {
+  props: Props;
+
+  handleClick = (e: Event) => {
+    e.preventDefault();
+    this.props.removeMetric(this.props.metric);
+  };
+
+  render() {
+    const isActionable = this.props.removeMetric != null;
+    const legendClass = classNames(
+      {
+        'project-activity-graph-legend-actionable': isActionable
+      },
+      this.props.className
+    );
+
+    return (
+      <span className={legendClass}>
+        <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}>
+            <CloseIcon className="text-danger" />
+          </a>}
+      </span>
+    );
+  }
+}
index 7dffdf753213c23ef593e9b76cb1bdfac44d0886..123bc9548b71c9d433cfb5e36bfa67a78658b4da 100644 (file)
@@ -18,8 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import React from 'react';
-import classNames from 'classnames';
-import ChartLegendIcon from '../../../components/icons-components/ChartLegendIcon';
+import GraphsLegendItem from './GraphsLegendItem';
 
 type Props = {
   series: Array<{ name: string, translatedName: string, style: string }>
@@ -29,15 +28,13 @@ export default function GraphsLegendStatic({ series }: Props) {
   return (
     <div className="project-activity-graph-legends">
       {series.map(serie => (
-        <span className="big-spacer-left big-spacer-right" key={serie.name}>
-          <ChartLegendIcon
-            className={classNames(
-              'spacer-right line-chart-legend',
-              'line-chart-legend-' + serie.style
-            )}
-          />
-          {serie.translatedName}
-        </span>
+        <GraphsLegendItem
+          className="big-spacer-left big-spacer-right"
+          key={serie.name}
+          metric={serie.name}
+          name={serie.translatedName}
+          style={serie.style}
+        />
       ))}
     </div>
   );
index 02a1f1b5eb3cf49f9a372bf0462104302d76278f..d982bc837cc2c149e3db690005a31a33ca3b302a 100644 (file)
@@ -127,7 +127,6 @@ export default class ProjectActivityApp extends React.PureComponent {
               measuresHistory={measuresHistory}
               metrics={this.props.metrics}
               metricsType={this.getMetricType()}
-              project={this.props.project.key}
               query={query}
               updateQuery={this.props.updateQuery}
             />
index 0b86b71bdc5ea5bbe234ba8a614c5936e60e7033..13d42349be3a24deeba72a3719795e07386ca2ea 100644 (file)
@@ -40,7 +40,6 @@ type Props = {
   measuresHistory: Array<MeasureHistory>,
   metrics: Array<Metric>,
   metricsType: string,
-  project: string,
   query: Query,
   updateQuery: RawQuery => void
 };
@@ -108,7 +107,15 @@ export default class ProjectActivityGraphs extends React.PureComponent {
     }
   };
 
-  updateSelectedDate = (selectedDate: ?Date) => this.props.updateQuery({ selectedDate });
+  addCustomMetric = (metric: string) =>
+    this.props.updateQuery({ customMetrics: [...this.props.query.customMetrics, metric] });
+
+  removeCustomMetric = (removedMetric: string) =>
+    this.props.updateQuery({
+      customMetrics: this.props.query.customMetrics.filter(metric => metric !== removedMetric)
+    });
+
+  updateGraph = (graph: string) => this.props.updateQuery({ graph });
 
   updateGraphZoom = (graphStartDate: ?Date, graphEndDate: ?Date) => {
     if (graphEndDate != null && graphStartDate != null) {
@@ -123,6 +130,8 @@ export default class ProjectActivityGraphs extends React.PureComponent {
     this.updateQueryDateRange([graphStartDate, graphEndDate]);
   };
 
+  updateSelectedDate = (selectedDate: ?Date) => this.props.updateQuery({ selectedDate });
+
   updateQueryDateRange = (dates: Array<?Date>) => {
     if (dates[0] == null || dates[1] == null) {
       this.props.updateQuery({ from: dates[0], to: dates[1] });
@@ -133,15 +142,16 @@ export default class ProjectActivityGraphs extends React.PureComponent {
   };
 
   render() {
-    const { leakPeriodDate, loading, metricsType, query } = this.props;
+    const { leakPeriodDate, loading, metrics, metricsType, query } = this.props;
     const { series } = this.state;
     return (
       <div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner">
         <ProjectActivityGraphsHeader
+          addCustomMetric={this.addCustomMetric}
           graph={query.graph}
-          metrics={this.props.metrics}
+          metrics={metrics}
           selectedMetrics={this.props.query.customMetrics}
-          updateQuery={this.props.updateQuery}
+          updateGraph={this.updateGraph}
         />
         <GraphsHistory
           analyses={this.props.analyses}
@@ -152,8 +162,9 @@ export default class ProjectActivityGraphs extends React.PureComponent {
           leakPeriodDate={leakPeriodDate}
           loading={loading}
           measuresHistory={this.props.measuresHistory}
+          metrics={metrics}
           metricsType={metricsType}
-          project={this.props.project}
+          removeCustomMetric={this.removeCustomMetric}
           selectedDate={this.props.query.selectedDate}
           series={series}
           updateGraphZoom={this.updateGraphZoom}
index 194af091b6925e07fb63c296d1743d0c9fffa4ef..007b04f4d52b37c618264c84110da29364591efc 100644 (file)
@@ -24,13 +24,13 @@ import AddGraphMetric from './forms/AddGraphMetric';
 import { isCustomGraph, GRAPH_TYPES } from '../utils';
 import { translate } from '../../../helpers/l10n';
 import type { Metric } from '../types';
-import type { RawQuery } from '../../../helpers/query';
 
 type Props = {
+  addCustomMetric: string => void,
   graph: string,
   metrics: Array<Metric>,
   selectedMetrics: Array<string>,
-  updateQuery: RawQuery => void
+  updateGraph: string => void
 };
 
 export default class ProjectActivityGraphsHeader extends React.PureComponent {
@@ -38,15 +38,10 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent {
 
   handleGraphChange = (option: { value: string }) => {
     if (option.value !== this.props.graph) {
-      this.props.updateQuery({ graph: option.value });
+      this.props.updateGraph(option.value);
     }
   };
 
-  handleAddMetric = (metric: string) => {
-    const selectedMetrics = [...this.props.selectedMetrics, metric];
-    this.props.updateQuery({ customMetrics: selectedMetrics });
-  };
-
   render() {
     const selectOptions = GRAPH_TYPES.map(graph => ({
       label: translate('project_activity.graphs', graph),
@@ -65,7 +60,7 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent {
         />
         {isCustomGraph(this.props.graph) &&
           <AddGraphMetric
-            addMetric={this.handleAddMetric}
+            addMetric={this.props.addCustomMetric}
             className="spacer-left"
             metrics={this.props.metrics}
             selectedMetrics={this.props.selectedMetrics}
index 94d9d32634040fd74b9bac645d6c84a54e9f86f0..f13577ae57135d895a61acc128f472667df39ccb 100644 (file)
@@ -87,7 +87,9 @@ const DEFAULT_PROPS = {
   leakPeriodDate: '2017-05-16T13:50:02+0200',
   loading: false,
   measuresHistory: [],
+  metrics: [],
   metricsType: 'INT',
+  removeCustomMetric: () => {},
   selectedDate: null,
   series: SERIES,
   updateGraphZoom: () => {},
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendCustom-test.js
new file mode 100644 (file)
index 0000000..9b785f2
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import React from 'react';
+import { 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: 'foo', translatedName: 'Foo', style: '0', data: [] }
+];
+
+const METRICS = [
+  { key: 'bugs', name: 'Bugs' },
+  { key: 'my_metric', name: 'My Metric', custom: true }
+];
+
+it('should render correctly the list of series', () => {
+  expect(
+    shallow(<GraphsLegendCustom metrics={METRICS} removeMetric={() => {}} series={SERIES} />)
+  ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendItem-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendItem-test.js
new file mode 100644 (file)
index 0000000..a000a1d
--- /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.
+ */
+import React from 'react';
+import { shallow } from 'enzyme';
+import GraphsLegendItem from '../GraphsLegendItem';
+
+it('should render correctly a legend', () => {
+  expect(shallow(<GraphsLegendItem metric="bugs" name="Bugs" style="2" />)).toMatchSnapshot();
+});
+
+it('should render correctly an actionable legend', () => {
+  expect(
+    shallow(
+      <GraphsLegendItem
+        className="myclass"
+        metric="foo"
+        name="Foo"
+        style="1"
+        removeMetric={() => {}}
+      />
+    )
+  ).toMatchSnapshot();
+});
index 2b27845d57cfd267c11bd790a5efda575859fddd..07930d404634bb5038ada1223d182855222cbfa5 100644 (file)
@@ -71,7 +71,6 @@ const DEFAULT_PROPS = {
     }
   ],
   metricsType: 'INT',
-  project: 'org.sonarsource.sonarqube:sonarqube',
   query: { category: '', graph: 'overview', project: 'org.sonarsource.sonarqube:sonarqube' },
   updateQuery: () => {}
 };
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendCustom-test.js.snap
new file mode 100644 (file)
index 0000000..b19b8e8
--- /dev/null
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly the list of series 1`] = `
+<div
+  className="project-activity-graph-legends"
+>
+  <span
+    className="spacer-left spacer-right"
+  >
+    <GraphsLegendItem
+      metric="bugs"
+      name="Bugs"
+      removeMetric={[Function]}
+      style="2"
+    />
+  </span>
+  <span
+    className="spacer-left spacer-right"
+  >
+    <GraphsLegendItem
+      metric="my_metric"
+      name="My Metric"
+      removeMetric={[Function]}
+      style="1"
+    />
+  </span>
+  <span
+    className="spacer-left spacer-right"
+  >
+    <GraphsLegendItem
+      metric="foo"
+      name="Foo"
+      removeMetric={[Function]}
+      style="0"
+    />
+  </span>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendItem-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendItem-test.js.snap
new file mode 100644 (file)
index 0000000..84dc737
--- /dev/null
@@ -0,0 +1,32 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly a legend 1`] = `
+<span
+  className=""
+>
+  <ChartLegendIcon
+    className="spacer-right line-chart-legend line-chart-legend-2"
+  />
+  Bugs
+</span>
+`;
+
+exports[`should render correctly an actionable legend 1`] = `
+<span
+  className="project-activity-graph-legend-actionable myclass"
+>
+  <ChartLegendIcon
+    className="spacer-right line-chart-legend line-chart-legend-1"
+  />
+  Foo
+  <a
+    className="spacer-left button-clean text-text-top"
+    href="#"
+    onClick={[Function]}
+  >
+    <CloseIcon
+      className="text-danger"
+    />
+  </a>
+</span>
+`;
index 1fd564f69efab4b6e16797d569d411dd04dc621e..97610c6365ff7bf32c990d6333f9ad2f81926065 100644 (file)
@@ -4,21 +4,17 @@ exports[`should render correctly the list of series 1`] = `
 <div
   className="project-activity-graph-legends"
 >
-  <span
+  <GraphsLegendItem
     className="big-spacer-left big-spacer-right"
-  >
-    <ChartLegendIcon
-      className="spacer-right line-chart-legend line-chart-legend-2"
-    />
-    Bugs
-  </span>
-  <span
+    metric="bugs"
+    name="Bugs"
+    style="2"
+  />
+  <GraphsLegendItem
     className="big-spacer-left big-spacer-right"
-  >
-    <ChartLegendIcon
-      className="spacer-right line-chart-legend line-chart-legend-1"
-    />
-    Code Smells
-  </span>
+    metric="code_smells"
+    name="Code Smells"
+    style="1"
+  />
 </div>
 `;
index 1943a2f48ac36c7b9227e6552e63c2742c2270cd..7fa450f12c8cd451556cdb0086e5406b14202d70 100644 (file)
@@ -189,7 +189,6 @@ exports[`should render correctly 1`] = `
           ]
         }
         metricsType="INT"
-        project="org.sonarsource.sonarqube:sonarqube"
         query={
           Object {
             "category": "",
index 3c46221619f8cfa202e359efd55daddae0f14efa..a32cd28f590f81b4b6a7ab9df6071ca3c44fdac3 100644 (file)
@@ -5,8 +5,9 @@ exports[`should render correctly the graph and legends 1`] = `
   className="project-activity-layout-page-main-inner boxed-group boxed-group-inner"
 >
   <ProjectActivityGraphsHeader
+    addCustomMetric={[Function]}
     graph="overview"
-    updateQuery={[Function]}
+    updateGraph={[Function]}
   />
   <GraphsHistory
     analyses={
@@ -73,7 +74,7 @@ exports[`should render correctly the graph and legends 1`] = `
       ]
     }
     metricsType="INT"
-    project="org.sonarsource.sonarqube:sonarqube"
+    removeCustomMetric={[Function]}
     series={
       Array [
         Object {
index a7193b6209327670715c4a16821fb1d14dee6898..d0a435a97ce96beab2d560dfe388eec99d59c05c 100644 (file)
@@ -138,7 +138,10 @@ export default class AddGraphMetric extends React.PureComponent {
 
   render() {
     return (
-      <button className={this.props.className} onClick={this.openForm}>
+      <button
+        className={this.props.className}
+        onClick={this.openForm}
+        disabled={this.props.selectedMetrics.length >= 3}>
         {translate('project_activity.graphs.custom.add')}
         {this.state.open && this.renderModal()}
       </button>
index 35b512759920324939aa8f691e30869fc2705d1b..cc0b6d1bd7ad90edda695ecfe9c9d17ef30de537 100644 (file)
   text-align: center;
 }
 
+.project-activity-graph-legend-actionable {
+  padding: 4px 12px;
+  border: 1px solid #e6e6e6;
+  border-radius: 12px;
+}
+
 .project-activity-graph-tooltip {
   padding: 8px;
   pointer-events: none;