@@ -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> | |||
); | |||
})} |
@@ -17,15 +17,18 @@ | |||
* 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}> |
@@ -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: [] } | |||
]; | |||
@@ -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(); | |||
}); |
@@ -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> | |||
`; |
@@ -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> | |||
`; |
@@ -65,10 +65,15 @@ | |||
.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; |
@@ -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); | |||
@@ -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> | |||
); | |||
} |
@@ -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. | |||