*/
// @flow
import { getJSON, postJSON, post } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
type GetProjectActivityResponse = {
analyses: Array<Object>,
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,
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,
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);
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';
if (this.mounted) {
this.setState({ analyses: response[0].analyses, metrics: response[1], loading: false });
}
- }, throwGlobalError);
+ });
}
renderList(analyses: Array<AnalysisType>) {
valueOf: () => `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`
};
},
- toDate: () => new Date(date)
+ toDate: () => new Date(date),
+ format: format => `Formated.${format}:${date.valueOf()}`
}));
describe('generateCoveredLinesMetric', () => {
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'
});
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'
this.mounted = false;
}
- startChanging = () => {
+ startChanging = (e: MouseEvent) => {
+ e.stopPropagation();
this.setState({ changing: true });
};
}
};
- startDeleting = () => {
+ startDeleting = (e: MouseEvent) => {
+ e.stopPropagation();
this.setState({ deleting: true });
};
}
}
};
+
handleScroll = () => this.updateStickyBadges(true);
resetScrollTop = (newScrollTop: number, forceBadgeAlignement?: boolean) => {
handleClick = () => this.props.updateSelectedDate(this.props.analysis.date);
+ stopPropagation = (e: Event) => e.stopPropagation();
+
render() {
const { analysis, isFirst, canAdmin } = this.props;
const { date, events } = analysis;
<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" />
.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<*> =>
.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,
}
): 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>> => {
return (
<header className="page-header">
<Select
- className="input-medium"
+ className="pull-left input-medium"
clearable={false}
searchable={false}
value={this.props.graph}
{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}
/>}
this.mounted = false;
}
- openForm = (e: Object) => {
+ openForm = (e: Event) => {
e.preventDefault();
+ e.stopPropagation();
if (this.mounted) {
this.setState({ open: true });
}
this.mounted = false;
}
- openForm = (evt: Event) => {
- evt.preventDefault();
+ openForm = (e: Event) => {
+ e.preventDefault();
+ e.stopPropagation();
if (this.mounted) {
this.setState({ open: true });
}
}
};
- handleSubmit = (e: Object) => {
+ handleSubmit = (e: Event) => {
e.preventDefault();
this.setState({ processing: true });
this.props
{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>
.project-activity-graph {
flex: 1;
+ overflow: hidden;
}
.project-activity-graph-legends {
// @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';
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]);
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]))])
+ );
}
};
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 });
}
};
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">
* 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', () => {
});
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();
});
export const serializeDate = (value: ?Date): ?string => {
if (value != null && value.toISOString) {
- return value.toISOString();
+ return moment(value).format('YYYY-MM-DDTHH:mm:ssZZ');
}
};
fill: @secondFontColor;
font-size: 10px;
text-anchor: middle;
+ user-select: none;
}
.chart-mouse-events-overlay {