From: Grégoire Aubert Date: Fri, 16 Jun 2017 15:06:53 +0000 (+0200) Subject: SONAR-9401 Load gradually all projects history events and only at first load X-Git-Tag: 6.5-M2~49 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=81e84eee8fc89dffa83b8a9a0a8437b94be19f9f;p=sonarqube.git SONAR-9401 Load gradually all projects history events and only at first load --- diff --git a/server/sonar-web/src/main/js/api/time-machine.js b/server/sonar-web/src/main/js/api/time-machine.js index 2378a726302..6ed6de5bc77 100644 --- a/server/sonar-web/src/main/js/api/time-machine.js +++ b/server/sonar-web/src/main/js/api/time-machine.js @@ -50,10 +50,10 @@ export const getTimeMachineData = ( export const getAllTimeMachineData = ( component: string, metrics: Array, - other?: { p?: number, ps?: number, from?: string, to?: string }, + other?: { p?: number, from?: string, to?: string }, prev?: Response ): Promise => - getTimeMachineData(component, metrics, other).then((r: Response) => { + getTimeMachineData(component, metrics, { ...other, ps: 1000 }).then((r: Response) => { const result = prev ? { measures: prev.measures.map((measure, idx) => ({ @@ -64,15 +64,7 @@ export const getAllTimeMachineData = ( } : r; - if ( - // TODO Remove the sameAsPrevious condition when the webservice paging is working correctly ? - // Or keep it to be sure to not have an infinite loop ? - result.measures.every((measure, idx) => { - const equalToTotal = measure.history.length >= result.paging.total; - const sameAsPrevious = prev && measure.history.length === prev.measures[idx].history.length; - return equalToTotal || sameAsPrevious; - }) - ) { + if (result.paging.pageIndex * result.paging.pageSize >= result.paging.total) { return result; } return getAllTimeMachineData( diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js index 5ac56316836..c725c76d22c 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js @@ -63,6 +63,8 @@ const newEvent = { const emptyState = { analyses: [], + analysesLoading: false, + graphLoading: false, loading: false, measuresHistory: [], measures: [], diff --git a/server/sonar-web/src/main/js/apps/projectActivity/actions.js b/server/sonar-web/src/main/js/apps/projectActivity/actions.js index e4e36ff0f85..db482073f4e 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/actions.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/actions.js @@ -19,7 +19,7 @@ */ // @flow import type { Event } from './types'; -import type { State } from './components/ProjectActivityApp'; +import type { State } from './components/ProjectActivityAppContainer'; export const addCustomEvent = (analysis: string, event: Event) => (state: State) => ({ analyses: state.analyses.map(item => { diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js index 91064da44f7..5242772fef8 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js @@ -22,23 +22,21 @@ import React from 'react'; import { groupBy } from 'lodash'; import moment from 'moment'; import ProjectActivityAnalysis from './ProjectActivityAnalysis'; -import ProjectActivityPageFooter from './ProjectActivityPageFooter'; import FormattedDate from '../../../components/ui/FormattedDate'; import { translate } from '../../../helpers/l10n'; -import type { Analysis, Paging } from '../types'; +import type { Analysis } from '../types'; type Props = { addCustomEvent: (analysis: string, name: string, category?: string) => Promise<*>, addVersion: (analysis: string, version: string) => Promise<*>, analyses: Array, + analysesLoading: boolean, canAdmin: boolean, className?: string, changeEvent: (event: string, name: string) => Promise<*>, deleteAnalysis: (analysis: string) => Promise<*>, deleteEvent: (analysis: string, event: string) => Promise<*>, - fetchMoreActivity: () => void, - loading: boolean, - paging?: Paging + loading: boolean }; export default function ProjectActivityAnalysesList(props: Props) { @@ -84,13 +82,8 @@ export default function ProjectActivityAnalysesList(props: Props) { ))} + {props.analysesLoading &&
  • } - - ); } diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js index 0435340ddf4..266a7bf55fa 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js @@ -24,227 +24,102 @@ import moment from 'moment'; import ProjectActivityPageHeader from './ProjectActivityPageHeader'; import ProjectActivityAnalysesList from './ProjectActivityAnalysesList'; import ProjectActivityGraphs from './ProjectActivityGraphs'; -import throwGlobalError from '../../../app/utils/throwGlobalError'; -import * as api from '../../../api/projectActivity'; -import * as actions from '../actions'; -import { getAllTimeMachineData } from '../../../api/time-machine'; -import { getMetrics } from '../../../api/metrics'; -import { GRAPHS_METRICS, parseQuery, serializeQuery, serializeUrlQuery } from '../utils'; +import { GRAPHS_METRICS, activityQueryChanged } from '../utils'; import { translate } from '../../../helpers/l10n'; import './projectActivity.css'; -import type { Analysis, MeasureHistory, Metric, Query, Paging } from '../types'; -import type { RawQuery } from '../../../helpers/query'; +import type { Analysis, MeasureHistory, Metric, 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 } -}; - -export type State = { + addCustomEvent: (analysis: string, name: string, category?: string) => Promise<*>, + addVersion: (analysis: string, version: string) => Promise<*>, analyses: Array, + analysesLoading: boolean, + changeEvent: (event: string, name: string) => Promise<*>, + deleteAnalysis: (analysis: string) => Promise<*>, + deleteEvent: (analysis: string, event: string) => Promise<*>, loading: boolean, - measures: Array<*>, + project: { configuration?: { showHistory: boolean }, key: string, leakPeriodDate: string }, metrics: Array, measuresHistory: Array, - paging?: Paging, - query: Query + query: Query, + updateQuery: (newQuery: Query) => void +}; + +type State = { + filteredAnalyses: Array }; export default class ProjectActivityApp extends React.PureComponent { - mounted: boolean; props: Props; state: State; constructor(props: Props) { super(props); - this.state = { - analyses: [], - loading: true, - measures: [], - measuresHistory: [], - metrics: [], - query: parseQuery(props.location.query) - }; - } - - componentDidMount() { - this.mounted = true; - this.handleQueryChange(); - const elem = document.querySelector('html'); - elem && elem.classList.add('dashboard-page'); + this.state = { filteredAnalyses: this.filterAnalyses(props.analyses, props.query) }; } - componentDidUpdate(prevProps: Props) { - if (prevProps.location.query !== this.props.location.query) { - this.handleQueryChange(); + componentWillReceiveProps(nextProps: Props) { + if ( + nextProps.analyses !== this.props.analyses || + activityQueryChanged(this.props.query, nextProps.query) + ) { + this.setState({ + filteredAnalyses: this.filterAnalyses(nextProps.analyses, nextProps.query) + }); } } - componentWillUnmount() { - this.mounted = false; - const elem = document.querySelector('html'); - elem && elem.classList.remove('dashboard-page'); - } - - fetchActivity = ( - query: Query, - additional?: {} - ): Promise<{ analyses: Array, paging: Paging }> => { - const parameters = { - ...serializeQuery(query), - ...additional - }; - return api.getProjectActivity(parameters).catch(throwGlobalError); - }; - - fetchMetrics = (): Promise> => getMetrics().catch(throwGlobalError); - - fetchMeasuresHistory = (metrics: Array): Promise> => - getAllTimeMachineData(this.props.project.key, metrics).then( - ({ measures }) => - measures.map(measure => ({ - metric: measure.metric, - history: measure.history.map(analysis => ({ - date: moment(analysis.date).toDate(), - value: analysis.value - })) - })), - throwGlobalError - ); - - fetchMoreActivity = () => { - const { paging, query } = this.state; - if (!paging) { - return; + filterAnalyses = (analyses: Array, query: Query): Array => { + if (!query.category) { + return analyses; } - - this.setState({ loading: true }); - this.fetchActivity(query, { p: paging.pageIndex + 1 }).then(({ analyses, paging }) => { - if (this.mounted) { - this.setState((state: State) => ({ - analyses: state.analyses ? state.analyses.concat(analyses) : analyses, - loading: false, - paging - })); - } - }); + return analyses.filter( + analysis => analysis.events.find(event => event.category === query.category) != null + ); }; - addCustomEvent = (analysis: string, name: string, category?: string): Promise<*> => - api - .createEvent(analysis, name, category) - .then( - ({ analysis, ...event }) => - this.mounted && this.setState(actions.addCustomEvent(analysis, event)), - throwGlobalError - ); - - addVersion = (analysis: string, version: string): Promise<*> => - this.addCustomEvent(analysis, version, 'VERSION'); - - deleteEvent = (analysis: string, event: string): Promise<*> => - api - .deleteEvent(event) - .then( - () => this.mounted && this.setState(actions.deleteEvent(analysis, event)), - throwGlobalError - ); - - changeEvent = (event: string, name: string): Promise<*> => - api - .changeEvent(event, name) - .then( - ({ analysis, ...event }) => - this.mounted && this.setState(actions.changeEvent(analysis, event)), - throwGlobalError - ); - - deleteAnalysis = (analysis: string): Promise<*> => - api - .deleteAnalysis(analysis) - .then( - () => this.mounted && this.setState(actions.deleteAnalysis(analysis)), - throwGlobalError - ); - getMetricType = () => { - const metricKey = GRAPHS_METRICS[this.state.query.graph][0]; - const metric = this.state.metrics.find(metric => metric.key === metricKey); + const metricKey = GRAPHS_METRICS[this.props.query.graph][0]; + const metric = this.props.metrics.find(metric => metric.key === metricKey); return metric ? metric.type : 'INT'; }; - handleQueryChange() { - const query = parseQuery(this.props.location.query); - const graphMetrics = GRAPHS_METRICS[query.graph]; - this.setState({ loading: true, query }); - - Promise.all([ - this.fetchActivity(query), - this.fetchMetrics(), - this.fetchMeasuresHistory(graphMetrics) - ]).then(response => { - if (this.mounted) { - this.setState({ - analyses: response[0].analyses, - loading: false, - metrics: response[1], - measuresHistory: response[2], - paging: response[0].paging - }); - } - }); - } - - updateQuery = (newQuery: Query) => { - this.props.router.push({ - pathname: this.props.location.pathname, - query: { - ...serializeUrlQuery({ - ...this.state.query, - ...newQuery - }), - id: this.props.project.key - } - }); - }; - render() { - const { analyses, loading, query } = this.state; + const { loading, measuresHistory, query } = this.props; + const { filteredAnalyses } = this.state; const { configuration } = this.props.project; const canAdmin = configuration ? configuration.showHistory : false; return (
    - +
    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 9ed72083692..b6f96b945cf 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 @@ -18,13 +18,243 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // @flow +import React from 'react'; +import moment from 'moment'; import { connect } from 'react-redux'; import { withRouter } from 'react-router'; import ProjectActivityApp from './ProjectActivityApp'; +import throwGlobalError from '../../../app/utils/throwGlobalError'; import { getComponent } from '../../../store/rootReducer'; +import { getAllTimeMachineData } from '../../../api/time-machine'; +import { getMetrics } from '../../../api/metrics'; +import * as api from '../../../api/projectActivity'; +import * as actions from '../actions'; +import { GRAPHS_METRICS, parseQuery, serializeQuery, serializeUrlQuery } from '../utils'; +import type { RawQuery } from '../../../helpers/query'; +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 } +}; + +export type State = { + analyses: Array, + analysesLoading: boolean, + graphLoading: boolean, + loading: boolean, + metrics: Array, + measuresHistory: Array, + paging?: Paging, + query: Query +}; + +class ProjectActivityAppContainer extends React.PureComponent { + mounted: boolean; + props: Props; + state: State; + + constructor(props: Props) { + super(props); + this.state = { + analyses: [], + analysesLoading: false, + graphLoading: true, + loading: true, + measuresHistory: [], + metrics: [], + query: parseQuery(props.location.query) + }; + } + + componentDidMount() { + this.mounted = true; + this.firstLoadData(); + const elem = document.querySelector('html'); + elem && elem.classList.add('dashboard-page'); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.location.query !== this.props.location.query) { + const query = parseQuery(this.props.location.query); + if (query.graph !== this.state.query.graph) { + this.updateGraphData(query.graph); + } + this.setState({ query }); + } + } + + componentWillUnmount() { + this.mounted = false; + const elem = document.querySelector('html'); + elem && elem.classList.remove('dashboard-page'); + } + + addCustomEvent = (analysis: string, name: string, category?: string): Promise<*> => + api + .createEvent(analysis, name, category) + .then( + ({ analysis, ...event }) => + this.mounted && this.setState(actions.addCustomEvent(analysis, event)), + throwGlobalError + ); + + addVersion = (analysis: string, version: string): Promise<*> => + this.addCustomEvent(analysis, version, 'VERSION'); + + changeEvent = (event: string, name: string): Promise<*> => + api + .changeEvent(event, name) + .then( + ({ analysis, ...event }) => + this.mounted && this.setState(actions.changeEvent(analysis, event)), + throwGlobalError + ); + + deleteAnalysis = (analysis: string): Promise<*> => + api + .deleteAnalysis(analysis) + .then( + () => this.mounted && this.setState(actions.deleteAnalysis(analysis)), + throwGlobalError + ); + + deleteEvent = (analysis: string, event: string): Promise<*> => + api + .deleteEvent(event) + .then( + () => this.mounted && this.setState(actions.deleteEvent(analysis, event)), + throwGlobalError + ); + + fetchActivity = ( + project: string, + p: number, + ps: number, + additional?: { + [string]: string + } + ): Promise<{ analyses: Array, paging: Paging }> => { + const parameters = { project, p, ps }; + return api.getProjectActivity({ ...parameters, ...additional }).catch(throwGlobalError); + }; + + fetchMeasuresHistory = (metrics: Array): Promise> => + getAllTimeMachineData(this.props.project.key, metrics).then( + ({ measures }) => + measures.map(measure => ({ + metric: measure.metric, + history: measure.history.map(analysis => ({ + date: moment(analysis.date).toDate(), + value: analysis.value + })) + })), + throwGlobalError + ); + + fetchMetrics = (): Promise> => getMetrics().catch(throwGlobalError); + + loadAllActivities = ( + project: string, + prevResult?: { analyses: Array, paging: Paging } + ): Promise<{ analyses: Array, paging: Paging }> => { + if ( + prevResult && + prevResult.paging.pageIndex * prevResult.paging.pageSize >= prevResult.paging.total + ) { + return Promise.resolve(prevResult); + } + const nextPage = prevResult ? prevResult.paging.pageIndex + 1 : 1; + return this.fetchActivity(project, nextPage, 500).then(result => { + if (!prevResult) { + return this.loadAllActivities(project, result); + } + return this.loadAllActivities(project, { + analyses: prevResult.analyses.concat(result.analyses), + paging: result.paging + }); + }); + }; + + firstLoadData() { + const { query } = this.state; + const graphMetrics = GRAPHS_METRICS[query.graph]; + Promise.all([ + this.fetchActivity(query.project, 1, 100, serializeQuery(query)), + this.fetchMetrics(), + this.fetchMeasuresHistory(graphMetrics) + ]).then(response => { + if (this.mounted) { + this.setState({ + analyses: response[0].analyses, + analysesLoading: true, + graphLoading: false, + loading: false, + metrics: response[1], + measuresHistory: response[2], + paging: response[0].paging + }); + + this.loadAllActivities(query.project).then(({ analyses, paging }) => { + if (this.mounted) { + this.setState({ + analyses, + analysesLoading: false, + paging + }); + } + }); + } + }); + } + + updateGraphData = (graph: string) => { + this.setState({ graphLoading: true }); + return this.fetchMeasuresHistory( + GRAPHS_METRICS[graph] + ).then((measuresHistory: Array) => + this.setState({ graphLoading: false, measuresHistory }) + ); + }; + + updateQuery = (newQuery: Query) => { + this.props.router.push({ + pathname: this.props.location.pathname, + query: { + ...serializeUrlQuery({ + ...this.state.query, + ...newQuery + }), + id: this.props.project.key + } + }); + }; + + render() { + return ( + + ); + } +} const mapStateToProps = (state, ownProps) => ({ project: getComponent(state, ownProps.location.query.id) }); -export default connect(mapStateToProps)(withRouter(ProjectActivityApp)); +export default connect(mapStateToProps)(withRouter(ProjectActivityAppContainer)); diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js index 235ab128377..4e79f6b962c 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityGraphs.js @@ -21,7 +21,7 @@ import React from 'react'; import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader'; import StaticGraphs from './StaticGraphs'; -import { GRAPHS_METRICS_STYLE } from '../utils'; +import { GRAPHS_METRICS } from '../utils'; import type { RawQuery } from '../../../helpers/query'; import type { Analysis, MeasureHistory, Query } from '../types'; @@ -37,18 +37,19 @@ type Props = { }; export default function ProjectActivityGraphs(props: Props) { - const { graph } = props.query; + const { graph, category } = props.query; return (
    diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js index 29e3e16925f..47ba3989889 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js @@ -20,6 +20,7 @@ // @flow import React from 'react'; import Select from 'react-select'; +import { EVENT_TYPES } from '../utils'; import { translate } from '../../../helpers/l10n'; import type { RawQuery } from '../../../helpers/query'; @@ -29,18 +30,22 @@ type Props = { }; export default class ProjectActivityPageHeader extends React.PureComponent { + options: Array<{ label: string, value: string }>; props: Props; + constructor(props: Props) { + super(props); + this.options = EVENT_TYPES.map(category => ({ + label: translate('event.category', category), + value: category + })); + } + handleCategoryChange = (option: ?{ value: string }) => { this.props.updateQuery({ category: option ? option.value : '' }); }; render() { - const selectOptions = ['VERSION', 'QUALITY_GATE', 'QUALITY_PROFILE', 'OTHER'].map(category => ({ - label: translate('event.category', category), - value: category - })); - return (
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphs-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphs-test.js.snap new file mode 100644 index 00000000000..ba33441a2a6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphs-test.js.snap @@ -0,0 +1,91 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should correctly filter events 1`] = ` +Array [ + Object { + "className": "VERSION", + "date": 2016-10-26T10:17:29.000Z, + "name": "6.4", + }, + Object { + "className": "VERSION", + "date": 2016-10-27T14:33:50.000Z, + "name": "6.5-SNAPSHOT", + }, +] +`; + +exports[`should correctly filter events 2`] = ` +Array [ + Object { + "className": "OTHER", + "date": 2016-10-26T10:17:29.000Z, + "name": "foo", + }, +] +`; + +exports[`should correctly render a graph 1`] = ` +
    + +
    + +
    +
    +`; + +exports[`should show a loading view 1`] = ` +
    +
    + +
    +
    +`; + +exports[`should show that there is no data 1`] = ` +
    +
    + component_measures.no_history +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphsLegend-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphsLegend-test.js.snap new file mode 100644 index 00000000000..1fd564f69ef --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/StaticGraphsLegend-test.js.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly the list of series 1`] = ` +
    + + + Bugs + + + + Code Smells + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/utils.js b/server/sonar-web/src/main/js/apps/projectActivity/utils.js index e0c42628de7..d24e209c645 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/utils.js +++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.js @@ -23,28 +23,13 @@ import { translate } from '../../helpers/l10n'; import type { MeasureHistory, Query } from './types'; import type { RawQuery } from '../../helpers/query'; +export const EVENT_TYPES = ['VERSION', 'QUALITY_GATE', 'QUALITY_PROFILE', 'OTHER']; export const GRAPH_TYPES = ['overview', 'coverage', 'duplications', 'remediation']; export const GRAPHS_METRICS = { - overview: ['bugs', 'vulnerabilities', 'code_smells'], + overview: ['bugs', 'code_smells', 'vulnerabilities'], coverage: ['uncovered_lines', 'lines_to_cover'], duplications: ['duplicated_lines', 'ncloc'], - remediation: ['reliability_remediation_effort', 'security_remediation_effort', 'sqale_index'] -}; -export const GRAPHS_METRICS_STYLE = { - overview: { bugs: '0', code_smells: '1', vulnerabilities: '2' }, - coverage: { - lines_to_cover: '1', - uncovered_lines: '0' - }, - duplications: { - duplicated_lines: '0', - ncloc: '1' - }, - remediation: { - reliability_remediation_effort: '0', - security_remediation_effort: '2', - sqale_index: '1' - } + remediation: ['reliability_remediation_effort', 'sqale_index', 'security_remediation_effort'] }; const parseGraph = (value?: string): string => { @@ -74,6 +59,12 @@ export const serializeUrlQuery = (query: Query): RawQuery => { }); }; +export const activityQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => + prevQuery.category !== nextQuery.category; + +export const historyQueryChanged = (prevQuery: Query, nextQuery: Query): boolean => + prevQuery.graph !== nextQuery.graph; + export const generateCoveredLinesMetric = ( uncoveredLines: MeasureHistory, measuresHistory: Array diff --git a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js index a148053604b..ad24130a782 100644 --- a/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js +++ b/server/sonar-web/src/main/js/components/charts/AdvancedTimeline.js @@ -44,7 +44,8 @@ type Props = { leakPeriodDate: Date, padding: Array, series: Array, - showAreas?: boolean + showAreas?: boolean, + showEventMarkers?: boolean }; export default class AdvancedTimeline extends React.PureComponent { @@ -52,7 +53,7 @@ export default class AdvancedTimeline extends React.PureComponent { static defaultProps = { eventSize: 8, - padding: [10, 10, 10, 10] + padding: [25, 25, 30, 70] }; getRatingScale = (availableHeight: number) => @@ -199,16 +200,18 @@ export default class AdvancedTimeline extends React.PureComponent { if (!events || !eventSize) { return null; } - + const inRangeEvents = events.filter( + event => event.date >= xScale.domain()[0] && event.date <= xScale.domain()[1] + ); const offset = eventSize / 2; return ( - {events.map((event, idx) => ( + {inRangeEvents.map((event, idx) => ( ))} @@ -229,7 +232,7 @@ export default class AdvancedTimeline extends React.PureComponent { {this.renderTicks(xScale, yScale)} {this.props.showAreas && this.renderAreas(xScale, yScale)} {this.renderLines(xScale, yScale)} - {this.renderEvents(xScale, yScale)} + {this.props.showEventMarkers && this.renderEvents(xScale, yScale)} ); diff --git a/server/sonar-web/src/main/less/components/graphics.less b/server/sonar-web/src/main/less/components/graphics.less index f4422ba4322..c65a50abb5d 100644 --- a/server/sonar-web/src/main/less/components/graphics.less +++ b/server/sonar-web/src/main/less/components/graphics.less @@ -168,11 +168,11 @@ stroke-width: 2px; &.VERSION { - stroke: @green; + stroke: @blue; } &.QUALITY_GATE { - stroke: @blue; + stroke: @green; } &.QUALITY_PROFILE {