Browse Source

SONAR-9403 Create form to add custom metric on project activity graph

tags/6.5-RC1
Grégoire Aubert 6 years ago
parent
commit
cb00b707c0
24 changed files with 303 additions and 117 deletions
  1. 3
    1
      server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js
  2. 1
    0
      server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap
  3. 7
    9
      server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js
  4. 1
    1
      server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js
  5. 27
    33
      server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js
  6. 1
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
  7. 10
    4
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
  8. 19
    3
      server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js
  9. 6
    6
      server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js
  10. 2
    2
      server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js
  11. 1
    1
      server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap
  12. 0
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap
  13. 9
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
  14. 1
    1
      server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
  15. 147
    0
      server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js
  16. 2
    0
      server/sonar-web/src/main/js/apps/projectActivity/types.js
  17. 5
    0
      server/sonar-web/src/main/js/apps/projectActivity/utils.js
  18. 0
    13
      server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css
  19. 0
    1
      server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js
  20. 3
    5
      server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js
  21. 28
    36
      server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap
  22. 4
    1
      server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
  23. 23
    0
      server/sonar-web/src/main/less/components/react-select.less
  24. 3
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 3
- 1
server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js View File

@@ -98,7 +98,9 @@ export default class AddMemberForm extends React.PureComponent {
</div>
<footer className="modal-foot">
<div>
<button type="submit">{translate('organization.members.add_to_members')}</button>
<button type="submit" disabled={!this.state.selectedMember}>
{translate('organization.members.add_to_members')}
</button>
<button type="reset" className="button-link" onClick={this.closeForm}>
{translate('cancel')}
</button>

+ 1
- 0
server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap View File

@@ -61,6 +61,7 @@ exports[`should render and open the modal 2`] = `
>
<div>
<button
disabled={true}
type="submit"
>
organization.members.add_to_members

server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js → server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js View File

@@ -19,13 +19,13 @@
*/
import React from 'react';
import moment from 'moment';
import { some, sortBy } from 'lodash';
import { sortBy } from 'lodash';
import { AutoSizer } from 'react-virtualized';
import AdvancedTimeline from '../../../components/charts/AdvancedTimeline';
import GraphsTooltips from './GraphsTooltips';
import StaticGraphsLegend from './StaticGraphsLegend';
import GraphsLegendStatic from './GraphsLegendStatic';
import { formatMeasure, getShortType } from '../../../helpers/measures';
import { EVENT_TYPES, isCustomGraph } from '../utils';
import { EVENT_TYPES, hasHistoryData, isCustomGraph } from '../utils';
import { translate } from '../../../helpers/l10n';
import type { Analysis, MeasureHistory } from '../types';
import type { Serie } from '../../../components/charts/AdvancedTimeline';
@@ -52,7 +52,7 @@ type State = {
tooltipXPos: ?number
};

export default class StaticGraphs extends React.PureComponent {
export default class GraphsHistory extends React.PureComponent {
props: Props;
state: State = {
tooltipIdx: null,
@@ -99,13 +99,12 @@ export default class StaticGraphs extends React.PureComponent {
return [];
};

hasSeriesData = () => some(this.props.series, serie => serie.data && serie.data.length > 2);

updateTooltip = (selectedDate: ?Date, tooltipXPos: ?number, tooltipIdx: ?number) =>
this.setState({ selectedDate, tooltipXPos, tooltipIdx });

render() {
const { loading } = this.props;
const { graph, series } = this.props;

if (loading) {
return (
@@ -117,7 +116,7 @@ export default class StaticGraphs extends React.PureComponent {
);
}

if (!this.hasSeriesData()) {
if (!hasHistoryData(series)) {
return (
<div className="project-activity-graph-container">
<div className="note text-center">
@@ -132,10 +131,9 @@ export default class StaticGraphs extends React.PureComponent {
}

const { selectedDate, tooltipIdx, tooltipXPos } = this.state;
const { graph, series } = this.props;
return (
<div className="project-activity-graph-container">
<StaticGraphsLegend series={series} />
<GraphsLegendStatic series={series} />
<div className="project-activity-graph">
<AutoSizer>
{({ height, width }) => (

server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphsLegend.js → server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js View File

@@ -25,7 +25,7 @@ type Props = {
series: Array<{ name: string, translatedName: string, style: string }>
};

export default function StaticGraphsLegend({ series }: Props) {
export default function GraphsLegendStatic({ series }: Props) {
return (
<div className="project-activity-graph-legends">
{series.map(serie => (

+ 27
- 33
server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js View File

@@ -19,9 +19,9 @@
*/
// @flow
import React from 'react';
import { some } from 'lodash';
import { AutoSizer } from 'react-virtualized';
import ZoomTimeLine from '../../../components/charts/ZoomTimeLine';
import { hasHistoryData } from '../utils';
import type { Serie } from '../../../components/charts/AdvancedTimeline';

type Props = {
@@ -35,37 +35,31 @@ type Props = {
updateGraphZoom: (from: ?Date, to: ?Date) => void
};

export default class GraphsZoom extends React.PureComponent {
props: Props;

hasHistoryData = () => some(this.props.series, serie => serie.data && serie.data.length > 2);

render() {
const { loading } = this.props;
if (loading || !this.hasHistoryData()) {
return null;
}

return (
<div className="project-activity-graph-zoom">
<AutoSizer disableHeight={true}>
{({ width }) => (
<ZoomTimeLine
endDate={this.props.graphEndDate}
height={64}
width={width}
interpolate="linear"
leakPeriodDate={this.props.leakPeriodDate}
metricType={this.props.metricsType}
padding={[0, 10, 18, 60]}
series={this.props.series}
showAreas={this.props.showAreas}
startDate={this.props.graphStartDate}
updateZoom={this.props.updateGraphZoom}
/>
)}
</AutoSizer>
</div>
);
export default function GraphsZoom(props: Props) {
const { loading } = props;
if (loading || !hasHistoryData(props.series)) {
return null;
}

return (
<div className="project-activity-graph-zoom">
<AutoSizer disableHeight={true}>
{({ width }) => (
<ZoomTimeLine
endDate={props.graphEndDate}
height={64}
width={width}
interpolate="linear"
leakPeriodDate={props.leakPeriodDate}
metricType={props.metricsType}
padding={[0, 10, 18, 60]}
series={props.series}
showAreas={props.showAreas}
startDate={props.graphStartDate}
updateZoom={props.updateGraphZoom}
/>
)}
</AutoSizer>
</div>
);
}

+ 1
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js View File

@@ -125,6 +125,7 @@ export default class ProjectActivityApp extends React.PureComponent {
leakPeriodDate={moment(this.props.project.leakPeriodDate).toDate()}
loading={this.props.graphLoading}
measuresHistory={measuresHistory}
metrics={this.props.metrics}
metricsType={this.getMetricType()}
project={this.props.project.key}
query={query}

+ 10
- 4
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js View File

@@ -22,7 +22,7 @@ import React from 'react';
import { debounce, findLast, maxBy, minBy, sortBy } from 'lodash';
import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader';
import GraphsZoom from './GraphsZoom';
import StaticGraphs from './StaticGraphs';
import GraphsHistory from './GraphsHistory';
import {
datesQueryChanged,
generateSeries,
@@ -30,7 +30,7 @@ import {
historyQueryChanged
} from '../utils';
import type { RawQuery } from '../../../helpers/query';
import type { Analysis, MeasureHistory, Query } from '../types';
import type { Analysis, MeasureHistory, Metric, Query } from '../types';
import type { Serie } from '../../../components/charts/AdvancedTimeline';

type Props = {
@@ -38,6 +38,7 @@ type Props = {
leakPeriodDate: Date,
loading: boolean,
measuresHistory: Array<MeasureHistory>,
metrics: Array<Metric>,
metricsType: string,
project: string,
query: Query,
@@ -136,8 +137,13 @@ export default class ProjectActivityGraphs extends React.PureComponent {
const { series } = this.state;
return (
<div className="project-activity-layout-page-main-inner boxed-group boxed-group-inner">
<ProjectActivityGraphsHeader graph={query.graph} updateQuery={this.props.updateQuery} />
<StaticGraphs
<ProjectActivityGraphsHeader
graph={query.graph}
metrics={this.props.metrics}
selectedMetrics={this.props.query.customMetrics}
updateQuery={this.props.updateQuery}
/>
<GraphsHistory
analyses={this.props.analyses}
eventFilter={query.category}
graph={query.graph}

+ 19
- 3
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js View File

@@ -20,13 +20,17 @@
// @flow
import React from 'react';
import Select from 'react-select';
import { GRAPH_TYPES } from '../utils';
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 = {
updateQuery: RawQuery => void,
graph: string
graph: string,
metrics: Array<Metric>,
selectedMetrics: Array<string>,
updateQuery: RawQuery => void
};

export default class ProjectActivityGraphsHeader extends React.PureComponent {
@@ -38,6 +42,11 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent {
}
};

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),
@@ -54,6 +63,13 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent {
options={selectOptions}
onChange={this.handleGraphChange}
/>
{isCustomGraph(this.props.graph) &&
<AddGraphMetric
addMetric={this.handleAddMetric}
className="spacer-left"
metrics={this.props.metrics}
selectedMetrics={this.props.selectedMetrics}
/>}
</header>
);
}

server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js → server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js View File

@@ -19,7 +19,7 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import StaticGraphs from '../StaticGraphs';
import GraphsHistory from '../GraphsHistory';

const ANALYSES = [
{
@@ -95,20 +95,20 @@ const DEFAULT_PROPS = {
};

it('should show a loading view', () => {
expect(shallow(<StaticGraphs {...DEFAULT_PROPS} loading={true} />)).toMatchSnapshot();
expect(shallow(<GraphsHistory {...DEFAULT_PROPS} loading={true} />)).toMatchSnapshot();
});

it('should show that there is no data', () => {
expect(shallow(<StaticGraphs {...DEFAULT_PROPS} series={EMPTY_SERIES} />)).toMatchSnapshot();
expect(shallow(<GraphsHistory {...DEFAULT_PROPS} series={EMPTY_SERIES} />)).toMatchSnapshot();
});

it('should correctly render a graph', () => {
expect(shallow(<StaticGraphs {...DEFAULT_PROPS} />)).toMatchSnapshot();
expect(shallow(<GraphsHistory {...DEFAULT_PROPS} />)).toMatchSnapshot();
});

it('should correctly filter events', () => {
expect(shallow(<StaticGraphs {...DEFAULT_PROPS} />).instance().getEvents()).toMatchSnapshot();
expect(shallow(<GraphsHistory {...DEFAULT_PROPS} />).instance().getEvents()).toMatchSnapshot();
expect(
shallow(<StaticGraphs {...DEFAULT_PROPS} eventFilter="OTHER" />).instance().getEvents()
shallow(<GraphsHistory {...DEFAULT_PROPS} eventFilter="OTHER" />).instance().getEvents()
).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphsLegend-test.js → server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js View File

@@ -19,7 +19,7 @@
*/
import React from 'react';
import { shallow } from 'enzyme';
import StaticGraphsLegend from '../StaticGraphsLegend';
import GraphsLegendStatic from '../GraphsLegendStatic';

const SERIES = [
{ name: 'bugs', translatedName: 'Bugs', style: '2', data: [] },
@@ -27,5 +27,5 @@ const SERIES = [
];

it('should render correctly the list of series', () => {
expect(shallow(<StaticGraphsLegend series={SERIES} />)).toMatchSnapshot();
expect(shallow(<GraphsLegendStatic series={SERIES} />)).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphs-test.js.snap → server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap View File

@@ -29,7 +29,7 @@ exports[`should correctly render a graph 1`] = `
<div
className="project-activity-graph-container"
>
<StaticGraphsLegend
<GraphsLegendStatic
series={
Array [
Object {

server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphsLegend-test.js.snap → server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap View File


+ 9
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap View File

@@ -179,6 +179,15 @@ exports[`should render correctly 1`] = `
},
]
}
metrics={
Array [
Object {
"key": "code_smells",
"name": "Code Smells",
"type": "INT",
},
]
}
metricsType="INT"
project="org.sonarsource.sonarqube:sonarqube"
query={

+ 1
- 1
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap View File

@@ -8,7 +8,7 @@ exports[`should render correctly the graph and legends 1`] = `
graph="overview"
updateQuery={[Function]}
/>
<StaticGraphs
<GraphsHistory
analyses={
Array [
Object {

+ 147
- 0
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js View File

@@ -0,0 +1,147 @@
/*
* 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';
import Modal from 'react-modal';
import Select from 'react-select';
import { translate } from '../../../../helpers/l10n';
import type { Metric } from '../../types';

type Props = {
addMetric: (metric: string) => void,
className?: string,
metrics: Array<Metric>,
selectedMetrics: Array<string>
};

type State = {
open: boolean,
selectedMetric?: string
};

export default class AddGraphMetric extends React.PureComponent {
props: Props;
state: State = {
open: false
};

getMetricsType = () => {
if (this.props.selectedMetrics.length > 0) {
const metric = this.props.metrics.find(
metric => metric.key === this.props.selectedMetrics[0]
);
return metric && metric.type;
}
};

getMetricsOptions = () => {
const selectedType = this.getMetricsType();
return this.props.metrics
.filter(metric => {
if (metric.hidden) {
return false;
}
if (selectedType) {
return selectedType === metric.type && !this.props.selectedMetrics.includes(metric.key);
}
return true;
})
.map((metric: Metric) => ({
value: metric.key,
label: metric.custom ? metric.name : translate('metric', metric.key, 'name')
}));
};

openForm = () => {
this.setState({
open: true
});
};

closeForm = () => {
this.setState({
open: false,
selectedMetric: undefined
});
};

handleChange = (option: { value: string, label: string }) =>
this.setState({ selectedMetric: option.value });

handleSubmit = (e: Object) => {
e.preventDefault();
if (this.state.selectedMetric) {
this.props.addMetric(this.state.selectedMetric);
this.closeForm();
}
};

renderModal() {
return (
<Modal
isOpen={true}
contentLabel="graph metric add"
className="modal"
overlayClassName="modal-overlay"
onRequestClose={this.closeForm}>
<header className="modal-head">
<h2>{translate('project_activity.graphs.custom.add_metric')}</h2>
</header>
<form onSubmit={this.handleSubmit}>
<div className="modal-body">
<div className="modal-large-field">
<label>{translate('project_activity.graphs.custom.search')}</label>
<Select
autofocus={true}
className="Select-big"
clearable={false}
noResultsText={translate('no_results')}
onChange={this.handleChange}
options={this.getMetricsOptions()}
placeholder=""
searchable={true}
value={this.state.selectedMetric}
/>
</div>
</div>
<footer className="modal-foot">
<div>
<button type="submit" disabled={!this.state.selectedMetric}>
{translate('project_activity.graphs.custom.add')}
</button>
<button type="reset" className="button-link" onClick={this.closeForm}>
{translate('cancel')}
</button>
</div>
</footer>
</form>
</Modal>
);
}

render() {
return (
<button className={this.props.className} onClick={this.openForm}>
{translate('project_activity.graphs.custom.add')}
{this.state.open && this.renderModal()}
</button>
);
}
}

+ 2
- 0
server/sonar-web/src/main/js/apps/projectActivity/types.js View File

@@ -37,6 +37,8 @@ export type HistoryItem = { date: Date, value: string };
export type MeasureHistory = { metric: string, history: Array<HistoryItem> };

export type Metric = {
custom?: boolean,
hidden?: boolean,
key: string,
name: string,
type: string

+ 5
- 0
server/sonar-web/src/main/js/apps/projectActivity/utils.js View File

@@ -65,6 +65,11 @@ export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =
return previousFrom !== nextFrom || previousTo !== nextTo;
};

export const hasHistoryData = (series: Array<Serie>) =>
series.some(
serie => serie.data && serie.data.length > 2 && serie.data.some(p => p.y || p.y === 0)
);

export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
prevQuery.graph !== nextQuery.graph;


+ 0
- 13
server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css View File

@@ -1,13 +0,0 @@
.Select-big .Select-control {
padding-top: 4px;
padding-bottom: 4px;
}

.Select-big .Select-placeholder {
margin-top: 4px;
margin-bottom: 4px;
}

.Select-big .Select-value-label {
margin-top: 5px;
}

+ 0
- 1
server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js View File

@@ -24,7 +24,6 @@ import { debounce } from 'lodash';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import UsersSelectSearchOption from './UsersSelectSearchOption';
import UsersSelectSearchValue from './UsersSelectSearchValue';
import './UsersSelectSearch.css';

export type Option = {
login: string,

+ 3
- 5
server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js View File

@@ -62,11 +62,9 @@ export default class UsersSelectSearchOption extends React.PureComponent {
onMouseEnter={this.handleMouseEnter}
onMouseMove={this.handleMouseMove}
title={user.name}>
<div className="little-spacer-bottom little-spacer-top">
<Avatar hash={user.avatar} email={user.email} name={user.name} size={AVATAR_SIZE} />
<strong className="spacer-left">{this.props.children}</strong>
<span className="note little-spacer-left">{user.login}</span>
</div>
<Avatar hash={user.avatar} email={user.email} name={user.name} size={AVATAR_SIZE} />
<strong className="spacer-left">{this.props.children}</strong>
<span className="note little-spacer-left">{user.login}</span>
</div>
);
}

+ 28
- 36
server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap View File

@@ -7,25 +7,21 @@ exports[`should render correctly with email instead of hash 1`] = `
onMouseMove={[Function]}
title="Administrator"
>
<div
className="little-spacer-bottom little-spacer-top"
<Connect(Avatar)
email="admin@admin.ch"
name="Administrator"
size={20}
/>
<strong
className="spacer-left"
>
<Connect(Avatar)
email="admin@admin.ch"
name="Administrator"
size={20}
/>
<strong
className="spacer-left"
>
Administrator
</strong>
<span
className="note little-spacer-left"
>
admin
</span>
</div>
Administrator
</strong>
<span
className="note little-spacer-left"
>
admin
</span>
</div>
`;

@@ -36,24 +32,20 @@ exports[`should render correctly without all parameters 1`] = `
onMouseMove={[Function]}
title="Administrator"
>
<div
className="little-spacer-bottom little-spacer-top"
<Connect(Avatar)
hash="7daf6c79d4802916d83f6266e24850af"
name="Administrator"
size={20}
/>
<strong
className="spacer-left"
>
Administrator
</strong>
<span
className="note little-spacer-left"
>
<Connect(Avatar)
hash="7daf6c79d4802916d83f6266e24850af"
name="Administrator"
size={20}
/>
<strong
className="spacer-left"
>
Administrator
</strong>
<span
className="note little-spacer-left"
>
admin
</span>
</div>
admin
</span>
</div>
`;

+ 4
- 1
server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js View File

@@ -124,7 +124,10 @@ export default class AdvancedTimeline extends React.PureComponent {
} else if (props.metricType === 'LEVEL') {
return this.getLevelScale(availableHeight);
} else {
return scaleLinear().range([availableHeight, 0]).domain([0, max(flatData, d => d.y)]).nice();
return scaleLinear()
.range([availableHeight, 0])
.domain([0, max(flatData, d => d.y) || 0])
.nice();
}
};


+ 23
- 0
server/sonar-web/src/main/less/components/react-select.less View File

@@ -346,6 +346,29 @@
opacity: 0.5;
}

.Select-big .Select-control {
padding-top: 4px;
padding-bottom: 4px;
}

.Select-big .Select-placeholder {
margin-top: 4px;
margin-bottom: 4px;
}

.Select-big .Select-value-label {
display: inline-block;
margin-top: 5px;
}

.Select-big .Select-option {
padding: 4px 8px;
}

.Select-big img {
padding-top: 0;
}

.Select--multi .Select-value-icon,
.Select--multi .Select-value-label {
display: inline-block;

+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -1288,7 +1288,10 @@ project_activity.graphs.overview=Overview
project_activity.graphs.coverage=Coverage
project_activity.graphs.duplications=Duplications
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.no_history=There is no historical data to show, please add more metrics to your graph.
project_activity.graphs.custom.search=Search for a metric by name

project_activity.custom_metric.covered_lines=Covered Lines


Loading…
Cancel
Save