aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js4
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js)16
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphsLegend.js)2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js60
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js1
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js14
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js22
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js)12
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphsLegend-test.js)4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphs-test.js.snap)2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphsLegend-test.js.snap)0
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap9
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js147
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/types.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/utils.js5
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css13
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js1
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js8
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap64
-rw-r--r--server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js5
-rw-r--r--server/sonar-web/src/main/less/components/react-select.less23
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties3
24 files changed, 303 insertions, 117 deletions
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js b/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js
index 4502597be29..e59f7137a8b 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/AddMemberForm.js
@@ -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>
diff --git a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap
index 9fa2ecff49d..8fbd79d33c3 100644
--- a/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/components/forms/__tests__/__snapshots__/AddMemberForm-test.js.snap
@@ -61,6 +61,7 @@ exports[`should render and open the modal 2`] = `
>
<div>
<button
+ disabled={true}
type="submit"
>
organization.members.add_to_members
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js
index a303e66b9bc..36ac11b372e 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphs.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsHistory.js
@@ -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 }) => (
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphsLegend.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js
index a1ceab688f4..7dffdf75321 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/StaticGraphsLegend.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsLegendStatic.js
@@ -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 => (
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js
index 73e9411b862..f6e6bab8dc7 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/GraphsZoom.js
@@ -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>
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
index 954c6169f29..02a1f1b5eb3 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
@@ -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}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
index 7fb3bb818f6..0b86b71bdc5 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js
@@ -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}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js
index 33ee4ffab72..194af091b69 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js
index dacaff2025f..94d9d326340 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsHistory-test.js
@@ -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();
});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphsLegend-test.js b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js
index 05d28fa8da9..2226ebb4208 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphsLegend-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/GraphsLegendStatic-test.js
@@ -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();
});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphs-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap
index ba33441a2a6..06e5621c923 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphs-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsHistory-test.js.snap
@@ -29,7 +29,7 @@ exports[`should correctly render a graph 1`] = `
<div
className="project-activity-graph-container"
>
- <StaticGraphsLegend
+ <GraphsLegendStatic
series={
Array [
Object {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphsLegend-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap
index 1fd564f69ef..1fd564f69ef 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphsLegend-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsLegendStatic-test.js.snap
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
index 91f1dcdc0e4..1943a2f48ac 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
@@ -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={
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
index 6da3cc4d3bb..3c46221619f 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityGraphs-test.js.snap
@@ -8,7 +8,7 @@ exports[`should render correctly the graph and legends 1`] = `
graph="overview"
updateQuery={[Function]}
/>
- <StaticGraphs
+ <GraphsHistory
analyses={
Array [
Object {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js
new file mode 100644
index 00000000000..a7193b62093
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddGraphMetric.js
@@ -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>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/types.js b/server/sonar-web/src/main/js/apps/projectActivity/types.js
index 9a9c96abf71..ab6c9fa78a3 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/types.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/types.js
@@ -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
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/utils.js b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
index 9ee44a3fc59..24c4f3fecc6 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/utils.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
@@ -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;
diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css
deleted file mode 100644
index c8780833809..00000000000
--- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.css
+++ /dev/null
@@ -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;
-}
diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js
index f6a9b1ad5ee..3e887b078c4 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js
+++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.js
@@ -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,
diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js
index e7f4a5ab428..e1b8056fd0e 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js
+++ b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearchOption.js
@@ -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>
);
}
diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap
index 80c53be30fe..56c7a055882 100644
--- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearchOption-test.js.snap
@@ -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>
`;
diff --git a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
index 413a53a78d3..78ff543351f 100644
--- a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
+++ b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js
@@ -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();
}
};
diff --git a/server/sonar-web/src/main/less/components/react-select.less b/server/sonar-web/src/main/less/components/react-select.less
index a29d4113e52..e191986705f 100644
--- a/server/sonar-web/src/main/less/components/react-select.less
+++ b/server/sonar-web/src/main/less/components/react-select.less
@@ -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;
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index b5a2d54c317..78886e2d67b 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -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