@@ -272,6 +272,20 @@ | |||
margin-top: -30px; | |||
} | |||
.overview-panel .activity-graph-new-code-legend { | |||
position: relative; | |||
z-index: var(--aboveNormalZIndex); | |||
width: 12px; | |||
overflow: hidden; | |||
margin-top: 1px; | |||
margin-left: calc(2 * var(--gridSize)); | |||
text-indent: -9999px; | |||
} | |||
.overview-panel .activity-graph-new-code-legend::after { | |||
margin: 0; | |||
} | |||
.overview-analysis { | |||
color: var(--secondFontColor); | |||
} |
@@ -85,10 +85,15 @@ export default class GraphHistory extends React.PureComponent<Props, State> { | |||
return ( | |||
<div className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"> | |||
{isCustom && this.props.removeCustomMetric ? ( | |||
<GraphsLegendCustom removeMetric={this.props.removeCustomMetric} series={series} /> | |||
<GraphsLegendCustom | |||
removeMetric={this.props.removeCustomMetric} | |||
series={series} | |||
showLeakLegend={Boolean(leakPeriodDate)} | |||
/> | |||
) : ( | |||
<GraphsLegendStatic series={series} /> | |||
<GraphsLegendStatic series={series} showLeakLegend={Boolean(leakPeriodDate)} /> | |||
)} | |||
<div className="flex-1"> | |||
<AutoSizer> | |||
{({ height, width }) => ( |
@@ -22,42 +22,49 @@ import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { Serie } from '../../types/project-activity'; | |||
import GraphsLegendItem from './GraphsLegendItem'; | |||
import GraphsLegendNewCode from './GraphsLegendNewCode'; | |||
import { hasDataValues } from './utils'; | |||
interface Props { | |||
export interface GraphsLegendCustomProps { | |||
removeMetric: (metric: string) => void; | |||
series: Serie[]; | |||
showLeakLegend: boolean; | |||
} | |||
export default function GraphsLegendCustom({ removeMetric, series }: Props) { | |||
export default function GraphsLegendCustom(props: GraphsLegendCustomProps) { | |||
const { series, showLeakLegend } = props; | |||
return ( | |||
<div className="activity-graph-legends"> | |||
{series.map((serie, idx) => { | |||
const hasData = hasDataValues(serie); | |||
const legendItem = ( | |||
<GraphsLegendItem | |||
index={idx} | |||
metric={serie.name} | |||
name={serie.translatedName} | |||
removeMetric={removeMetric} | |||
showWarning={!hasData} | |||
/> | |||
); | |||
if (!hasData) { | |||
<div className="activity-graph-legends display-flex-center"> | |||
<div className="flex-1"> | |||
{series.map((serie, idx) => { | |||
const hasData = hasDataValues(serie); | |||
const legendItem = ( | |||
<GraphsLegendItem | |||
index={idx} | |||
metric={serie.name} | |||
name={serie.translatedName} | |||
removeMetric={props.removeMetric} | |||
showWarning={!hasData} | |||
/> | |||
); | |||
if (!hasData) { | |||
return ( | |||
<Tooltip | |||
key={serie.name} | |||
overlay={translate('project_activity.graphs.custom.metric_no_history')}> | |||
<span className="spacer-left spacer-right">{legendItem}</span> | |||
</Tooltip> | |||
); | |||
} | |||
return ( | |||
<Tooltip | |||
key={serie.name} | |||
overlay={translate('project_activity.graphs.custom.metric_no_history')}> | |||
<span className="spacer-left spacer-right">{legendItem}</span> | |||
</Tooltip> | |||
<span className="spacer-left spacer-right" key={serie.name}> | |||
{legendItem} | |||
</span> | |||
); | |||
} | |||
return ( | |||
<span className="spacer-left spacer-right" key={serie.name}> | |||
{legendItem} | |||
</span> | |||
); | |||
})} | |||
})} | |||
</div> | |||
{showLeakLegend && <GraphsLegendNewCode />} | |||
</div> | |||
); | |||
} |
@@ -0,0 +1,34 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 'sonar-ui-common/components/controls/Tooltip'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
export default function GraphsLegendNewCode() { | |||
return ( | |||
<Tooltip overlay={translate('project_activity.graphs.new_code_long')}> | |||
<span | |||
aria-label={translate('project_activity.graphs.new_code_long')} | |||
className="activity-graph-new-code-legend display-flex-center pull-right note"> | |||
{translate('project_activity.graphs.new_code')} | |||
</span> | |||
</Tooltip> | |||
); | |||
} |
@@ -20,12 +20,14 @@ | |||
import * as React from 'react'; | |||
import { Serie } from '../../types/project-activity'; | |||
import GraphsLegendItem from './GraphsLegendItem'; | |||
import GraphsLegendNewCode from './GraphsLegendNewCode'; | |||
interface Props { | |||
export interface GraphsLegendStaticProps { | |||
series: Array<Pick<Serie, 'name' | 'translatedName'>>; | |||
showLeakLegend: boolean; | |||
} | |||
export default function GraphsLegendStatic({ series }: Props) { | |||
export default function GraphsLegendStatic({ series, showLeakLegend }: GraphsLegendStaticProps) { | |||
return ( | |||
<div className="activity-graph-legends"> | |||
{series.map((serie, idx) => ( | |||
@@ -37,6 +39,8 @@ export default function GraphsLegendStatic({ series }: Props) { | |||
name={serie.translatedName} | |||
/> | |||
))} | |||
{showLeakLegend && <GraphsLegendNewCode />} | |||
</div> | |||
); | |||
} |
@@ -20,29 +20,39 @@ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { parseDate } from 'sonar-ui-common/helpers/dates'; | |||
import GraphsLegendCustom from '../GraphsLegendCustom'; | |||
import GraphsLegendCustom, { GraphsLegendCustomProps } from '../GraphsLegendCustom'; | |||
const SERIES = [ | |||
{ | |||
name: 'bugs', | |||
translatedName: 'Bugs', | |||
data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }], | |||
type: 'INT' | |||
}, | |||
{ | |||
name: 'my_metric', | |||
translatedName: 'My Metric', | |||
data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }], | |||
type: 'INT' | |||
}, | |||
{ | |||
name: 'foo', | |||
translatedName: 'Foo', | |||
data: [], | |||
type: 'INT' | |||
} | |||
]; | |||
it('should render correctly the list of series', () => { | |||
expect(shallow(<GraphsLegendCustom removeMetric={() => {}} series={SERIES} />)).toMatchSnapshot(); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ showLeakLegend: true })).toMatchSnapshot('with leak legend'); | |||
}); | |||
function shallowRender(props: Partial<GraphsLegendCustomProps> = {}) { | |||
return shallow<GraphsLegendCustomProps>( | |||
<GraphsLegendCustom | |||
removeMetric={jest.fn()} | |||
series={[ | |||
{ | |||
name: 'bugs', | |||
translatedName: 'Bugs', | |||
data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }], | |||
type: 'INT' | |||
}, | |||
{ | |||
name: 'my_metric', | |||
translatedName: 'My Metric', | |||
data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }], | |||
type: 'INT' | |||
}, | |||
{ | |||
name: 'foo', | |||
translatedName: 'Foo', | |||
data: [], | |||
type: 'INT' | |||
} | |||
]} | |||
showLeakLegend={false} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,31 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import GraphsLegendNewCode from '../GraphsLegendNewCode'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
}); | |||
function shallowRender() { | |||
return shallow(<GraphsLegendNewCode />); | |||
} |
@@ -19,13 +19,22 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import GraphsLegendStatic from '../GraphsLegendStatic'; | |||
import GraphsLegendStatic, { GraphsLegendStaticProps } from '../GraphsLegendStatic'; | |||
const SERIES = [ | |||
{ name: 'bugs', translatedName: 'Bugs', data: [] }, | |||
{ name: 'code_smells', translatedName: 'Code Smells', data: [] } | |||
]; | |||
it('should render correctly the list of series', () => { | |||
expect(shallow(<GraphsLegendStatic series={SERIES} />)).toMatchSnapshot(); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ showLeakLegend: true })).toMatchSnapshot('with leak legend'); | |||
}); | |||
function shallowRender(props: Partial<GraphsLegendStaticProps> = {}) { | |||
return shallow<GraphsLegendStaticProps>( | |||
<GraphsLegendStatic | |||
series={[ | |||
{ name: 'bugs', translatedName: 'Bugs' }, | |||
{ name: 'code_smells', translatedName: 'Code Smells' } | |||
]} | |||
showLeakLegend={false} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -28,6 +28,7 @@ exports[`should correctly render a graph 1`] = ` | |||
}, | |||
] | |||
} | |||
showLeakLegend={true} | |||
/> | |||
<div | |||
className="flex-1" |
@@ -1,48 +1,104 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly the list of series 1`] = ` | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="activity-graph-legends" | |||
className="activity-graph-legends display-flex-center" | |||
> | |||
<span | |||
className="spacer-left spacer-right" | |||
key="bugs" | |||
<div | |||
className="flex-1" | |||
> | |||
<GraphsLegendItem | |||
index={0} | |||
metric="bugs" | |||
name="Bugs" | |||
removeMetric={[Function]} | |||
showWarning={false} | |||
/> | |||
</span> | |||
<span | |||
className="spacer-left spacer-right" | |||
key="my_metric" | |||
> | |||
<GraphsLegendItem | |||
index={1} | |||
metric="my_metric" | |||
name="My Metric" | |||
removeMetric={[Function]} | |||
showWarning={false} | |||
/> | |||
</span> | |||
<Tooltip | |||
key="foo" | |||
overlay="project_activity.graphs.custom.metric_no_history" | |||
<span | |||
className="spacer-left spacer-right" | |||
key="bugs" | |||
> | |||
<GraphsLegendItem | |||
index={0} | |||
metric="bugs" | |||
name="Bugs" | |||
removeMetric={[MockFunction]} | |||
showWarning={false} | |||
/> | |||
</span> | |||
<span | |||
className="spacer-left spacer-right" | |||
key="my_metric" | |||
> | |||
<GraphsLegendItem | |||
index={1} | |||
metric="my_metric" | |||
name="My Metric" | |||
removeMetric={[MockFunction]} | |||
showWarning={false} | |||
/> | |||
</span> | |||
<Tooltip | |||
key="foo" | |||
overlay="project_activity.graphs.custom.metric_no_history" | |||
> | |||
<span | |||
className="spacer-left spacer-right" | |||
> | |||
<GraphsLegendItem | |||
index={2} | |||
metric="foo" | |||
name="Foo" | |||
removeMetric={[MockFunction]} | |||
showWarning={true} | |||
/> | |||
</span> | |||
</Tooltip> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: with leak legend 1`] = ` | |||
<div | |||
className="activity-graph-legends display-flex-center" | |||
> | |||
<div | |||
className="flex-1" | |||
> | |||
<span | |||
className="spacer-left spacer-right" | |||
key="bugs" | |||
> | |||
<GraphsLegendItem | |||
index={2} | |||
metric="foo" | |||
name="Foo" | |||
removeMetric={[Function]} | |||
showWarning={true} | |||
index={0} | |||
metric="bugs" | |||
name="Bugs" | |||
removeMetric={[MockFunction]} | |||
showWarning={false} | |||
/> | |||
</span> | |||
</Tooltip> | |||
<span | |||
className="spacer-left spacer-right" | |||
key="my_metric" | |||
> | |||
<GraphsLegendItem | |||
index={1} | |||
metric="my_metric" | |||
name="My Metric" | |||
removeMetric={[MockFunction]} | |||
showWarning={false} | |||
/> | |||
</span> | |||
<Tooltip | |||
key="foo" | |||
overlay="project_activity.graphs.custom.metric_no_history" | |||
> | |||
<span | |||
className="spacer-left spacer-right" | |||
> | |||
<GraphsLegendItem | |||
index={2} | |||
metric="foo" | |||
name="Foo" | |||
removeMetric={[MockFunction]} | |||
showWarning={true} | |||
/> | |||
</span> | |||
</Tooltip> | |||
</div> | |||
<GraphsLegendNewCode /> | |||
</div> | |||
`; |
@@ -0,0 +1,14 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Tooltip | |||
overlay="project_activity.graphs.new_code_long" | |||
> | |||
<span | |||
aria-label="project_activity.graphs.new_code_long" | |||
className="activity-graph-new-code-legend display-flex-center pull-right note" | |||
> | |||
project_activity.graphs.new_code | |||
</span> | |||
</Tooltip> | |||
`; |
@@ -1,6 +1,6 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly the list of series 1`] = ` | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="activity-graph-legends" | |||
> | |||
@@ -20,3 +20,25 @@ exports[`should render correctly the list of series 1`] = ` | |||
/> | |||
</div> | |||
`; | |||
exports[`should render correctly: with leak legend 1`] = ` | |||
<div | |||
className="activity-graph-legends" | |||
> | |||
<GraphsLegendItem | |||
className="big-spacer-left big-spacer-right" | |||
index={0} | |||
key="bugs" | |||
metric="bugs" | |||
name="Bugs" | |||
/> | |||
<GraphsLegendItem | |||
className="big-spacer-left big-spacer-right" | |||
index={1} | |||
key="code_smells" | |||
metric="code_smells" | |||
name="Code Smells" | |||
/> | |||
<GraphsLegendNewCode /> | |||
</div> | |||
`; |
@@ -67,3 +67,17 @@ | |||
border-style: solid; | |||
border-radius: calc(1.5 * var(--gridSize)); | |||
} | |||
.activity-graph-new-code-legend { | |||
margin-right: 10px; /* padding of activity graph */ | |||
} | |||
.activity-graph-new-code-legend::after { | |||
content: ''; | |||
display: inline-block; | |||
margin-left: calc(var(--gridSize) / 2); | |||
width: var(--gridSize); | |||
height: var(--gridSize); | |||
background-color: var(--leakPrimaryColor); | |||
border: 2px solid var(--leakSecondaryColor); | |||
} |
@@ -1301,6 +1301,8 @@ project_activity.events.tooltip.delete=Delete this event | |||
project_activity.new_code_period_start=New Code Period starts here | |||
project_activity.new_code_period_start.help=The analysis before this mark is the baseline for New Code comparison | |||
project_activity.graphs.new_code=New Code | |||
project_activity.graphs.new_code_long=New Code is indicated in yellow on the graph. | |||
project_activity.graphs.issues=Issues | |||
project_activity.graphs.coverage=Coverage | |||
project_activity.graphs.duplications=Duplications |