@@ -2,7 +2,7 @@ | |||
exports[`addCustomEvent should correctly add a custom event 1`] = ` | |||
Object { | |||
"date": "2016-10-27T12:21:15+0200", | |||
"date": 2016-10-27T10:21:15.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "Custom", | |||
@@ -16,7 +16,7 @@ Object { | |||
exports[`changeEvent should correctly update an event 1`] = ` | |||
Object { | |||
"date": "2016-10-27T16:33:50+0200", | |||
"date": 2016-10-27T14:33:50.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", | |||
@@ -31,12 +31,12 @@ Object { | |||
exports[`deleteAnalysis should correctly delete an analyses 1`] = ` | |||
Array [ | |||
Object { | |||
"date": "2016-10-27T12:21:15+0200", | |||
"date": 2016-10-27T10:21:15.000Z, | |||
"events": Array [], | |||
"key": "A2", | |||
}, | |||
Object { | |||
"date": "2016-10-26T12:17:29+0200", | |||
"date": 2016-10-26T10:17:29.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "OTHER", | |||
@@ -56,7 +56,7 @@ Array [ | |||
exports[`deleteEvent should correctly remove an event 1`] = ` | |||
Object { | |||
"date": "2016-10-27T16:33:50+0200", | |||
"date": 2016-10-27T14:33:50.000Z, | |||
"events": Array [], | |||
"key": "A1", | |||
} | |||
@@ -64,7 +64,7 @@ Object { | |||
exports[`deleteEvent should correctly remove an event 2`] = ` | |||
Object { | |||
"date": "2016-10-27T12:21:15+0200", | |||
"date": 2016-10-27T10:21:15.000Z, | |||
"events": Array [], | |||
"key": "A2", | |||
} | |||
@@ -72,7 +72,7 @@ Object { | |||
exports[`deleteEvent should correctly remove an event 3`] = ` | |||
Object { | |||
"date": "2016-10-26T12:17:29+0200", | |||
"date": 2016-10-26T10:17:29.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "OTHER", |
@@ -23,7 +23,7 @@ import * as actions from '../actions'; | |||
const ANALYSES = [ | |||
{ | |||
key: 'A1', | |||
date: '2016-10-27T16:33:50+0200', | |||
date: new Date('2016-10-27T16:33:50+0200'), | |||
events: [ | |||
{ | |||
key: 'E1', | |||
@@ -34,12 +34,12 @@ const ANALYSES = [ | |||
}, | |||
{ | |||
key: 'A2', | |||
date: '2016-10-27T12:21:15+0200', | |||
date: new Date('2016-10-27T12:21:15+0200'), | |||
events: [] | |||
}, | |||
{ | |||
key: 'A3', | |||
date: '2016-10-26T12:17:29+0200', | |||
date: new Date('2016-10-26T12:17:29+0200'), | |||
events: [ | |||
{ | |||
key: 'E2', |
@@ -70,12 +70,16 @@ export default class ProjectActivityApp extends React.PureComponent { | |||
} | |||
filterAnalyses = (analyses: Array<Analysis>, query: Query): Array<Analysis> => { | |||
if (!query.category) { | |||
if (!query.category && !query.from && !query.to) { | |||
return analyses; | |||
} | |||
return analyses.filter( | |||
analysis => analysis.events.find(event => event.category === query.category) != null | |||
); | |||
return analyses.filter(analysis => { | |||
const isAfterFrom = !query.from || analysis.date >= query.from; | |||
const isBeforeTo = !query.to || analysis.date <= query.to; | |||
const hasSelectedCategoryEvents = | |||
!query.category || analysis.events.find(event => event.category === query.category) != null; | |||
return isAfterFrom && isBeforeTo && hasSelectedCategoryEvents; | |||
}); | |||
}; | |||
getMetricType = () => { |
@@ -137,7 +137,13 @@ class ProjectActivityAppContainer extends React.PureComponent { | |||
} | |||
): Promise<{ analyses: Array<Analysis>, paging: Paging }> => { | |||
const parameters = { project, p, ps }; | |||
return api.getProjectActivity({ ...parameters, ...additional }).catch(throwGlobalError); | |||
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>> => |
@@ -24,7 +24,7 @@ import ProjectActivityApp from '../ProjectActivityApp'; | |||
const ANALYSES = [ | |||
{ | |||
key: 'A1', | |||
date: '2016-10-27T16:33:50+0200', | |||
date: new Date('2016-10-27T16:33:50+0200'), | |||
events: [ | |||
{ | |||
key: 'E1', | |||
@@ -35,12 +35,12 @@ const ANALYSES = [ | |||
}, | |||
{ | |||
key: 'A2', | |||
date: '2016-10-27T12:21:15+0200', | |||
date: new Date('2016-10-27T12:21:15+0200'), | |||
events: [] | |||
}, | |||
{ | |||
key: 'A3', | |||
date: '2016-10-26T12:17:29+0200', | |||
date: new Date('2016-10-26T12:17:29+0200'), | |||
events: [ | |||
{ | |||
key: 'E2', | |||
@@ -86,8 +86,20 @@ it('should render correctly', () => { | |||
expect(shallow(<ProjectActivityApp {...DEFAULT_PROPS} />)).toMatchSnapshot(); | |||
}); | |||
it('should correctly filter analyses', () => { | |||
it('should correctly filter analyses by category', () => { | |||
const wrapper = mount(<ProjectActivityApp {...DEFAULT_PROPS} />); | |||
wrapper.setProps({ query: { ...DEFAULT_PROPS.query, category: 'VERSION' } }); | |||
expect(wrapper.state()).toMatchSnapshot(); | |||
}); | |||
it('should correctly filter analyses by date range', () => { | |||
const wrapper = mount(<ProjectActivityApp {...DEFAULT_PROPS} />); | |||
wrapper.setProps({ | |||
query: { | |||
...DEFAULT_PROPS.query, | |||
from: new Date('2016-10-27T12:21:15+0200'), | |||
to: new Date('2016-10-27T12:21:15+0200') | |||
} | |||
}); | |||
expect(wrapper.state()).toMatchSnapshot(); | |||
}); |
@@ -24,7 +24,7 @@ import StaticGraphs from '../StaticGraphs'; | |||
const ANALYSES = [ | |||
{ | |||
key: 'A1', | |||
date: '2016-10-27T16:33:50+0200', | |||
date: new Date('2016-10-27T16:33:50+0200'), | |||
events: [ | |||
{ | |||
key: 'E1', | |||
@@ -35,12 +35,12 @@ const ANALYSES = [ | |||
}, | |||
{ | |||
key: 'A2', | |||
date: '2016-10-27T12:21:15+0200', | |||
date: new Date('2016-10-27T12:21:15+0200'), | |||
events: [] | |||
}, | |||
{ | |||
key: 'A3', | |||
date: '2016-10-26T12:17:29+0200', | |||
date: new Date('2016-10-26T12:17:29+0200'), | |||
events: [ | |||
{ | |||
key: 'E2', |
@@ -1,10 +1,10 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should correctly filter analyses 1`] = ` | |||
exports[`should correctly filter analyses by category 1`] = ` | |||
Object { | |||
"filteredAnalyses": Array [ | |||
Object { | |||
"date": "2016-10-27T16:33:50+0200", | |||
"date": 2016-10-27T14:33:50.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", | |||
@@ -15,7 +15,7 @@ Object { | |||
"key": "A1", | |||
}, | |||
Object { | |||
"date": "2016-10-26T12:17:29+0200", | |||
"date": 2016-10-26T10:17:29.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", | |||
@@ -34,6 +34,18 @@ Object { | |||
} | |||
`; | |||
exports[`should correctly filter analyses by date range 1`] = ` | |||
Object { | |||
"filteredAnalyses": Array [ | |||
Object { | |||
"date": 2016-10-27T10:21:15.000Z, | |||
"events": Array [], | |||
"key": "A2", | |||
}, | |||
], | |||
} | |||
`; | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="page page-limited" | |||
@@ -58,7 +70,7 @@ exports[`should render correctly 1`] = ` | |||
analyses={ | |||
Array [ | |||
Object { | |||
"date": "2016-10-27T16:33:50+0200", | |||
"date": 2016-10-27T14:33:50.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", | |||
@@ -69,12 +81,12 @@ exports[`should render correctly 1`] = ` | |||
"key": "A1", | |||
}, | |||
Object { | |||
"date": "2016-10-27T12:21:15+0200", | |||
"date": 2016-10-27T10:21:15.000Z, | |||
"events": Array [], | |||
"key": "A2", | |||
}, | |||
Object { | |||
"date": "2016-10-26T12:17:29+0200", | |||
"date": 2016-10-26T10:17:29.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", | |||
@@ -106,7 +118,7 @@ exports[`should render correctly 1`] = ` | |||
analyses={ | |||
Array [ | |||
Object { | |||
"date": "2016-10-27T16:33:50+0200", | |||
"date": 2016-10-27T14:33:50.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", | |||
@@ -117,12 +129,12 @@ exports[`should render correctly 1`] = ` | |||
"key": "A1", | |||
}, | |||
Object { | |||
"date": "2016-10-27T12:21:15+0200", | |||
"date": 2016-10-27T10:21:15.000Z, | |||
"events": Array [], | |||
"key": "A2", | |||
}, | |||
Object { | |||
"date": "2016-10-26T12:17:29+0200", | |||
"date": 2016-10-26T10:17:29.000Z, | |||
"events": Array [ | |||
Object { | |||
"category": "VERSION", |
@@ -28,7 +28,7 @@ export type Event = { | |||
export type Analysis = { | |||
key: string, | |||
date: string, | |||
date: Date, | |||
events: Array<Event> | |||
}; | |||
@@ -50,6 +50,8 @@ export type Paging = { | |||
export type Query = { | |||
category: string, | |||
from?: Date, | |||
graph: string, | |||
project: string | |||
project: string, | |||
to?: Date | |||
}; |
@@ -18,7 +18,13 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import { cleanQuery, parseAsString, serializeString } from '../../helpers/query'; | |||
import { | |||
cleanQuery, | |||
parseAsDate, | |||
parseAsString, | |||
serializeDate, | |||
serializeString | |||
} from '../../helpers/query'; | |||
import { translate } from '../../helpers/l10n'; | |||
import type { MeasureHistory, Query } from './types'; | |||
import type { RawQuery } from '../../helpers/query'; | |||
@@ -41,8 +47,10 @@ const serializeGraph = (value: string): string => (value === 'overview' ? '' : v | |||
export const parseQuery = (urlQuery: RawQuery): Query => ({ | |||
category: parseAsString(urlQuery['category']), | |||
from: parseAsDate(urlQuery['from']), | |||
graph: parseGraph(urlQuery['graph']), | |||
project: parseAsString(urlQuery['id']) | |||
project: parseAsString(urlQuery['id']), | |||
to: parseAsDate(urlQuery['to']) | |||
}); | |||
export const serializeQuery = (query: Query): RawQuery => | |||
@@ -54,13 +62,17 @@ export const serializeQuery = (query: Query): RawQuery => | |||
export const serializeUrlQuery = (query: Query): RawQuery => { | |||
return cleanQuery({ | |||
category: serializeString(query.category), | |||
from: serializeDate(query.from), | |||
graph: serializeGraph(query.graph), | |||
id: serializeString(query.project) | |||
id: serializeString(query.project), | |||
to: serializeDate(query.to) | |||
}); | |||
}; | |||
export const activityQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => | |||
prevQuery.category !== nextQuery.category; | |||
prevQuery.category !== nextQuery.category || | |||
prevQuery.from !== nextQuery.from || | |||
prevQuery.to !== nextQuery.to; | |||
export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => | |||
prevQuery.graph !== nextQuery.graph; |
@@ -7,3 +7,5 @@ Object { | |||
"e": 0, | |||
} | |||
`; | |||
exports[`parseAsDate should parse string date correctly 1`] = `2016-06-20T13:09:48.256Z`; |
@@ -69,6 +69,24 @@ describe('parseAsArray', () => { | |||
}); | |||
}); | |||
describe('parseAsDate', () => { | |||
it('should parse string date correctly', () => { | |||
expect(query.parseAsDate('2016-06-20T13:09:48.256Z')).toMatchSnapshot(); | |||
expect(query.parseAsDate('')).toBeFalsy(); | |||
expect(query.parseAsDate()).toBeFalsy(); | |||
}); | |||
}); | |||
describe('serializeDate', () => { | |||
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('')).toBeUndefined(); | |||
expect(query.serializeDate()).toBeUndefined(); | |||
}); | |||
}); | |||
describe('serializeString', () => { | |||
it('should serialize string correctly', () => { | |||
expect(query.serializeString('foo')).toBe('foo'); |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { isNil, omitBy } from 'lodash'; | |||
import moment from 'moment'; | |||
export type RawQuery = { [string]: string }; | |||
@@ -54,6 +55,13 @@ export const cleanQuery = (query: { [string]: ?string }): RawQuery => omitBy(que | |||
export const parseAsBoolean = (value: ?string, defaultValue: boolean = true): boolean => | |||
(value === 'false' ? false : value === 'true' ? true : defaultValue); | |||
export const parseAsDate = (value: ?string): ?Date => { | |||
const date = moment(value); | |||
if (value && date) { | |||
return date.toDate(); | |||
} | |||
}; | |||
export const parseAsFacetMode = (facetMode: string) => | |||
(facetMode === 'debt' || facetMode === 'effort' ? 'effort' : 'count'); | |||
@@ -62,6 +70,12 @@ export const parseAsString = (value: ?string): string => value || ''; | |||
export const parseAsArray = <T>(value: ?string, itemParser: string => T): Array<T> => | |||
(value ? value.split(',').map(itemParser) : []); | |||
export const serializeDate = (value: ?Date): ?string => { | |||
if (value != null && value.toISOString) { | |||
return value.toISOString(); | |||
} | |||
}; | |||
export const serializeString = (value: string): ?string => value || undefined; | |||
export const serializeStringArray = (value: ?Array<string>): ?string => |