]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9402 Filter activity list based on a date range in the project activity page
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 20 Jun 2017 13:28:20 +0000 (15:28 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 4 Jul 2017 12:15:34 +0000 (14:15 +0200)
12 files changed:
server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/actions-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/StaticGraphs-test.js
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityApp-test.js.snap
server/sonar-web/src/main/js/apps/projectActivity/types.js
server/sonar-web/src/main/js/apps/projectActivity/utils.js
server/sonar-web/src/main/js/helpers/__tests__/__snapshots__/query-test.js.snap
server/sonar-web/src/main/js/helpers/__tests__/query-test.js
server/sonar-web/src/main/js/helpers/query.js

index 2e1d7d96c0994adc322271e31aae0f7303c4f167..10cb48ebfeb92d8caaeb2809c00a16238e50e8e8 100644 (file)
@@ -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",
index c725c76d22cc703e4c41d99e2eabf214b9858e10..c28dfae52103ad53ce34d52224a7a5f36ebacc8e 100644 (file)
@@ -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',
index 266a7bf55fa2a44a257200008ffe298e7f2dd34a..5419cf50e102fdf6e6f7e3312ff13b0a35122b5f 100644 (file)
@@ -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 = () => {
index b6f96b945cfe9b95df71f361c4ea13d88088fedc..22f52f533e56304d9cf025ca09ddf41c4eef45c2 100644 (file)
@@ -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>> =>
index d1a695af5dac0194d1beb56f59652c14fab902de..e6122763349ab39bd49235a14b82a258319e6c79 100644 (file)
@@ -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();
+});
index ae7a7fcd09e3bef1d30da639149faf80b2956724..4798403088215bcb18b3dbeb158ef3d2ed875580 100644 (file)
@@ -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',
index 34a8cc878520d3dc48c3234e0b26138b5ed8d786..c34ab47e81c3e90c69e8d0f83e72c5b1fcf7dc8c 100644 (file)
@@ -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",
index ddd64281ed9aa67cf244ea64d7cc6637171e166c..106fb632866459c3914c63d20678aa10582e6248 100644 (file)
@@ -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
 };
index d24e209c64537ca0d72daa36c2795c5193a6afd4..c4d1b9239fa47dee937e6b99db3f1792b0f34777 100644 (file)
  * 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;
index 55b274979e3a0961da65e6e2ce806e5a74701e43..4f0b5538ba82202ccf4718172a0325a93ceea7e5 100644 (file)
@@ -7,3 +7,5 @@ Object {
   "e": 0,
 }
 `;
+
+exports[`parseAsDate should parse string date correctly 1`] = `2016-06-20T13:09:48.256Z`;
index 5a60b4a52c3fc735926f5561ccce0325f5ed2ebd..6ef12860f005df891e8a2b9ecc12eeb5494646e5 100644 (file)
@@ -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');
index 6e7746d4b0812a6070ed11e6ebaa3b8b30b4c2ae..37663f2cd49522cab486c3fc4288484f83cf9f31 100644 (file)
@@ -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 =>