aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-07-07 17:12:00 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-07-13 14:34:17 +0200
commitb5e582ccc44788e5d57dde7df2ed0b0050f82c1a (patch)
tree8dae37a74e5f9e9569d282b93a212a4e298499e6 /server/sonar-web
parent598a5a81e4842fa03705f7f1c26a79bc520c70ef (diff)
downloadsonarqube-b5e582ccc44788e5d57dde7df2ed0b0050f82c1a.tar.gz
sonarqube-b5e582ccc44788e5d57dde7df2ed0b0050f82c1a.zip
Apply several feedbacks
* Fix dates serializing * Prevent clicks on event button to activate the graph tooltip * Throttle part of the zoom graph * Reload history when an analysis is deleted * Fix multiple style flaws
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/api/projectActivity.js12
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js3
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js11
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/Event.js6
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js1
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js5
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js32
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphsHeader.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js3
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js11
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css1
-rw-r--r--server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js28
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/query-test.js6
-rw-r--r--server/sonar-web/src/main/js/helpers/query.js2
-rw-r--r--server/sonar-web/src/main/less/components/graphics.less1
15 files changed, 70 insertions, 56 deletions
diff --git a/server/sonar-web/src/main/js/api/projectActivity.js b/server/sonar-web/src/main/js/api/projectActivity.js
index 8ffe600e8df..19dd91243dc 100644
--- a/server/sonar-web/src/main/js/api/projectActivity.js
+++ b/server/sonar-web/src/main/js/api/projectActivity.js
@@ -19,6 +19,7 @@
*/
// @flow
import { getJSON, postJSON, post } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
type GetProjectActivityResponse = {
analyses: Array<Object>,
@@ -38,7 +39,8 @@ type GetProjectActivityOptions = {
export const getProjectActivity = (
data: GetProjectActivityOptions
-): Promise<GetProjectActivityResponse> => getJSON('/api/project_analyses/search', data);
+): Promise<GetProjectActivityResponse> =>
+ getJSON('/api/project_analyses/search', data).catch(throwGlobalError);
type CreateEventResponse = {
analysis: string,
@@ -61,11 +63,11 @@ export const createEvent = (
if (description) {
data.description = description;
}
- return postJSON('/api/project_analyses/create_event', data).then(r => r.event);
+ return postJSON('/api/project_analyses/create_event', data).then(r => r.event, throwGlobalError);
};
export const deleteEvent = (event: string): Promise<*> =>
- post('/api/project_analyses/delete_event', { event });
+ post('/api/project_analyses/delete_event', { event }).catch(throwGlobalError);
export const changeEvent = (
event: string,
@@ -79,8 +81,8 @@ export const changeEvent = (
if (description) {
data.description = description;
}
- return postJSON('/api/project_analyses/update_event', data).then(r => r.event);
+ return postJSON('/api/project_analyses/update_event', data).then(r => r.event, throwGlobalError);
};
export const deleteAnalysis = (analysis: string): Promise<*> =>
- post('/api/project_analyses/delete', { analysis });
+ post('/api/project_analyses/delete', { analysis }).catch(throwGlobalError);
diff --git a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js
index 9746ea02c4a..05856e6601e 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js
@@ -22,7 +22,6 @@ import React from 'react';
import { Link } from 'react-router';
import Analysis from './Analysis';
import PreviewGraph from './PreviewGraph';
-import throwGlobalError from '../../../app/utils/throwGlobalError';
import { getMetrics } from '../../../api/metrics';
import { getProjectActivity } from '../../../api/projectActivity';
import { translate } from '../../../helpers/l10n';
@@ -72,7 +71,7 @@ export default class AnalysesList extends React.PureComponent {
if (this.mounted) {
this.setState({ analyses: response[0].analyses, metrics: response[1], loading: false });
}
- }, throwGlobalError);
+ });
}
renderList(analyses: Array<AnalysisType>) {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js
index d94a9f626c4..c165f9740b8 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/utils-test.js
@@ -88,7 +88,8 @@ jest.mock('moment', () => date => ({
valueOf: () => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`
};
},
- toDate: () => new Date(date)
+ toDate: () => new Date(date),
+ format: format => `Formated.${format}:${date.valueOf()}`
}));
describe('generateCoveredLinesMetric', () => {
@@ -164,11 +165,11 @@ describe('parseQuery', () => {
describe('serializeQuery', () => {
it('should serialize query for api request', () => {
expect(utils.serializeQuery(QUERY)).toEqual({
- from: '2017-04-27T06:21:32.000Z',
+ from: 'Formated.YYYY-MM-DDTHH:mm:ssZZ:1493274092000',
project: 'foo'
});
expect(utils.serializeQuery({ ...QUERY, graph: 'coverage', category: 'test' })).toEqual({
- from: '2017-04-27T06:21:32.000Z',
+ from: 'Formated.YYYY-MM-DDTHH:mm:ssZZ:1493274092000',
project: 'foo',
category: 'test'
});
@@ -178,14 +179,14 @@ describe('serializeQuery', () => {
describe('serializeUrlQuery', () => {
it('should serialize query for url', () => {
expect(utils.serializeUrlQuery(QUERY)).toEqual({
- from: '2017-04-27T06:21:32.000Z',
+ from: 'Formated.YYYY-MM-DDTHH:mm:ssZZ:1493274092000',
id: 'foo',
custom_metrics: 'foo,bar,baz'
});
expect(
utils.serializeUrlQuery({ ...QUERY, graph: 'coverage', category: 'test', customMetrics: [] })
).toEqual({
- from: '2017-04-27T06:21:32.000Z',
+ from: 'Formated.YYYY-MM-DDTHH:mm:ssZZ:1493274092000',
id: 'foo',
graph: 'coverage',
category: 'test'
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/Event.js b/server/sonar-web/src/main/js/apps/projectActivity/components/Event.js
index 6e1a3878c88..f5bc0579b16 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/Event.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/Event.js
@@ -56,7 +56,8 @@ export default class Event extends React.PureComponent {
this.mounted = false;
}
- startChanging = () => {
+ startChanging = (e: MouseEvent) => {
+ e.stopPropagation();
this.setState({ changing: true });
};
@@ -66,7 +67,8 @@ export default class Event extends React.PureComponent {
}
};
- startDeleting = () => {
+ startDeleting = (e: MouseEvent) => {
+ e.stopPropagation();
this.setState({ deleting: true });
};
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
index 9c98102a95e..a0a8a41dd31 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
@@ -118,6 +118,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent {
}
}
};
+
handleScroll = () => this.updateStickyBadges(true);
resetScrollTop = (newScrollTop: number, forceBadgeAlignement?: boolean) => {
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
index 842b3ff8a44..72490f7d41b 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
@@ -45,6 +45,8 @@ export default class ProjectActivityAnalysis extends React.PureComponent {
handleClick = () => this.props.updateSelectedDate(this.props.analysis.date);
+ stopPropagation = (e: Event) => e.stopPropagation();
+
render() {
const { analysis, isFirst, canAdmin } = this.props;
const { date, events } = analysis;
@@ -69,7 +71,8 @@ export default class ProjectActivityAnalysis extends React.PureComponent {
<div className="dropdown display-inline-block">
<button
className="js-analysis-actions button-small button-compact dropdown-toggle"
- data-toggle="dropdown">
+ data-toggle="dropdown"
+ onClick={this.stopPropagation}>
<i className="icon-settings" />
{' '}
<i className="icon-dropdown" />
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
index afe28a6ebe1..dd3b3690c07 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
@@ -118,8 +118,7 @@ class ProjectActivityAppContainer extends React.PureComponent {
.createEvent(analysis, name, category)
.then(
({ analysis, ...event }) =>
- this.mounted && this.setState(actions.addCustomEvent(analysis, event)),
- throwGlobalError
+ this.mounted && this.setState(actions.addCustomEvent(analysis, event))
);
addVersion = (analysis: string, version: string): Promise<*> =>
@@ -130,25 +129,21 @@ class ProjectActivityAppContainer extends React.PureComponent {
.changeEvent(event, name)
.then(
({ analysis, ...event }) =>
- this.mounted && this.setState(actions.changeEvent(analysis, event)),
- throwGlobalError
+ this.mounted && this.setState(actions.changeEvent(analysis, event))
);
deleteAnalysis = (analysis: string): Promise<*> =>
- api
- .deleteAnalysis(analysis)
- .then(
- () => this.mounted && this.setState(actions.deleteAnalysis(analysis)),
- throwGlobalError
- );
+ api.deleteAnalysis(analysis).then(() => {
+ if (this.mounted) {
+ this.updateGraphData(this.state.query.graph, this.state.query.customMetrics);
+ this.setState(actions.deleteAnalysis(analysis));
+ }
+ });
deleteEvent = (analysis: string, event: string): Promise<*> =>
api
.deleteEvent(event)
- .then(
- () => this.mounted && this.setState(actions.deleteEvent(analysis, event)),
- throwGlobalError
- );
+ .then(() => this.mounted && this.setState(actions.deleteEvent(analysis, event)));
fetchActivity = (
project: string,
@@ -159,13 +154,12 @@ class ProjectActivityAppContainer extends React.PureComponent {
}
): Promise<{ analyses: Array<Analysis>, paging: Paging }> => {
const parameters = { project, p, ps };
- return api.getProjectActivity({ ...parameters, ...additional }).then(
- ({ analyses, paging }) => ({
+ return api
+ .getProjectActivity({ ...parameters, ...additional })
+ .then(({ analyses, paging }) => ({
analyses: analyses.map(analysis => ({ ...analysis, date: moment(analysis.date).toDate() })),
paging
- }),
- throwGlobalError
- );
+ }));
};
fetchMeasuresHistory = (metrics: Array<string>): Promise<Array<MeasureHistory>> => {
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 007b04f4d52..d4e82b0fa62 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
@@ -51,7 +51,7 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent {
return (
<header className="page-header">
<Select
- className="input-medium"
+ className="pull-left input-medium"
clearable={false}
searchable={false}
value={this.props.graph}
@@ -61,7 +61,7 @@ export default class ProjectActivityGraphsHeader extends React.PureComponent {
{isCustomGraph(this.props.graph) &&
<AddGraphMetric
addMetric={this.props.addCustomMetric}
- className="spacer-left"
+ className="pull-left spacer-left"
metrics={this.props.metrics}
selectedMetrics={this.props.selectedMetrics}
/>}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
index 92c4436f20d..028fbc125a1 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
@@ -52,8 +52,9 @@ export default class AddEventForm extends React.PureComponent {
this.mounted = false;
}
- openForm = (e: Object) => {
+ openForm = (e: Event) => {
e.preventDefault();
+ e.stopPropagation();
if (this.mounted) {
this.setState({ open: true });
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
index 138274caefc..d96a05f7334 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
@@ -49,8 +49,9 @@ export default class RemoveAnalysisForm extends React.PureComponent {
this.mounted = false;
}
- openForm = (evt: Event) => {
- evt.preventDefault();
+ openForm = (e: Event) => {
+ e.preventDefault();
+ e.stopPropagation();
if (this.mounted) {
this.setState({ open: true });
}
@@ -74,7 +75,7 @@ export default class RemoveAnalysisForm extends React.PureComponent {
}
};
- handleSubmit = (e: Object) => {
+ handleSubmit = (e: Event) => {
e.preventDefault();
this.setState({ processing: true });
this.props
@@ -104,7 +105,9 @@ export default class RemoveAnalysisForm extends React.PureComponent {
{this.state.processing
? <i className="spinner" />
: <div>
- <button type="submit" className="button-red">{translate('delete')}</button>
+ <button type="submit" className="button-red" autoFocus={true}>
+ {translate('delete')}
+ </button>
<button type="reset" className="button-link" onClick={this.closeForm}>
{translate('cancel')}
</button>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
index cc0b6d1bd7a..740aef24ba3 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/projectActivity.css
@@ -54,6 +54,7 @@
.project-activity-graph {
flex: 1;
+ overflow: hidden;
}
.project-activity-graph-legends {
diff --git a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
index 4de2cda1f77..81ad1921a0e 100644
--- a/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
+++ b/server/sonar-web/src/main/js/components/charts/ZoomTimeLine.js
@@ -20,7 +20,7 @@
// @flow
import React from 'react';
import classNames from 'classnames';
-import { flatten, sortBy } from 'lodash';
+import { flatten, sortBy, throttle } from 'lodash';
import { extent, max } from 'd3-array';
import { scaleLinear, scalePoint, scaleTime } from 'd3-scale';
import { line as d3Line, area, curveBasis } from 'd3-shape';
@@ -51,15 +51,18 @@ type State = {
export default class ZoomTimeLine extends React.PureComponent {
props: Props;
+ state: State;
+
static defaultProps = {
padding: [0, 0, 18, 0],
showXTicks: true
};
- state: State = {
- overlayLeftPos: null,
- newZoomStart: null
- };
+ constructor(props: Props) {
+ super(props);
+ this.state = { overlayLeftPos: null, newZoomStart: null };
+ this.handleZoomUpdate = throttle(this.handleZoomUpdate, 40);
+ }
getRatingScale = (availableHeight: number) =>
scalePoint().domain([5, 4, 3, 2, 1]).range([availableHeight, 0]);
@@ -135,10 +138,10 @@ export default class ZoomTimeLine extends React.PureComponent {
handleNewZoomDrag = (xScale: Scale, xDim: Array<number>) => (e: Event, data: DraggableData) => {
const { newZoomStart, overlayLeftPos } = this.state;
if (newZoomStart != null && overlayLeftPos != null && data.deltaX) {
- this.handleZoomUpdate(xScale, [
- newZoomStart,
- Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1]))
- ]);
+ this.handleZoomUpdate(
+ xScale,
+ sortBy([newZoomStart, Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1]))])
+ );
}
};
@@ -149,7 +152,7 @@ export default class ZoomTimeLine extends React.PureComponent {
const { newZoomStart, overlayLeftPos } = this.state;
if (newZoomStart != null && overlayLeftPos != null) {
const x = Math.round(Math.max(xDim[0], Math.min(data.x - overlayLeftPos, xDim[1])));
- this.handleZoomUpdate(xScale, newZoomStart === x ? xDim : [newZoomStart, x]);
+ this.handleZoomUpdate(xScale, newZoomStart === x ? xDim : sortBy([newZoomStart, x]));
this.setState({ newZoomStart: null, overlayLeftPos: null });
}
};
@@ -303,7 +306,10 @@ export default class ZoomTimeLine extends React.PureComponent {
const endX = Math.round(this.props.endDate ? xScale(this.props.endDate) : xDim[1]);
const xArray = sortBy([startX, endX]);
const zoomBoxWidth = xArray[1] - xArray[0];
- const showZoomArea = this.state.newZoomStart == null || this.state.newZoomStart === startX;
+ const showZoomArea =
+ this.state.newZoomStart == null ||
+ this.state.newZoomStart === startX ||
+ this.state.newZoomStart === endX;
return (
<g className="chart-zoom">
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/query-test.js b/server/sonar-web/src/main/js/helpers/__tests__/query-test.js
index 6ef12860f00..982f9375a36 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/query-test.js
+++ b/server/sonar-web/src/main/js/helpers/__tests__/query-test.js
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import moment from 'moment';
import * as query from '../query';
describe('queriesEqual', () => {
@@ -78,10 +79,9 @@ describe('parseAsDate', () => {
});
describe('serializeDate', () => {
+ const date = moment.utc('2016-06-20T13:09:48.256Z');
it('should serialize string correctly', () => {
- expect(query.serializeDate(new Date('2016-06-20T13:09:48.256Z'))).toBe(
- '2016-06-20T13:09:48.256Z'
- );
+ expect(query.serializeDate(date)).toBe('2016-06-20T13:09:48+0000');
expect(query.serializeDate('')).toBeUndefined();
expect(query.serializeDate()).toBeUndefined();
});
diff --git a/server/sonar-web/src/main/js/helpers/query.js b/server/sonar-web/src/main/js/helpers/query.js
index 37663f2cd49..5f4a28ec12c 100644
--- a/server/sonar-web/src/main/js/helpers/query.js
+++ b/server/sonar-web/src/main/js/helpers/query.js
@@ -72,7 +72,7 @@ export const parseAsArray = <T>(value: ?string, itemParser: string => T): Array<
export const serializeDate = (value: ?Date): ?string => {
if (value != null && value.toISOString) {
- return value.toISOString();
+ return moment(value).format('YYYY-MM-DDTHH:mm:ssZZ');
}
};
diff --git a/server/sonar-web/src/main/less/components/graphics.less b/server/sonar-web/src/main/less/components/graphics.less
index 14f9d2d1e0e..5ba0e6da818 100644
--- a/server/sonar-web/src/main/less/components/graphics.less
+++ b/server/sonar-web/src/main/less/components/graphics.less
@@ -294,6 +294,7 @@
fill: @secondFontColor;
font-size: 10px;
text-anchor: middle;
+ user-select: none;
}
.chart-mouse-events-overlay {