From b41d68b7105b3d9704319183dcb7e573e6dd1386 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Thu, 22 Jun 2017 17:22:21 +0200 Subject: [PATCH] SONAR-9404 Remember last selected project history graph --- .../components/ProjectActivityAppContainer.js | 35 ++++++++-- .../src/main/js/apps/projectActivity/utils.js | 62 ++++++++--------- .../apps/projects/components/AllProjects.js | 21 +++--- .../components/DefaultPageSelector.js | 2 +- .../projects/components/FavoriteFilter.js | 2 +- .../src/main/js/apps/projects/routes.js | 2 +- .../src/main/js/apps/projects/utils.js | 44 ------------ .../sonar-web/src/main/js/helpers/storage.js | 67 +++++++++++++++++++ 8 files changed, 142 insertions(+), 93 deletions(-) create mode 100644 server/sonar-web/src/main/js/helpers/storage.js 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 22f52f533e5..58b855b49f0 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 @@ -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 ( + 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, + 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, - 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') - }; -}; diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js index 13078f320a5..cc8ebb4af9a 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js @@ -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) { diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js index bab378af29e..d306afcd9db 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js +++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js @@ -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'; diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js index 77cd3f97cc8..d3eb8d65b83 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js @@ -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 = { diff --git a/server/sonar-web/src/main/js/apps/projects/routes.js b/server/sonar-web/src/main/js/apps/projects/routes.js index e2307b12175..8c67972c076 100644 --- a/server/sonar-web/src/main/js/apps/projects/routes.js +++ b/server/sonar-web/src/main/js/apps/projects/routes.js @@ -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 = [ { diff --git a/server/sonar-web/src/main/js/apps/projects/utils.js b/server/sonar-web/src/main/js/apps/projects/utils.js index 68b26f1b41c..02f2ae59d59 100644 --- a/server/sonar-web/src/main/js/apps/projects/utils.js +++ b/server/sonar-web/src/main/js/apps/projects/utils.js @@ -20,50 +20,6 @@ // @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 index 00000000000..ac6fa964f5f --- /dev/null +++ b/server/sonar-web/src/main/js/helpers/storage.js @@ -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); -- 2.39.5