*/
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 = {
<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>
);
})}
* 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
};
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}>
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: [] }
];
)
).toMatchSnapshot();
});
+
+it('should render correctly legends with warning', () => {
+ expect(
+ shallow(
+ <GraphsLegendItem className="myclass" metric="foo" name="Foo" showWarning={true} style="1" />
+ )
+ ).toMatchSnapshot();
+});
metric="bugs"
name="Bugs"
removeMetric={[Function]}
+ showWarning={false}
style="2"
/>
</span>
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>
`;
</a>
</span>
`;
+
+exports[`should render correctly legends with warning 1`] = `
+<span
+ className="alert-warning myclass"
+>
+ <AlertWarnIcon
+ className="spacer-right"
+ />
+ Foo
+</span>
+`;
.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;
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);
--- /dev/null
+/*
+ * 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>
+ );
+}
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.