@@ -33,13 +33,11 @@ import { getAllTimeMachineData } from '../../../api/time-machine'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { enhanceMeasuresWithMetrics } from '../../../helpers/measures'; | |||
import { getLeakPeriod } from '../../../helpers/periods'; | |||
import { get } from '../../../helpers/storage'; | |||
import { METRICS, HISTORY_METRICS_LIST } from '../utils'; | |||
import { | |||
DEFAULT_GRAPH, | |||
getDisplayedHistoryMetrics, | |||
PROJECT_ACTIVITY_GRAPH, | |||
PROJECT_ACTIVITY_GRAPH_CUSTOM | |||
getProjectActivityGraph | |||
} from '../../projectActivity/utils'; | |||
import { | |||
isSameBranchLike, | |||
@@ -51,22 +49,14 @@ import { getMetrics, Store } from '../../../store/rootReducer'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import '../styles.css'; | |||
interface OwnProps { | |||
interface Props { | |||
branchLike?: T.BranchLike; | |||
component: T.Component; | |||
fetchMetrics: () => void; | |||
onComponentChange: (changes: {}) => void; | |||
} | |||
interface StateToProps { | |||
metrics: { [key: string]: T.Metric }; | |||
} | |||
interface DispatchToProps { | |||
fetchMetrics: () => void; | |||
} | |||
type Props = StateToProps & DispatchToProps & OwnProps; | |||
interface State { | |||
history?: { | |||
[metric: string]: Array<{ date: Date; value?: string }>; | |||
@@ -100,40 +90,24 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
this.mounted = false; | |||
} | |||
loadMeasures() { | |||
const { branchLike, component } = this.props; | |||
this.setState({ loading: true }); | |||
getApplicationLeakPeriod = () => { | |||
return this.state.measures.find(measure => measure.metric.key === 'new_bugs') | |||
? ({ index: 1 } as T.Period) | |||
: undefined; | |||
}; | |||
return getMeasuresAndMeta(component.key, METRICS, { | |||
additionalFields: 'metrics,periods', | |||
...getBranchLikeQuery(branchLike) | |||
}).then( | |||
({ component, metrics, periods }) => { | |||
if (this.mounted && metrics && component.measures) { | |||
this.setState({ | |||
loading: false, | |||
measures: enhanceMeasuresWithMetrics(component.measures, metrics), | |||
periods | |||
}); | |||
} | |||
}, | |||
error => { | |||
throwGlobalError(error); | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
} | |||
isEmpty = () => { | |||
return ( | |||
this.state.measures === undefined || | |||
this.state.measures.find(measure => measure.metric.key === 'ncloc') === undefined | |||
); | |||
} | |||
}; | |||
loadHistory = () => { | |||
const { branchLike, component } = this.props; | |||
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM); | |||
let graphMetrics = getDisplayedHistoryMetrics( | |||
get(PROJECT_ACTIVITY_GRAPH) || 'issues', | |||
customGraphs ? customGraphs.split(',') : [] | |||
); | |||
const { graph, customGraphs } = getProjectActivityGraph(component.key); | |||
let graphMetrics = getDisplayedHistoryMetrics(graph, customGraphs); | |||
if (!graphMetrics || graphMetrics.length <= 0) { | |||
graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []); | |||
} | |||
@@ -159,24 +133,33 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
}); | |||
}; | |||
getApplicationLeakPeriod = () => | |||
this.state.measures.find(measure => measure.metric.key === 'new_bugs') | |||
? ({ index: 1 } as T.Period) | |||
: undefined; | |||
isEmpty = () => | |||
this.state.measures === undefined || | |||
this.state.measures.find(measure => measure.metric.key === 'ncloc') === undefined; | |||
loadMeasures = () => { | |||
const { branchLike, component } = this.props; | |||
this.setState({ loading: true }); | |||
renderLoading() { | |||
return ( | |||
<div className="text-center"> | |||
<i className="spinner spinner-margin" /> | |||
</div> | |||
return getMeasuresAndMeta(component.key, METRICS, { | |||
additionalFields: 'metrics,periods', | |||
...getBranchLikeQuery(branchLike) | |||
}).then( | |||
({ component, metrics, periods }) => { | |||
if (this.mounted && metrics && component.measures) { | |||
this.setState({ | |||
loading: false, | |||
measures: enhanceMeasuresWithMetrics(component.measures, metrics), | |||
periods | |||
}); | |||
} | |||
}, | |||
error => { | |||
throwGlobalError(error); | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
} | |||
); | |||
} | |||
}; | |||
renderEmpty() { | |||
renderEmpty = () => { | |||
const { component } = this.props; | |||
const isApp = component.qualifier === 'APP'; | |||
return ( | |||
@@ -191,9 +174,17 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
</h3> | |||
</div> | |||
); | |||
} | |||
}; | |||
renderMain() { | |||
renderLoading = () => { | |||
return ( | |||
<div className="text-center"> | |||
<i className="spinner spinner-margin" /> | |||
</div> | |||
); | |||
}; | |||
renderMain = () => { | |||
const { branchLike, component } = this.props; | |||
const { periods, measures, history, historyStartDate } = this.state; | |||
const leakPeriod = | |||
@@ -230,7 +221,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
</div> | |||
</div> | |||
); | |||
} | |||
}; | |||
render() { | |||
const { branchLike, component } = this.props; | |||
@@ -261,13 +252,9 @@ export class OverviewApp extends React.PureComponent<Props, State> { | |||
} | |||
} | |||
const mapDispatchToProps: DispatchToProps = { | |||
fetchMetrics | |||
}; | |||
const mapDispatchToProps = { fetchMetrics }; | |||
const mapStateToProps = (state: Store): StateToProps => ({ | |||
metrics: getMetrics(state) | |||
}); | |||
const mapStateToProps = (state: Store) => ({ metrics: getMetrics(state) }); | |||
export default connect( | |||
mapStateToProps, |
@@ -21,14 +21,12 @@ import * as React from 'react'; | |||
import { | |||
getDisplayedHistoryMetrics, | |||
DEFAULT_GRAPH, | |||
PROJECT_ACTIVITY_GRAPH, | |||
PROJECT_ACTIVITY_GRAPH_CUSTOM | |||
getProjectActivityGraph | |||
} from '../../projectActivity/utils'; | |||
import PreviewGraph from '../../../components/preview-graph/PreviewGraph'; | |||
import { getAllTimeMachineData } from '../../../api/time-machine'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { get } from '../../../helpers/storage'; | |||
interface Props { | |||
component: string; | |||
@@ -64,11 +62,8 @@ export default class Activity extends React.PureComponent<Props> { | |||
fetchHistory = () => { | |||
const { component } = this.props; | |||
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM); | |||
let graphMetrics = getDisplayedHistoryMetrics( | |||
get(PROJECT_ACTIVITY_GRAPH) || 'issues', | |||
customGraphs ? customGraphs.split(',') : [] | |||
); | |||
const { graph, customGraphs } = getProjectActivityGraph(component); | |||
let graphMetrics = getDisplayedHistoryMetrics(graph, customGraphs); | |||
if (!graphMetrics || graphMetrics.length <= 0) { | |||
graphMetrics = getDisplayedHistoryMetrics(DEFAULT_GRAPH, []); | |||
} |
@@ -17,36 +17,37 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* eslint-disable import/first, import/order */ | |||
jest.mock('../../../../helpers/storage', () => ({ | |||
get: (key: string) => (key === 'sonarqube.project_activity.graph.custom' ? 'coverage' : 'custom') | |||
})); | |||
jest.mock('../../../../api/time-machine', () => ({ | |||
getAllTimeMachineData: jest.fn(() => | |||
Promise.resolve({ | |||
measures: [ | |||
{ | |||
metric: 'coverage', | |||
history: [ | |||
{ date: '2017-01-01T00:00:00.000Z', value: '73' }, | |||
{ date: '2017-01-02T00:00:00.000Z', value: '82' } | |||
] | |||
} | |||
] | |||
}) | |||
) | |||
})); | |||
import * as React from 'react'; | |||
import { mount, shallow } from 'enzyme'; | |||
import Activity from '../Activity'; | |||
import { getAllTimeMachineData } from '../../../../api/time-machine'; | |||
import { getProjectActivityGraph } from '../../../projectActivity/utils'; | |||
jest.mock('../../../projectActivity/utils', () => { | |||
const utils = require.requireActual('../../../projectActivity/utils'); | |||
utils.getProjectActivityGraph = jest | |||
.fn() | |||
.mockReturnValue({ graph: 'custom', customGraphs: ['coverage'] }); | |||
return utils; | |||
}); | |||
const getAllTimeMachineData = require('../../../../api/time-machine') | |||
.getAllTimeMachineData as jest.Mock<any>; | |||
jest.mock('../../../../api/time-machine', () => ({ | |||
getAllTimeMachineData: jest.fn().mockResolvedValue({ | |||
measures: [ | |||
{ | |||
metric: 'coverage', | |||
history: [ | |||
{ date: '2017-01-01T00:00:00.000Z', value: '73' }, | |||
{ date: '2017-01-02T00:00:00.000Z', value: '82' } | |||
] | |||
} | |||
] | |||
}) | |||
})); | |||
beforeEach(() => { | |||
getAllTimeMachineData.mockClear(); | |||
(getAllTimeMachineData as jest.Mock).mockClear(); | |||
(getProjectActivityGraph as jest.Mock).mockClear(); | |||
}); | |||
it('renders', () => { | |||
@@ -62,6 +63,7 @@ it('renders', () => { | |||
metrics: [{ key: 'coverage' }] | |||
}); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(getProjectActivityGraph).toBeCalledWith('foo'); | |||
}); | |||
it('fetches history', () => { |
@@ -38,7 +38,7 @@ interface Props { | |||
deleteEvent: (analysis: string, event: string) => Promise<void>; | |||
graphLoading: boolean; | |||
initializing: boolean; | |||
project: Pick<T.Component, 'configuration' | 'leakPeriodDate' | 'qualifier'>; | |||
project: Pick<T.Component, 'configuration' | 'key' | 'leakPeriodDate' | 'qualifier'>; | |||
metrics: T.Metric[]; | |||
measuresHistory: MeasureHistory[]; | |||
query: Query; | |||
@@ -93,6 +93,7 @@ export default function ProjectActivityApp(props: Props) { | |||
loading={props.graphLoading} | |||
measuresHistory={measuresHistory} | |||
metrics={props.metrics} | |||
project={props.project.key} | |||
query={query} | |||
updateQuery={props.updateQuery} | |||
/> |
@@ -27,20 +27,18 @@ import * as api from '../../../api/projectActivity'; | |||
import * as actions from '../actions'; | |||
import { getBranchLikeQuery } from '../../../helpers/branches'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { get } from '../../../helpers/storage'; | |||
import { | |||
customMetricsChanged, | |||
DEFAULT_GRAPH, | |||
getHistoryMetrics, | |||
getProjectActivityGraph, | |||
isCustomGraph, | |||
parseQuery, | |||
PROJECT_ACTIVITY_GRAPH, | |||
PROJECT_ACTIVITY_GRAPH_CUSTOM, | |||
serializeQuery, | |||
serializeUrlQuery, | |||
MeasureHistory, | |||
parseQuery, | |||
ParsedAnalysis, | |||
Query, | |||
ParsedAnalysis | |||
serializeQuery, | |||
serializeUrlQuery | |||
} from '../utils'; | |||
import { RawQuery } from '../../../helpers/query'; | |||
@@ -81,10 +79,10 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro | |||
componentDidMount() { | |||
this.mounted = true; | |||
if (this.shouldRedirect()) { | |||
const newQuery = { ...this.state.query, graph: get(PROJECT_ACTIVITY_GRAPH) || 'issues' }; | |||
const { graph, customGraphs } = getProjectActivityGraph(this.props.component.key); | |||
const newQuery = { ...this.state.query, graph }; | |||
if (isCustomGraph(newQuery.graph)) { | |||
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM); | |||
newQuery.customMetrics = customGraphs ? customGraphs.split(',') : []; | |||
newQuery.customMetrics = customGraphs; | |||
} | |||
this.props.router.replace({ | |||
pathname: this.props.location.pathname, | |||
@@ -98,14 +96,14 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro | |||
} | |||
} | |||
componentWillReceiveProps(nextProps: Props) { | |||
if (nextProps.location.query !== this.props.location.query) { | |||
const query = parseQuery(nextProps.location.query); | |||
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 || customMetricsChanged(this.state.query, query)) { | |||
if (this.state.initialized) { | |||
this.updateGraphData(query.graph, query.customMetrics); | |||
} else { | |||
this.firstLoadData(query, nextProps.component); | |||
this.firstLoadData(query, this.props.component); | |||
} | |||
} | |||
this.setState({ query }); | |||
@@ -185,6 +183,26 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro | |||
); | |||
}; | |||
fetchAllActivities = (topLevelComponent: string) => { | |||
this.setState({ analysesLoading: true }); | |||
this.loadAllActivities(topLevelComponent).then( | |||
({ analyses, paging }) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
analyses, | |||
analysesLoading: false, | |||
paging | |||
}); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ analysesLoading: false }); | |||
} | |||
} | |||
); | |||
}; | |||
loadAllActivities = ( | |||
project: string, | |||
prevResult?: { analyses: ParsedAnalysis[]; paging: T.Paging } | |||
@@ -230,7 +248,6 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro | |||
if (this.mounted) { | |||
this.setState({ | |||
analyses: response[0].analyses, | |||
analysesLoading: true, | |||
graphLoading: false, | |||
initialized: true, | |||
measuresHistory: response[2], | |||
@@ -238,20 +255,12 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro | |||
paging: response[0].paging | |||
}); | |||
this.loadAllActivities(topLevelComponent).then(({ analyses, paging }) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
analyses, | |||
analysesLoading: false, | |||
paging | |||
}); | |||
} | |||
}); | |||
this.fetchAllActivities(topLevelComponent); | |||
} | |||
}, | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ initialized: true, analysesLoading: false, graphLoading: false }); | |||
this.setState({ initialized: true, graphLoading: false }); | |||
} | |||
} | |||
); | |||
@@ -298,10 +307,8 @@ export default class ProjectActivityAppContainer extends React.PureComponent<Pro | |||
key => key !== 'id' && locationQuery[key] !== '' | |||
); | |||
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM); | |||
const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues'; | |||
const emptyCustomGraph = | |||
isCustomGraph(graph) && customGraphs && customGraphs.split(',').length <= 0; | |||
const { graph, customGraphs } = getProjectActivityGraph(this.props.component.key); | |||
const emptyCustomGraph = isCustomGraph(graph) && customGraphs.length <= 0; | |||
// if there is no filter, but there are saved preferences in the localStorage | |||
// also don't redirect to custom if there is no metrics selected for it |
@@ -22,7 +22,7 @@ import { debounce, findLast, maxBy, minBy, sortBy } from 'lodash'; | |||
import ProjectActivityGraphsHeader from './ProjectActivityGraphsHeader'; | |||
import GraphsZoom from './GraphsZoom'; | |||
import GraphsHistory from './GraphsHistory'; | |||
import { get, save } from '../../../helpers/storage'; | |||
import { save } from '../../../helpers/storage'; | |||
import { | |||
datesQueryChanged, | |||
generateSeries, | |||
@@ -30,14 +30,15 @@ import { | |||
getSeriesMetricType, | |||
historyQueryChanged, | |||
isCustomGraph, | |||
MeasureHistory, | |||
ParsedAnalysis, | |||
Point, | |||
PROJECT_ACTIVITY_GRAPH, | |||
PROJECT_ACTIVITY_GRAPH_CUSTOM, | |||
splitSeriesInGraphs, | |||
MeasureHistory, | |||
Query, | |||
Serie, | |||
Point, | |||
ParsedAnalysis | |||
splitSeriesInGraphs, | |||
getProjectActivityGraph | |||
} from '../utils'; | |||
interface Props { | |||
@@ -46,6 +47,7 @@ interface Props { | |||
loading: boolean; | |||
measuresHistory: MeasureHistory[]; | |||
metrics: T.Metric[]; | |||
project: string; | |||
query: Query; | |||
updateQuery: (changes: Partial<Query>) => void; | |||
} | |||
@@ -77,23 +79,23 @@ export default class ProjectActivityGraphs extends React.PureComponent<Props, St | |||
this.updateQueryDateRange = debounce(this.updateQueryDateRange, 500); | |||
} | |||
componentWillReceiveProps(nextProps: Props) { | |||
componentDidUpdate(prevProps: Props) { | |||
let newSeries; | |||
let newGraphs; | |||
if ( | |||
nextProps.measuresHistory !== this.props.measuresHistory || | |||
historyQueryChanged(this.props.query, nextProps.query) | |||
prevProps.measuresHistory !== this.props.measuresHistory || | |||
historyQueryChanged(prevProps.query, this.props.query) | |||
) { | |||
newSeries = generateSeries( | |||
nextProps.measuresHistory, | |||
nextProps.query.graph, | |||
nextProps.metrics, | |||
getDisplayedHistoryMetrics(nextProps.query.graph, nextProps.query.customMetrics) | |||
this.props.measuresHistory, | |||
this.props.query.graph, | |||
this.props.metrics, | |||
getDisplayedHistoryMetrics(this.props.query.graph, this.props.query.customMetrics) | |||
); | |||
newGraphs = splitSeriesInGraphs(newSeries, MAX_GRAPH_NB, MAX_SERIES_PER_GRAPH); | |||
} | |||
const newDates = this.getStateZoomDates(this.props, nextProps, newSeries); | |||
const newDates = this.getStateZoomDates(prevProps, this.props, newSeries); | |||
if (newSeries || newDates) { | |||
let newState = {} as State; | |||
@@ -110,28 +112,27 @@ export default class ProjectActivityGraphs extends React.PureComponent<Props, St | |||
} | |||
} | |||
getStateZoomDates = (props: Props | undefined, nextProps: Props, newSeries?: Serie[]) => { | |||
getStateZoomDates = (prevProps: Props | undefined, props: Props, newSeries?: Serie[]) => { | |||
const newDates = { | |||
from: nextProps.query.from || undefined, | |||
to: nextProps.query.to || undefined | |||
from: props.query.from || undefined, | |||
to: props.query.to || undefined | |||
}; | |||
if (!props || datesQueryChanged(props.query, nextProps.query)) { | |||
if (!prevProps || datesQueryChanged(prevProps.query, props.query)) { | |||
return { graphEndDate: newDates.to, graphStartDate: newDates.from }; | |||
} | |||
if (newDates.to === undefined && newDates.from === undefined && newSeries !== undefined) { | |||
const series = newSeries ? newSeries : this.state.series; | |||
const firstValid = minBy( | |||
series.map(serie => serie.data.find(p => Boolean(p.y || p.y === 0))), | |||
newSeries.map(serie => serie.data.find(p => Boolean(p.y || p.y === 0))), | |||
'x' | |||
); | |||
const lastValid = maxBy<Point>( | |||
series.map(serie => findLast(serie.data, p => Boolean(p.y || p.y === 0))!), | |||
newSeries.map(serie => findLast(serie.data, p => Boolean(p.y || p.y === 0))!), | |||
'x' | |||
); | |||
return { | |||
graphEndDate: lastValid ? lastValid.x : newDates.to, | |||
graphStartDate: firstValid ? firstValid.x : newDates.from | |||
graphEndDate: lastValid && lastValid.x, | |||
graphStartDate: firstValid && firstValid.x | |||
}; | |||
} | |||
return null; | |||
@@ -148,21 +149,21 @@ export default class ProjectActivityGraphs extends React.PureComponent<Props, St | |||
addCustomMetric = (metric: string) => { | |||
const customMetrics = [...this.props.query.customMetrics, metric]; | |||
save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(',')); | |||
save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(','), this.props.project); | |||
this.props.updateQuery({ customMetrics }); | |||
}; | |||
removeCustomMetric = (removedMetric: string) => { | |||
const customMetrics = this.props.query.customMetrics.filter(metric => metric !== removedMetric); | |||
save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(',')); | |||
save(PROJECT_ACTIVITY_GRAPH_CUSTOM, customMetrics.join(','), this.props.project); | |||
this.props.updateQuery({ customMetrics }); | |||
}; | |||
updateGraph = (graph: string) => { | |||
save(PROJECT_ACTIVITY_GRAPH, graph); | |||
save(PROJECT_ACTIVITY_GRAPH, graph, this.props.project); | |||
if (isCustomGraph(graph) && this.props.query.customMetrics.length <= 0) { | |||
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM); | |||
this.props.updateQuery({ graph, customMetrics: customGraphs ? customGraphs.split(',') : [] }); | |||
const { customGraphs } = getProjectActivityGraph(this.props.project); | |||
this.props.updateQuery({ graph, customMetrics: customGraphs }); | |||
} else { | |||
this.props.updateQuery({ graph, customMetrics: [] }); | |||
} |
@@ -70,6 +70,7 @@ const DEFAULT_PROPS = { | |||
graphLoading: false, | |||
initializing: false, | |||
project: { | |||
key: 'foo', | |||
leakPeriodDate: '2017-05-16T13:50:02+0200', | |||
qualifier: 'TRK' | |||
}, |
@@ -61,6 +61,7 @@ const DEFAULT_PROPS: ProjectActivityGraphs['props'] = { | |||
} | |||
], | |||
metrics: METRICS, | |||
project: 'foo', | |||
query: { | |||
category: '', | |||
customMetrics: [], |
@@ -17,6 +17,7 @@ exports[`should render correctly 1`] = ` | |||
category="" | |||
project={ | |||
Object { | |||
"key": "foo", | |||
"leakPeriodDate": "2017-05-16T13:50:02+0200", | |||
"qualifier": "TRK", | |||
} | |||
@@ -78,6 +79,7 @@ exports[`should render correctly 1`] = ` | |||
initializing={false} | |||
project={ | |||
Object { | |||
"key": "foo", | |||
"leakPeriodDate": "2017-05-16T13:50:02+0200", | |||
"qualifier": "TRK", | |||
} | |||
@@ -162,6 +164,7 @@ exports[`should render correctly 1`] = ` | |||
}, | |||
] | |||
} | |||
project="foo" | |||
query={ | |||
Object { | |||
"category": "", |
@@ -30,6 +30,7 @@ import { | |||
} from '../../helpers/query'; | |||
import { parseDate, startOfDay } from '../../helpers/dates'; | |||
import { getLocalizedMetricName, translate } from '../../helpers/l10n'; | |||
import { get } from '../../helpers/storage'; | |||
export type ParsedAnalysis = T.Omit<T.Analysis, 'date'> & { date: Date }; | |||
@@ -84,12 +85,8 @@ export const GRAPHS_METRICS: { [x: string]: string[] } = { | |||
duplications: GRAPHS_METRICS_DISPLAYED['duplications'].concat(['duplicated_lines_density']) | |||
}; | |||
export const PROJECT_ACTIVITY_GRAPH = 'sonarqube.project_activity.graph'; | |||
export const PROJECT_ACTIVITY_GRAPH_CUSTOM = 'sonarqube.project_activity.graph.custom'; | |||
export function datesQueryChanged(prevQuery: Query, nextQuery: Query) { | |||
return !isEqual(prevQuery.from, nextQuery.from) || !isEqual(prevQuery.to, nextQuery.to); | |||
} | |||
export const PROJECT_ACTIVITY_GRAPH = 'sonar_project_activity.graph'; | |||
export const PROJECT_ACTIVITY_GRAPH_CUSTOM = 'sonar_project_activity.graph.custom'; | |||
export function activityQueryChanged(prevQuery: Query, nextQuery: Query) { | |||
return prevQuery.category !== nextQuery.category || datesQueryChanged(prevQuery, nextQuery); | |||
@@ -99,6 +96,10 @@ export function customMetricsChanged(prevQuery: Query, nextQuery: Query) { | |||
return !isEqual(prevQuery.customMetrics, nextQuery.customMetrics); | |||
} | |||
export function datesQueryChanged(prevQuery: Query, nextQuery: Query) { | |||
return !isEqual(prevQuery.from, nextQuery.from) || !isEqual(prevQuery.to, nextQuery.to); | |||
} | |||
export function hasDataValues(serie: Serie) { | |||
return serie.data.some(point => Boolean(point.y || point.y === 0)); | |||
} | |||
@@ -246,6 +247,14 @@ export function getHistoryMetrics(graph: string, customMetrics: string[]) { | |||
return isCustomGraph(graph) ? customMetrics : GRAPHS_METRICS[graph]; | |||
} | |||
export function getProjectActivityGraph(project: string) { | |||
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM, project); | |||
return { | |||
graph: get(PROJECT_ACTIVITY_GRAPH, project) || 'issues', | |||
customGraphs: customGraphs ? customGraphs.split(',') : [] | |||
}; | |||
} | |||
function parseGraph(value?: string) { | |||
const graph = parseAsString(value); | |||
return GRAPH_TYPES.includes(graph) ? graph : DEFAULT_GRAPH; |
@@ -24,16 +24,14 @@ import PreviewGraphTooltips from './PreviewGraphTooltips'; | |||
import AdvancedTimeline from '../charts/AdvancedTimeline'; | |||
import { | |||
DEFAULT_GRAPH, | |||
getDisplayedHistoryMetrics, | |||
generateSeries, | |||
getDisplayedHistoryMetrics, | |||
getProjectActivityGraph, | |||
getSeriesMetricType, | |||
hasHistoryDataValue, | |||
PROJECT_ACTIVITY_GRAPH, | |||
PROJECT_ACTIVITY_GRAPH_CUSTOM, | |||
splitSeriesInGraphs, | |||
Serie | |||
Serie, | |||
splitSeriesInGraphs | |||
} from '../../apps/projectActivity/utils'; | |||
import { get } from '../../helpers/storage'; | |||
import { formatMeasure, getShortType } from '../../helpers/measures'; | |||
import { getBranchLikeQuery } from '../../helpers/branches'; | |||
import { withRouter, Router } from '../hoc/withRouter'; | |||
@@ -67,9 +65,7 @@ const MAX_SERIES_PER_GRAPH = 3; | |||
class PreviewGraph extends React.PureComponent<Props, State> { | |||
constructor(props: Props) { | |||
super(props); | |||
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM); | |||
const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues'; | |||
const customMetrics = customGraphs ? customGraphs.split(',') : []; | |||
const { graph, customGraphs: customMetrics } = getProjectActivityGraph(props.project); | |||
const series = splitSeriesInGraphs( | |||
this.getSeries(props.history, graph, customMetrics, props.metrics), | |||
MAX_GRAPH_NB, | |||
@@ -82,13 +78,11 @@ class PreviewGraph extends React.PureComponent<Props, State> { | |||
}; | |||
} | |||
componentWillReceiveProps(nextProps: Props) { | |||
if (nextProps.history !== this.props.history || nextProps.metrics !== this.props.metrics) { | |||
const customGraphs = get(PROJECT_ACTIVITY_GRAPH_CUSTOM); | |||
const graph = get(PROJECT_ACTIVITY_GRAPH) || 'issues'; | |||
const customMetrics = customGraphs ? customGraphs.split(',') : []; | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.history !== this.props.history || prevProps.metrics !== this.props.metrics) { | |||
const { graph, customGraphs: customMetrics } = getProjectActivityGraph(this.props.project); | |||
const series = splitSeriesInGraphs( | |||
this.getSeries(nextProps.history, graph, customMetrics, nextProps.metrics), | |||
this.getSeries(this.props.history, graph, customMetrics, this.props.metrics), | |||
MAX_GRAPH_NB, | |||
MAX_SERIES_PER_GRAPH | |||
); |