]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9404 Remember last selected project history graph
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 22 Jun 2017 15:22:21 +0000 (17:22 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 4 Jul 2017 12:15:34 +0000 (14:15 +0200)
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
server/sonar-web/src/main/js/apps/projectActivity/utils.js
server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js
server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
server/sonar-web/src/main/js/apps/projects/routes.js
server/sonar-web/src/main/js/apps/projects/utils.js
server/sonar-web/src/main/js/helpers/storage.js [new file with mode: 0644]

index 22f52f533e56304d9cf025ca09ddf41c4eef45c2..58b855b49f0d9bee7ac2bdb73e5cdb0bfe7ea556 100644 (file)
@@ -29,6 +29,7 @@ import { getAllTimeMachineData } from '../../../api/time-machine';
 import { getMetrics } from '../../../api/metrics';
 import * as api from '../../../api/projectActivity';
 import * as actions from '../actions';
+import { getGraph, saveGraph } from '../../../helpers/storage';
 import { GRAPHS_METRICS, parseQuery, serializeQuery, serializeUrlQuery } from '../utils';
 import type { RawQuery } from '../../../helpers/query';
 import type { Analysis, MeasureHistory, Metric, Paging, Query } from '../types';
@@ -36,7 +37,10 @@ import type { Analysis, MeasureHistory, Metric, Paging, Query } from '../types';
 type Props = {
   location: { pathname: string, query: RawQuery },
   project: { configuration?: { showHistory: boolean }, key: string, leakPeriodDate: string },
-  router: { push: ({ pathname: string, query?: RawQuery }) => void }
+  router: {
+    push: ({ pathname: string, query?: RawQuery }) => void,
+    replace: ({ pathname: string, query?: RawQuery }) => void
+  }
 };
 
 export type State = {
@@ -66,6 +70,13 @@ class ProjectActivityAppContainer extends React.PureComponent {
       metrics: [],
       query: parseQuery(props.location.query)
     };
+
+    if (this.shouldRedirect()) {
+      this.props.router.replace({
+        pathname: props.location.pathname,
+        query: serializeUrlQuery({ ...this.state.query, graph: getGraph() })
+      });
+    }
   }
 
   componentDidMount() {
@@ -225,18 +236,32 @@ class ProjectActivityAppContainer extends React.PureComponent {
   };
 
   updateQuery = (newQuery: Query) => {
+    const query = serializeUrlQuery({
+      ...this.state.query,
+      ...newQuery
+    });
+    saveGraph(query.graph);
     this.props.router.push({
       pathname: this.props.location.pathname,
       query: {
-        ...serializeUrlQuery({
-          ...this.state.query,
-          ...newQuery
-        }),
+        ...query,
         id: this.props.project.key
       }
     });
   };
 
+  shouldRedirect = () => {
+    const locationQuery = this.props.location.query;
+    if (locationQuery) {
+      const filtered = Object.keys(locationQuery).some(
+        key => key !== 'id' && locationQuery[key] !== ''
+      );
+
+      // if there is no filter, but there are saved preferences in the localStorage
+      return !filtered && getGraph();
+    }
+  };
+
   render() {
     return (
       <ProjectActivityApp
index ae651fd891f927274c3dc840806c371f088f0317..616633b0a2580961475c11cc401463c37ee59e6f 100644 (file)
@@ -38,12 +38,42 @@ export const GRAPHS_METRICS = {
   remediation: ['reliability_remediation_effort', 'sqale_index', 'security_remediation_effort']
 };
 
+export const activityQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
+  prevQuery.category !== nextQuery.category ||
+  prevQuery.from !== nextQuery.from ||
+  prevQuery.to !== nextQuery.to;
+
+export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
+  prevQuery.from !== nextQuery.from || prevQuery.to !== nextQuery.to;
+
+export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
+  prevQuery.graph !== nextQuery.graph;
+
+export const generateCoveredLinesMetric = (
+  uncoveredLines: MeasureHistory,
+  measuresHistory: Array<MeasureHistory>,
+  style: string
+) => {
+  const linesToCover = measuresHistory.find(measure => measure.metric === 'lines_to_cover');
+  return {
+    data: linesToCover
+      ? uncoveredLines.history.map((analysis, idx) => ({
+          x: analysis.date,
+          y: Number(linesToCover.history[idx].value) - Number(analysis.value)
+        }))
+      : [],
+    name: 'covered_lines',
+    style,
+    translatedName: translate('project_activity.custom_metric.covered_lines')
+  };
+};
+
 const parseGraph = (value?: string): string => {
   const graph = parseAsString(value);
   return GRAPH_TYPES.includes(graph) ? graph : 'overview';
 };
 
-const serializeGraph = (value: string): string => (value === 'overview' ? '' : value);
+const serializeGraph = (value: string): ?string => (value === 'overview' ? undefined : value);
 
 export const parseQuery = (urlQuery: RawQuery): Query => ({
   category: parseAsString(urlQuery['category']),
@@ -68,33 +98,3 @@ export const serializeUrlQuery = (query: Query): RawQuery => {
     to: serializeDate(query.to)
   });
 };
-
-export const activityQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
-  prevQuery.category !== nextQuery.category ||
-  prevQuery.from !== nextQuery.from ||
-  prevQuery.to !== nextQuery.to;
-
-export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
-  prevQuery.graph !== nextQuery.graph;
-
-export const datesQueryChanged = (prevQuery: Query, nextQuery: Query): boolean =>
-  prevQuery.from !== nextQuery.from || prevQuery.to !== nextQuery.to;
-
-export const generateCoveredLinesMetric = (
-  uncoveredLines: MeasureHistory,
-  measuresHistory: Array<MeasureHistory>,
-  style: string
-) => {
-  const linesToCover = measuresHistory.find(measure => measure.metric === 'lines_to_cover');
-  return {
-    data: linesToCover
-      ? uncoveredLines.history.map((analysis, idx) => ({
-          x: analysis.date,
-          y: Number(linesToCover.history[idx].value) - Number(analysis.value)
-        }))
-      : [],
-    name: 'covered_lines',
-    style,
-    translatedName: translate('project_activity.custom_metric.covered_lines')
-  };
-};
index 13078f320a564ce0d09cf6f8577ea49c44928f5b..cc8ebb4af9a4b9c3a4a161557971e4afce7dfb58 100644 (file)
@@ -28,6 +28,7 @@ import VisualizationsContainer from '../visualizations/VisualizationsContainer';
 import { parseUrlQuery } from '../store/utils';
 import { translate } from '../../../helpers/l10n';
 import * as utils from '../utils';
+import * as storage from '../../../helpers/storage';
 import type { RawQuery } from '../../../helpers/query';
 import '../styles.css';
 
@@ -78,14 +79,14 @@ export default class AllProjects extends React.PureComponent {
 
   getSavedOptions = () => {
     const options = {};
-    if (utils.getSort()) {
-      options.sort = utils.getSort();
+    if (storage.getSort()) {
+      options.sort = storage.getSort();
     }
-    if (utils.getView()) {
-      options.view = utils.getView();
+    if (storage.getView()) {
+      options.view = storage.getView();
     }
-    if (utils.getVisualization()) {
-      options.visualization = utils.getVisualization();
+    if (storage.getVisualization()) {
+      options.visualization = storage.getVisualization();
     }
     return options;
   };
@@ -108,15 +109,15 @@ export default class AllProjects extends React.PureComponent {
       this.updateLocationQuery(query);
     }
 
-    utils.saveSort(query.sort);
-    utils.saveView(query.view);
-    utils.saveVisualization(visualization);
+    storage.saveSort(query.sort);
+    storage.saveView(query.view);
+    storage.saveVisualization(visualization);
   };
 
   handleSortChange = (sort: string, desc: boolean) => {
     const asString = (desc ? '-' : '') + sort;
     this.updateLocationQuery({ sort: asString });
-    utils.saveSort(asString);
+    storage.saveSort(asString);
   };
 
   handleQueryChange(initialMount: boolean) {
index bab378af29e727740c7d9a22d63eba3561fcf70b..d306afcd9db48771ce59ff14fab22de69f06cdf5 100644 (file)
@@ -23,7 +23,7 @@ import { connect } from 'react-redux';
 import { withRouter } from 'react-router';
 import AllProjectsContainer from './AllProjectsContainer';
 import { getCurrentUser } from '../../../store/rootReducer';
-import { isFavoriteSet, isAllSet } from '../utils';
+import { isFavoriteSet, isAllSet } from '../../../helpers/storage';
 import { searchProjects } from '../../../api/components';
 import type { RawQuery } from '../../../helpers/query';
 
index 77cd3f97cc8546b3753c5938cff462e21927d8d7..d3eb8d65b831744ac722f3415fe180fed726ca4b 100644 (file)
@@ -21,7 +21,7 @@
 import React from 'react';
 import { IndexLink, Link } from 'react-router';
 import { translate } from '../../../helpers/l10n';
-import { saveAll, saveFavorite } from '../utils';
+import { saveAll, saveFavorite } from '../../../helpers/storage';
 import type { RawQuery } from '../../../helpers/query';
 
 type Props = {
index e2307b121751ef1da9622e3fbb2b89f00a1e4c9d..8c67972c076e8101f08945eccd5ecd021d114c0e 100644 (file)
@@ -17,7 +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 { saveAll } from './utils';
+import { saveAll } from '../../helpers/storage';
 
 const routes = [
   {
index 68b26f1b41cc73c397bc726c0c0792cf60754983..02f2ae59d59f19c5d19ad42d0010421a59739645 100644 (file)
 // @flow
 import { translate } from '../../helpers/l10n';
 
-const DEFAULT_FILTER = 'sonarqube.projects.default';
-const FAVORITE = 'favorite';
-const ALL = 'all';
-
-const VIEW = 'sonarqube.projects.view';
-const VISUALIZATION = 'sonarqube.projects.visualization';
-const SORT = 'sonarqube.projects.sort';
-
-export const isFavoriteSet = (): boolean => {
-  const setting = window.localStorage.getItem(DEFAULT_FILTER);
-  return setting === FAVORITE;
-};
-
-export const isAllSet = (): boolean => {
-  const setting = window.localStorage.getItem(DEFAULT_FILTER);
-  return setting === ALL;
-};
-
-const save = (key: string, value: ?string) => {
-  try {
-    if (value) {
-      window.localStorage.setItem(key, value);
-    } else {
-      window.localStorage.removeItem(key);
-    }
-  } catch (e) {
-    // usually that means the storage is full
-    // just do nothing in this case
-  }
-};
-
-export const saveAll = () => save(DEFAULT_FILTER, ALL);
-
-export const saveFavorite = () => save(DEFAULT_FILTER, FAVORITE);
-
-export const saveView = (view: ?string) => save(VIEW, view);
-export const getView = () => window.localStorage.getItem(VIEW);
-
-export const saveVisualization = (visualization: ?string) => save(VISUALIZATION, visualization);
-export const getVisualization = () => window.localStorage.getItem(VISUALIZATION);
-
-export const saveSort = (sort: ?string) => save(SORT, sort);
-export const getSort = () => window.localStorage.getItem(SORT);
-
 export const SORTING_METRICS = [
   { value: 'name' },
   { value: 'analysis_date' },
diff --git a/server/sonar-web/src/main/js/helpers/storage.js b/server/sonar-web/src/main/js/helpers/storage.js
new file mode 100644 (file)
index 0000000..ac6fa96
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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
+const PROJECTS_DEFAULT_FILTER = 'sonarqube.projects.default';
+const PROJECTS_FAVORITE = 'favorite';
+const PROJECTS_ALL = 'all';
+
+const PROJECTS_VIEW = 'sonarqube.projects.view';
+const PROJECTS_VISUALIZATION = 'sonarqube.projects.visualization';
+const PROJECTS_SORT = 'sonarqube.projects.sort';
+
+const PROJECT_ACTIVITY_GRAPH = 'sonarqube.project_activity.graph';
+
+const save = (key: string, value: ?string) => {
+  try {
+    if (value) {
+      window.localStorage.setItem(key, value);
+    } else {
+      window.localStorage.removeItem(key);
+    }
+  } catch (e) {
+    // usually that means the storage is full
+    // just do nothing in this case
+  }
+};
+
+export const saveFavorite = () => save(PROJECTS_DEFAULT_FILTER, PROJECTS_FAVORITE);
+export const isFavoriteSet = (): boolean => {
+  const setting = window.localStorage.getItem(PROJECTS_DEFAULT_FILTER);
+  return setting === PROJECTS_FAVORITE;
+};
+
+export const saveAll = () => save(PROJECTS_DEFAULT_FILTER, PROJECTS_ALL);
+export const isAllSet = (): boolean => {
+  const setting = window.localStorage.getItem(PROJECTS_DEFAULT_FILTER);
+  return setting === PROJECTS_ALL;
+};
+
+export const saveView = (view: ?string) => save(PROJECTS_VIEW, view);
+export const getView = () => window.localStorage.getItem(PROJECTS_VIEW);
+
+export const saveVisualization = (visualization: ?string) =>
+  save(PROJECTS_VISUALIZATION, visualization);
+export const getVisualization = () => window.localStorage.getItem(PROJECTS_VISUALIZATION);
+
+export const saveSort = (sort: ?string) => save(PROJECTS_SORT, sort);
+export const getSort = () => window.localStorage.getItem(PROJECTS_SORT);
+
+export const saveGraph = (graph: ?string) => save(PROJECT_ACTIVITY_GRAPH, graph);
+export const getGraph = () => window.localStorage.getItem(PROJECT_ACTIVITY_GRAPH);