diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-12-03 13:50:17 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-01-16 09:43:04 +0100 |
commit | 6208b5a001ccfa21697463e0a780b9b3b3fee6e3 (patch) | |
tree | ef86ae6b72ea6bf2bd1fae16152ba6c25730840a /server/sonar-web/src/main/js | |
parent | 10e2a7a4152933a0c8a533d78255f737ed69c6da (diff) | |
download | sonarqube-6208b5a001ccfa21697463e0a780b9b3b3fee6e3.tar.gz sonarqube-6208b5a001ccfa21697463e0a780b9b3b3fee6e3.zip |
SONAR-11479 Display the measures of the currently selected directory on the Measures page (#1002)
Diffstat (limited to 'server/sonar-web/src/main/js')
28 files changed, 598 insertions, 860 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index 60317a41e39..7b97f66111c 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -94,6 +94,7 @@ export function getComponentTree( metrics: string[] = [], additional: RequestData = {} ): Promise<{ + baseComponent: T.ComponentMeasure; components: T.ComponentMeasure[]; metrics: T.Metric[]; paging: T.Paging; diff --git a/server/sonar-web/src/main/js/api/measures.ts b/server/sonar-web/src/main/js/api/measures.ts index 324ca966a54..5ebda3a570b 100644 --- a/server/sonar-web/src/main/js/api/measures.ts +++ b/server/sonar-web/src/main/js/api/measures.ts @@ -31,8 +31,11 @@ export function getMeasuresAndMeta( metrics: string[], additional: RequestData = {} ): Promise<{ component: T.ComponentMeasure; metrics?: T.Metric[]; periods?: T.Period[] }> { - const data = { ...additional, component, metricKeys: metrics.join(',') }; - return getJSON('/api/measures/component', data); + return getJSON('/api/measures/component', { + ...additional, + component, + metricKeys: metrics.join(',') + }).catch(throwGlobalError); } interface MeasuresForProjects { diff --git a/server/sonar-web/src/main/js/apps/code/utils.ts b/server/sonar-web/src/main/js/apps/code/utils.ts index baee517fccb..86f015f0e51 100644 --- a/server/sonar-web/src/main/js/apps/code/utils.ts +++ b/server/sonar-web/src/main/js/apps/code/utils.ts @@ -17,7 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { without } from 'lodash'; import { addComponent, getComponent as getComponentFromBucket, @@ -59,61 +58,12 @@ const LEAK_METRICS = [ const PAGE_SIZE = 100; -function requestChildren( - componentKey: string, - metrics: string[], - page: number, - branchLike?: T.BranchLike -): Promise<T.ComponentMeasure[]> { - return getChildren(componentKey, metrics, { - p: page, - ps: PAGE_SIZE, - ...getBranchLikeQuery(branchLike) - }).then(r => { - if (r.paging.total > r.paging.pageSize * r.paging.pageIndex) { - return requestChildren(componentKey, metrics, page + 1, branchLike).then(moreComponents => { - return [...r.components, ...moreComponents]; - }); - } - return r.components; - }); -} - -function requestAllChildren( - componentKey: string, - metrics: string[], - branchLike?: T.BranchLike -): Promise<T.ComponentMeasure[]> { - return requestChildren(componentKey, metrics, 1, branchLike); -} - interface Children { components: T.ComponentMeasure[]; page: number; total: number; } -interface ExpandRootDirFunc { - (children: Children): Promise<Children>; -} - -function expandRootDir(metrics: string[], branchLike?: T.BranchLike): ExpandRootDirFunc { - return function({ components, total, ...other }) { - const rootDir = components.find( - (component: T.ComponentMeasure) => component.qualifier === 'DIR' && component.name === '/' - ); - if (rootDir) { - return requestAllChildren(rootDir.key, metrics, branchLike).then(rootDirComponents => { - const nextComponents = without([...rootDirComponents, ...components], rootDir); - const nextTotal = total + rootDirComponents.length - /* root dir */ 1; - return { components: nextComponents, total: nextTotal, ...other }; - }); - } else { - return Promise.resolve({ components, total, ...other }); - } - }; -} - function prepareChildren(r: any): Children { return { components: r.components, @@ -202,7 +152,6 @@ export function retrieveComponentChildren( ...getBranchLikeQuery(branchLike) }) .then(prepareChildren) - .then(expandRootDir(metrics, branchLike)) .then(r => { addComponentChildren(componentKey, r.components, r.total, r.page); storeChildrenBase(r.components); @@ -268,7 +217,6 @@ export function loadMoreChildren( ...getBranchLikeQuery(branchLike) }) .then(prepareChildren) - .then(expandRootDir(metrics, branchLike)) .then(r => { addComponentChildren(componentKey, r.components, r.total, r.page); storeChildrenBase(r.components); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx index 51de9019d2f..551d4d9f80d 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/App.tsx @@ -19,9 +19,10 @@ */ import * as React from 'react'; import * as key from 'keymaster'; -import { InjectedRouter } from 'react-router'; +import { withRouter, WithRouterProps } from 'react-router'; import Helmet from 'react-helmet'; -import MeasureContentContainer from './MeasureContentContainer'; +import { keyBy } from 'lodash'; +import MeasureContent from './MeasureContent'; import MeasuresEmpty from './MeasuresEmpty'; import MeasureOverviewContainer from './MeasureOverviewContainer'; import Sidebar from '../sidebar/Sidebar'; @@ -35,7 +36,9 @@ import { hasFullMeasures, getMeasuresPageMetricKeys, groupByDomains, - sortMeasures + sortMeasures, + hasTreemap, + hasTree } from '../utils'; import { isSameBranchLike, @@ -55,62 +58,59 @@ import { removeSideBarClass, removeWhitePageClass } from '../../../helpers/pages'; -import { RawQuery } from '../../../helpers/query'; import '../../../components/search-navigator.css'; import '../style.css'; +import { getAllMetrics } from '../../../api/metrics'; +import { getMeasuresAndMeta } from '../../../api/measures'; +import { enhanceMeasure } from '../../../components/measure/utils'; +import { getLeakPeriod } from '../../../helpers/periods'; -interface Props { +interface Props extends WithRouterProps { branchLike?: T.BranchLike; component: T.ComponentMeasure; - location: { pathname: string; query: RawQuery }; - fetchMeasures: ( - component: string, - metricsKey: string[], - branchLike?: T.BranchLike - ) => Promise<{ - component: T.ComponentMeasure; - measures: T.MeasureEnhanced[]; - leakPeriod?: T.Period; - }>; - fetchMetrics: () => void; - metrics: { [metric: string]: T.Metric }; - metricsKey: string[]; - router: InjectedRouter; } interface State { + leakPeriod?: T.Period; loading: boolean; measures: T.MeasureEnhanced[]; - leakPeriod?: T.Period; + metrics: { [metric: string]: T.Metric }; } -export default class App extends React.PureComponent<Props, State> { +export class App extends React.PureComponent<Props, State> { mounted = false; - - constructor(props: Props) { - super(props); - this.state = { loading: true, measures: [] }; - } + state: State = { + loading: true, + measures: [], + metrics: {} + }; componentDidMount() { this.mounted = true; key.setScope('measures-files'); - this.props.fetchMetrics(); - this.fetchMeasures(this.props); + getAllMetrics().then( + metrics => { + const byKey = keyBy(metrics, 'key'); + this.setState({ metrics: byKey }); + this.fetchMeasures(byKey); + }, + () => {} + ); } - componentWillReceiveProps(nextProps: Props) { + componentDidUpdate(prevProps: Props, prevState: State) { + const prevQuery = parseQuery(prevProps.location.query); + const query = parseQuery(this.props.location.query); + if ( - !isSameBranchLike(nextProps.branchLike, this.props.branchLike) || - nextProps.component.key !== this.props.component.key || - nextProps.metrics !== this.props.metrics + !isSameBranchLike(prevProps.branchLike, this.props.branchLike) || + prevProps.component.key !== this.props.component.key || + prevQuery.selected !== query.selected ) { - this.fetchMeasures(nextProps); + this.fetchMeasures(this.state.metrics); } - } - componentDidUpdate(_prevProps: Props, prevState: State) { if (prevState.measures.length === 0 && this.state.measures.length > 0) { addWhitePageClass(); addSideBarClass(); @@ -124,13 +124,40 @@ export default class App extends React.PureComponent<Props, State> { key.deleteScope('measures-files'); } - fetchMeasures = ({ branchLike, component, fetchMeasures, metrics }: Props) => { - this.setState({ loading: true }); + fetchMeasures(metrics: State['metrics']) { + const { branchLike } = this.props; + const query = parseQuery(this.props.location.query); + const componentKey = query.selected || this.props.component.key; const filteredKeys = getMeasuresPageMetricKeys(metrics, branchLike); - fetchMeasures(component.key, filteredKeys, branchLike).then( - ({ measures, leakPeriod }) => { + + const banQualityGate = ({ measures = [], qualifier }: T.ComponentMeasure) => { + const bannedMetrics: string[] = []; + if (!['VW', 'SVW'].includes(qualifier)) { + bannedMetrics.push('alert_status'); + } + if (qualifier === 'APP') { + bannedMetrics.push('releasability_rating', 'releasability_effort'); + } + return measures.filter(measure => !bannedMetrics.includes(measure.metric)); + }; + + getMeasuresAndMeta(componentKey, filteredKeys, { + additionalFields: 'periods', + ...getBranchLikeQuery(branchLike) + }).then( + ({ component, periods }) => { if (this.mounted) { + const measures = banQualityGate(component).map(measure => + enhanceMeasure(measure, metrics) + ); + + const newBugs = measures.find(measure => measure.metric.key === 'new_bugs'); + const applicationPeriods = newBugs ? [{ index: 1 } as T.Period] : []; + const leakPeriod = getLeakPeriod( + component.qualifier === 'APP' ? applicationPeriods : periods + ); + this.setState({ loading: false, leakPeriod, @@ -146,7 +173,7 @@ export default class App extends React.PureComponent<Props, State> { } } ); - }; + } getHelmetTitle = (query: Query, displayOverview: boolean, metric?: T.Metric) => { if (displayOverview && query.metric) { @@ -164,7 +191,7 @@ export default class App extends React.PureComponent<Props, State> { if (displayOverview) { return undefined; } - const metric = this.props.metrics[query.metric]; + const metric = this.state.metrics[query.metric]; if (!metric) { const domainMeasures = groupByDomains(this.state.measures); const firstMeasure = @@ -177,14 +204,21 @@ export default class App extends React.PureComponent<Props, State> { }; updateQuery = (newQuery: Partial<Query>) => { - const query = serializeQuery({ - ...parseQuery(this.props.location.query), - ...newQuery - }); + const query: Query = { ...parseQuery(this.props.location.query), ...newQuery }; + + const metric = this.getSelectedMetric(query, false); + if (metric) { + if (query.view === 'treemap' && !hasTreemap(metric.key, metric.type)) { + query.view = 'tree'; + } else if (query.view === 'tree' && !hasTree(metric.key)) { + query.view = 'list'; + } + } + this.props.router.push({ pathname: this.props.location.pathname, query: { - ...query, + ...serializeQuery(query), ...getBranchLikeQuery(this.props.branchLike), id: this.props.component.key } @@ -192,7 +226,7 @@ export default class App extends React.PureComponent<Props, State> { }; renderContent = (displayOverview: boolean, query: Query, metric?: T.Metric) => { - const { branchLike, component, fetchMeasures, metrics } = this.props; + const { branchLike, component } = this.props; const { leakPeriod } = this.state; if (displayOverview) { return ( @@ -201,7 +235,7 @@ export default class App extends React.PureComponent<Props, State> { className="layout-page-main" domain={query.metric} leakPeriod={leakPeriod} - metrics={metrics} + metrics={this.state.metrics} rootComponent={component} router={this.props.router} selected={query.selected} @@ -229,13 +263,11 @@ export default class App extends React.PureComponent<Props, State> { } return ( - <MeasureContentContainer + <MeasureContent branchLike={branchLike} - className="layout-page-main" - fetchMeasures={fetchMeasures} leakPeriod={leakPeriod} - metric={metric} - metrics={metrics} + metrics={this.state.metrics} + requestedMetric={metric} rootComponent={component} router={this.props.router} selected={query.selected} @@ -246,16 +278,17 @@ export default class App extends React.PureComponent<Props, State> { }; render() { - const isLoading = this.state.loading || this.props.metricsKey.length <= 0; - if (isLoading) { + if (this.state.loading) { return <i className="spinner spinner-margin" />; } + const { branchLike } = this.props; const { measures } = this.state; const query = parseQuery(this.props.location.query); const hasOverview = hasFullMeasures(branchLike); const displayOverview = hasOverview && hasBubbleChart(query.metric); const metric = this.getSelectedMetric(query, displayOverview); + return ( <div id="component-measures"> <Suggestions suggestions="component_measures" /> @@ -288,3 +321,5 @@ export default class App extends React.PureComponent<Props, State> { ); } } + +export default withRouter(App); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.tsx deleted file mode 100644 index e7ea87f126e..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -import { Dispatch } from 'redux'; -import { connect } from 'react-redux'; -import { withRouter, WithRouterProps } from 'react-router'; -import App from './App'; -import throwGlobalError from '../../../app/utils/throwGlobalError'; -import { getMetrics, getMetricsKey } from '../../../store/rootReducer'; -import { fetchMetrics } from '../../../store/rootActions'; -import { getMeasuresAndMeta } from '../../../api/measures'; -import { getLeakPeriod } from '../../../helpers/periods'; -import { enhanceMeasure } from '../../../components/measure/utils'; -import { getBranchLikeQuery } from '../../../helpers/branches'; - -interface StateToProps { - metrics: { [metric: string]: T.Metric }; - metricsKey: string[]; -} - -interface DispatchToProps { - fetchMeasures: ( - component: string, - metricsKey: string[], - branchLike?: T.BranchLike - ) => Promise<{ - component: T.ComponentMeasure; - measures: T.MeasureEnhanced[]; - leakPeriod?: T.Period; - }>; - fetchMetrics: () => void; -} - -interface OwnProps { - branchLike?: T.BranchLike; - component: T.ComponentMeasure; -} - -const mapStateToProps = (state: any): StateToProps => ({ - metrics: getMetrics(state), - metricsKey: getMetricsKey(state) -}); - -function banQualityGate({ measures = [], qualifier }: T.ComponentMeasure): T.Measure[] { - const bannedMetrics: string[] = []; - if (!['VW', 'SVW'].includes(qualifier)) { - bannedMetrics.push('alert_status'); - } - if (qualifier === 'APP') { - bannedMetrics.push('releasability_rating', 'releasability_effort'); - } - return measures.filter(measure => !bannedMetrics.includes(measure.metric)); -} - -const fetchMeasures = (component: string, metricsKey: string[], branchLike?: T.BranchLike) => ( - _dispatch: Dispatch, - getState: () => any -) => { - if (metricsKey.length <= 0) { - return Promise.resolve({ component: {}, measures: [], leakPeriod: null }); - } - - return getMeasuresAndMeta(component, metricsKey, { - additionalFields: 'periods', - ...getBranchLikeQuery(branchLike) - }).then(({ component, periods }) => { - const measures = banQualityGate(component).map(measure => - enhanceMeasure(measure, getMetrics(getState())) - ); - - const newBugs = measures.find(measure => measure.metric.key === 'new_bugs'); - const applicationPeriods = newBugs ? [{ index: 1 } as T.Period] : []; - const leakPeriod = getLeakPeriod(component.qualifier === 'APP' ? applicationPeriods : periods); - return { component, measures, leakPeriod }; - }, throwGlobalError); -}; - -const mapDispatchToProps: DispatchToProps = { fetchMeasures: fetchMeasures as any, fetchMetrics }; - -export default withRouter<OwnProps>( - connect<StateToProps, DispatchToProps, OwnProps & WithRouterProps>( - mapStateToProps, - mapDispatchToProps - )(App) -); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx index d771d65dc59..e7202a2a15c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import * as key from 'keymaster'; import Breadcrumb from './Breadcrumb'; import { getBreadcrumbs } from '../../../api/components'; -import { getBranchLikeQuery } from '../../../helpers/branches'; +import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches'; interface Props { backToFirst: boolean; @@ -42,13 +42,16 @@ export default class Breadcrumbs extends React.PureComponent<Props, State> { componentDidMount() { this.mounted = true; - this.fetchBreadcrumbs(this.props); + this.fetchBreadcrumbs(); this.attachShortcuts(); } - componentWillReceiveProps(nextProps: Props) { - if (this.props.component !== nextProps.component) { - this.fetchBreadcrumbs(nextProps); + componentDidUpdate(prevProps: Props) { + if ( + this.props.component !== prevProps.component || + !isSameBranchLike(prevProps.branchLike, this.props.branchLike) + ) { + this.fetchBreadcrumbs(); } } @@ -72,7 +75,8 @@ export default class Breadcrumbs extends React.PureComponent<Props, State> { key.unbind('left', 'measures-files'); } - fetchBreadcrumbs = ({ branchLike, component, rootComponent }: Props) => { + fetchBreadcrumbs = () => { + const { branchLike, component, rootComponent } = this.props; const isRoot = component.key === rootComponent.key; if (isRoot) { if (this.mounted) { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx index 86982a68e0a..4b545348cdd 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx @@ -18,69 +18,72 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as classNames from 'classnames'; import { InjectedRouter } from 'react-router'; import Breadcrumbs from './Breadcrumbs'; import MeasureContentHeader from './MeasureContentHeader'; import MeasureHeader from './MeasureHeader'; import MeasureViewSelect from './MeasureViewSelect'; -import MetricNotFound from './MetricNotFound'; import PageActions from './PageActions'; -import FilesView from '../drilldown/FilesView'; +import { complementary } from '../config/complementary'; import CodeView from '../drilldown/CodeView'; +import FilesView from '../drilldown/FilesView'; import TreeMapView from '../drilldown/TreeMapView'; +import { Query, View, isFileType, enhanceComponent, isViewType } from '../utils'; import { getComponentTree } from '../../../api/components'; -import { complementary } from '../config/complementary'; -import { enhanceComponent, isFileType, isViewType, View } from '../utils'; -import { getProjectUrl } from '../../../helpers/urls'; -import { isDiffMetric } from '../../../helpers/measures'; import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'; -import DeferredSpinner from '../../../components/common/DeferredSpinner'; +import { isDiffMetric, getPeriodValue } from '../../../helpers/measures'; import { RequestData } from '../../../helpers/request'; +import { getProjectUrl } from '../../../helpers/urls'; +import { getMeasures } from '../../../api/measures'; interface Props { branchLike?: T.BranchLike; - className?: string; - component: T.ComponentMeasure; - loading: boolean; - loadingMore: boolean; leakPeriod?: T.Period; - measure?: T.MeasureEnhanced; - metric: T.Metric; + requestedMetric: Pick<T.Metric, 'key' | 'direction'>; metrics: { [metric: string]: T.Metric }; rootComponent: T.ComponentMeasure; router: InjectedRouter; - secondaryMeasure?: T.MeasureEnhanced; - updateLoading: (param: { [key: string]: boolean }) => void; - updateSelected: (component: string) => void; - updateView: (view: View) => void; + selected?: string; + updateQuery: (query: Partial<Query>) => void; view: View; } interface State { + baseComponent?: T.ComponentMeasure; components: T.ComponentMeasureEnhanced[]; + loading: boolean; + loadingMoreComponents: boolean; + measure?: T.Measure; metric?: T.Metric; paging?: T.Paging; + secondaryMeasure?: T.Measure; selected?: string; } export default class MeasureContent extends React.PureComponent<Props, State> { container?: HTMLElement | null; mounted = false; - state: State = { components: [] }; + state: State = { + components: [], + loading: true, + loadingMoreComponents: false + }; componentDidMount() { this.mounted = true; - this.fetchComponents(this.props); + this.fetchComponentTree(); } - componentWillReceiveProps(nextProps: Props) { + componentDidUpdate(prevProps: Props) { + const prevComponentKey = prevProps.selected || prevProps.rootComponent.key; + const componentKey = this.props.selected || this.props.rootComponent.key; if ( - !isSameBranchLike(nextProps.branchLike, this.props.branchLike) || - nextProps.component !== this.props.component || - nextProps.metric !== this.props.metric + prevComponentKey !== componentKey || + !isSameBranchLike(prevProps.branchLike, this.props.branchLike) || + prevProps.requestedMetric !== this.props.requestedMetric || + prevProps.view !== this.props.view ) { - this.fetchComponents(nextProps); + this.fetchComponentTree(); } } @@ -88,15 +91,95 @@ export default class MeasureContent extends React.PureComponent<Props, State> { this.mounted = false; } - getSelectedIndex = () => { - const componentKey = isFileType(this.props.component) - ? this.props.component.key - : this.state.selected; - const index = this.state.components.findIndex(component => component.key === componentKey); - return index !== -1 ? index : undefined; + fetchComponentTree = () => { + this.setState({ loading: true }); + const { metricKeys, opts, strategy } = this.getComponentRequestParams( + this.props.view, + this.props.requestedMetric + ); + const componentKey = this.props.selected || this.props.rootComponent.key; + const baseComponentMetrics = [this.props.requestedMetric.key]; + if (this.props.requestedMetric.key === 'ncloc') { + baseComponentMetrics.push('ncloc_language_distribution'); + } + Promise.all([ + getComponentTree(strategy, componentKey, metricKeys, opts), + getMeasures({ componentKey, metricKeys: baseComponentMetrics.join() }) + ]).then( + ([tree, measures]) => { + if (this.mounted) { + const metric = tree.metrics.find(m => m.key === this.props.requestedMetric.key); + const components = tree.components.map(component => + enhanceComponent(component, metric, this.props.metrics) + ); + + const measure = measures.find( + measure => measure.metric === this.props.requestedMetric.key + ); + const secondaryMeasure = measures.find( + measure => measure.metric !== this.props.requestedMetric.key + ); + + this.setState(({ selected }) => ({ + baseComponent: tree.baseComponent, + components, + measure, + metric, + paging: tree.paging, + secondaryMeasure, + selected: + components.length > 0 && components.find(c => c.key === selected) + ? selected + : undefined + })); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); }; - getComponentRequestParams = (view: View, metric: T.Metric, options: Object = {}) => { + fetchMoreComponents = () => { + const { metrics, view } = this.props; + const { baseComponent, metric, paging } = this.state; + if (!baseComponent || !paging || !metric) { + return; + } + const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric, { + p: paging.pageIndex + 1 + }); + this.setState({ loadingMoreComponents: true }); + getComponentTree(strategy, baseComponent.key, metricKeys, opts).then( + r => { + if (metric === this.props.requestedMetric) { + if (this.mounted) { + this.setState(state => ({ + components: [ + ...state.components, + ...r.components.map(component => enhanceComponent(component, metric, metrics)) + ], + paging: r.paging + })); + } + this.setState({ loadingMoreComponents: false }); + } + }, + () => { + if (this.mounted) { + this.setState({ loadingMoreComponents: false }); + } + } + ); + }; + + getComponentRequestParams( + view: View, + metric: Pick<T.Metric, 'key' | 'direction'>, + options: Object = {} + ) { const strategy = view === 'list' ? 'leaves' : 'children'; const metricKeys = [metric.key]; const opts: RequestData = { @@ -133,68 +216,16 @@ export default class MeasureContent extends React.PureComponent<Props, State> { } return { metricKeys, opts: { ...opts, ...options }, strategy }; - }; - - fetchComponents = ({ component, metric, metrics, view }: Props) => { - if (isFileType(component)) { - return; - } + } - const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric); - this.props.updateLoading({ components: true }); - getComponentTree(strategy, component.key, metricKeys, opts).then( - r => { - if (metric === this.props.metric) { - if (this.mounted) { - this.setState(({ selected }: State) => ({ - components: r.components.map(component => - enhanceComponent(component, metric, metrics) - ), - metric: { ...metric, ...r.metrics.find(m => m.key === metric.key) }, - paging: r.paging, - selected: - r.components.length > 0 && r.components.find(c => c.key === selected) - ? selected - : undefined, - view - })); - } - this.props.updateLoading({ components: false }); - } - }, - () => this.props.updateLoading({ components: false }) - ); + updateSelected = (component: string) => { + this.props.updateQuery({ + selected: component !== this.props.rootComponent.key ? component : undefined + }); }; - fetchMoreComponents = () => { - const { component, metric, metrics, view } = this.props; - const { paging } = this.state; - if (!paging) { - return; - } - const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric, { - p: paging.pageIndex + 1 - }); - this.props.updateLoading({ moreComponents: true }); - getComponentTree(strategy, component.key, metricKeys, opts).then( - r => { - if (metric === this.props.metric) { - if (this.mounted) { - this.setState(state => ({ - components: [ - ...state.components, - ...r.components.map(component => enhanceComponent(component, metric, metrics)) - ], - // merge to get the metric best value - metric: { ...metric, ...r.metrics.find(m => m.key === metric.key) }, - paging: r.paging - })); - } - this.props.updateLoading({ moreComponents: false }); - } - }, - () => this.props.updateLoading({ moreComponents: false }) - ); + updateView = (view: View) => { + this.props.updateQuery({ view }); }; onOpenComponent = (componentKey: string) => { @@ -209,26 +240,35 @@ export default class MeasureContent extends React.PureComponent<Props, State> { return; } } - this.setState({ selected: this.props.component.key }); - this.props.updateSelected(componentKey); + this.setState(state => ({ selected: state.baseComponent!.key })); + this.updateSelected(componentKey); if (this.container) { this.container.focus(); } }; - onSelectComponent = (componentKey: string) => this.setState({ selected: componentKey }); + onSelectComponent = (componentKey: string) => { + this.setState({ selected: componentKey }); + }; + + getSelectedIndex = () => { + const componentKey = isFileType(this.state.baseComponent!) + ? this.state.baseComponent!.key + : this.state.selected; + const index = this.state.components.findIndex(component => component.key === componentKey); + return index !== -1 ? index : undefined; + }; renderCode() { return ( <div className="measure-details-viewer"> <CodeView branchLike={this.props.branchLike} - component={this.props.component} + component={this.state.baseComponent!} components={this.state.components} leakPeriod={this.props.leakPeriod} - metric={this.props.metric} selectedIdx={this.getSelectedIndex()} - updateSelected={this.props.updateSelected} + updateSelected={this.updateSelected} /> </div> ); @@ -250,13 +290,14 @@ export default class MeasureContent extends React.PureComponent<Props, State> { fetchMore={this.fetchMoreComponents} handleOpen={this.onOpenComponent} handleSelect={this.onSelectComponent} - loadingMore={this.props.loadingMore} + loadingMore={this.state.loadingMoreComponents} metric={metric} metrics={this.props.metrics} paging={this.state.paging} rootComponent={this.props.rootComponent} selectedIdx={selectedIdx} selectedKey={selectedIdx !== undefined ? this.state.selected : undefined} + view={view} /> ); } else { @@ -272,13 +313,20 @@ export default class MeasureContent extends React.PureComponent<Props, State> { } render() { - const { branchLike, component, measure, metric, rootComponent, view } = this.props; - const isFile = isFileType(component); + const { branchLike, rootComponent, view } = this.props; + const { baseComponent, measure, metric, secondaryMeasure } = this.state; + + if (!baseComponent || !metric) { + return null; + } + + const measureValue = + measure && (isDiffMetric(measure.metric) ? getPeriodValue(measure, 1) : measure.value); + const isFile = isFileType(baseComponent); const selectedIdx = this.getSelectedIndex(); + return ( - <div - className={classNames('no-outline', this.props.className)} - ref={container => (this.container = container)}> + <div className="layout-page-main no-outline" ref={container => (this.container = container)}> <div className="layout-page-header-panel layout-page-main-header"> <div className="layout-page-header-panel-inner layout-page-main-header-inner"> <div className="layout-page-main-inner"> @@ -288,21 +336,22 @@ export default class MeasureContent extends React.PureComponent<Props, State> { backToFirst={view === 'list'} branchLike={branchLike} className="text-ellipsis flex-1" - component={component} + component={baseComponent} handleSelect={this.onOpenComponent} rootComponent={rootComponent} /> } right={ <div className="display-flex-center"> - {!isFile && ( - <MeasureViewSelect - className="measure-view-select big-spacer-right" - handleViewChange={this.props.updateView} - metric={metric} - view={view} - /> - )} + {!isFile && + metric && ( + <MeasureViewSelect + className="measure-view-select big-spacer-right" + handleViewChange={this.updateView} + metric={metric} + view={view} + /> + )} <PageActions current={ selectedIdx !== undefined && view !== 'treemap' @@ -320,22 +369,18 @@ export default class MeasureContent extends React.PureComponent<Props, State> { </div> </div> </div> - {!metric && <MetricNotFound className="layout-page-main-inner measure-details-content" />} - {metric && ( - <div className="layout-page-main-inner measure-details-content"> - <MeasureHeader - branchLike={branchLike} - component={component} - leakPeriod={this.props.leakPeriod} - measure={measure} - metric={metric} - secondaryMeasure={this.props.secondaryMeasure} - /> - <DeferredSpinner loading={this.props.loading}> - {isFileType(component) ? this.renderCode() : this.renderMeasure()} - </DeferredSpinner> - </div> - )} + + <div className="layout-page-main-inner measure-details-content"> + <MeasureHeader + branchLike={branchLike} + component={baseComponent} + leakPeriod={this.props.leakPeriod} + measureValue={measureValue} + metric={metric} + secondaryMeasure={secondaryMeasure} + /> + {isFile ? this.renderCode() : this.renderMeasure()} + </div> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx deleted file mode 100644 index cd6edf18d8b..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -import * as React from 'react'; -import { InjectedRouter } from 'react-router'; -import MeasureContent from './MeasureContent'; -import { Query, View } from '../utils'; - -interface Props { - branchLike?: T.BranchLike; - className?: string; - rootComponent: T.ComponentMeasure; - fetchMeasures: ( - component: string, - metricsKey: string[], - branchLike?: T.BranchLike - ) => Promise<{ component: T.ComponentMeasure; measures: T.MeasureEnhanced[] }>; - leakPeriod?: T.Period; - metric: T.Metric; - metrics: { [metric: string]: T.Metric }; - router: InjectedRouter; - selected?: string; - updateQuery: (query: Partial<Query>) => void; - view: View; -} - -interface LoadingState { - measure: boolean; - components: boolean; - moreComponents: boolean; -} - -interface State { - component?: T.ComponentMeasure; - loading: LoadingState; - measure?: T.MeasureEnhanced; - secondaryMeasure?: T.MeasureEnhanced; -} - -export default class MeasureContentContainer extends React.PureComponent<Props, State> { - mounted = false; - state: State = { loading: { measure: false, components: false, moreComponents: false } }; - - componentDidMount() { - this.mounted = true; - this.fetchMeasure(this.props); - } - - componentWillReceiveProps(nextProps: Props) { - const { component } = this.state; - const componentChanged = - !component || - nextProps.rootComponent.key !== component.key || - nextProps.selected !== component.key; - if (componentChanged || nextProps.metric !== this.props.metric) { - this.fetchMeasure(nextProps); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchMeasure = ({ branchLike, rootComponent, fetchMeasures, metric, selected }: Props) => { - this.updateLoading({ measure: true }); - - const metricKeys = [metric.key]; - if (metric.key === 'ncloc') { - metricKeys.push('ncloc_language_distribution'); - } - - fetchMeasures(selected || rootComponent.key, metricKeys, branchLike).then( - ({ component, measures }) => { - if (this.mounted) { - const measure = measures.find(measure => measure.metric.key === metric.key); - const secondaryMeasure = measures.find(measure => measure.metric.key !== metric.key); - this.setState({ component, measure, secondaryMeasure }); - this.updateLoading({ measure: false }); - } - }, - () => this.updateLoading({ measure: false }) - ); - }; - - updateLoading = (loading: Partial<LoadingState>) => { - if (this.mounted) { - this.setState(state => ({ loading: { ...state.loading, ...loading } })); - } - }; - - updateSelected = (component: string) => { - this.props.updateQuery({ - selected: component !== this.props.rootComponent.key ? component : undefined - }); - }; - - updateView = (view: View) => { - this.props.updateQuery({ view }); - }; - - render() { - if (!this.state.component) { - return null; - } - - return ( - <MeasureContent - branchLike={this.props.branchLike} - className={this.props.className} - component={this.state.component} - leakPeriod={this.props.leakPeriod} - loading={this.state.loading.measure || this.state.loading.components} - loadingMore={this.state.loading.moreComponents} - measure={this.state.measure} - metric={this.props.metric} - metrics={this.props.metrics} - rootComponent={this.props.rootComponent} - router={this.props.router} - secondaryMeasure={this.state.secondaryMeasure} - updateLoading={this.updateLoading} - updateSelected={this.updateSelected} - updateView={this.updateView} - view={this.props.view} - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx index 2f261e24198..9c1b1898cfa 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx @@ -34,13 +34,13 @@ interface Props { branchLike?: T.BranchLike; component: T.ComponentMeasure; leakPeriod?: T.Period; - measure?: T.MeasureEnhanced; + measureValue?: string; metric: T.Metric; - secondaryMeasure?: T.MeasureEnhanced; + secondaryMeasure?: T.Measure; } export default function MeasureHeader(props: Props) { - const { branchLike, component, leakPeriod, measure, metric, secondaryMeasure } = props; + const { branchLike, component, leakPeriod, measureValue, metric, secondaryMeasure } = props; const isDiff = isDiffMetric(metric.key); const hasHistory = component.qualifier !== 'FIL' && component.qualifier !== 'UTS' && hasFullMeasures(branchLike); @@ -53,20 +53,12 @@ export default function MeasureHeader(props: Props) { {getLocalizedMetricName(metric)} <span className="measure-details-value spacer-left"> <strong> - {isDiff ? ( - <Measure - className="leak-box" - metricKey={metric.key} - metricType={metric.type} - value={measure && measure.leak} - /> - ) : ( - <Measure - metricKey={metric.key} - metricType={metric.type} - value={measure && measure.value} - /> - )} + <Measure + className={isDiff ? 'leak-box' : undefined} + metricKey={metric.key} + metricType={metric.type} + value={measureValue} + /> </strong> </span> {!isDiff && @@ -88,7 +80,7 @@ export default function MeasureHeader(props: Props) { </div> </div> {secondaryMeasure && - secondaryMeasure.metric.key === 'ncloc_language_distribution' && + secondaryMeasure.metric === 'ncloc_language_distribution' && secondaryMeasure.value !== undefined && ( <div className="measure-details-secondary"> <LanguageDistributionContainer diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx index e18c6821108..cc871ea922f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx @@ -26,7 +26,7 @@ import BubbleChart from '../drilldown/BubbleChart'; import SourceViewer from '../../../components/SourceViewer/SourceViewer'; import { getComponentLeaves } from '../../../api/components'; import { enhanceComponent, getBubbleMetrics, isFileType } from '../utils'; -import { getBranchLikeQuery } from '../../../helpers/branches'; +import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; interface Props { @@ -55,16 +55,17 @@ export default class MeasureOverview extends React.PureComponent<Props, State> { componentDidMount() { this.mounted = true; - this.fetchComponents(this.props); + this.fetchComponents(); } - componentWillReceiveProps(nextProps: Props) { + componentDidUpdate(prevProps: Props) { if ( - nextProps.component !== this.props.component || - nextProps.metrics !== this.props.metrics || - nextProps.domain !== this.props.domain + prevProps.component !== this.props.component || + !isSameBranchLike(prevProps.branchLike, this.props.branchLike) || + prevProps.metrics !== this.props.metrics || + prevProps.domain !== this.props.domain ) { - this.fetchComponents(nextProps); + this.fetchComponents(); } } @@ -72,8 +73,8 @@ export default class MeasureOverview extends React.PureComponent<Props, State> { this.mounted = false; } - fetchComponents = (props: Props) => { - const { branchLike, component, domain, metrics } = props; + fetchComponents = () => { + const { branchLike, component, domain, metrics } = this.props; if (isFileType(component)) { this.setState({ components: [], paging: undefined }); return; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx index 200fce078a2..af26325e68a 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx @@ -23,7 +23,7 @@ import MeasureOverview from './MeasureOverview'; import { getComponentShow } from '../../../api/components'; import { getProjectUrl } from '../../../helpers/urls'; import { isViewType, Query } from '../utils'; -import { getBranchLikeQuery } from '../../../helpers/branches'; +import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branches'; interface Props { branchLike?: T.BranchLike; @@ -56,17 +56,18 @@ export default class MeasureOverviewContainer extends React.PureComponent<Props, componentDidMount() { this.mounted = true; - this.fetchComponent(this.props); + this.fetchComponent(); } - componentWillReceiveProps(nextProps: Props) { - const { component } = this.state; - const componentChanged = - !component || - nextProps.rootComponent.key !== component.key || - nextProps.selected !== component.key; - if (componentChanged || nextProps.domain !== this.props.domain) { - this.fetchComponent(nextProps); + componentDidUpdate(prevProps: Props) { + const prevComponentKey = prevProps.selected || prevProps.rootComponent.key; + const componentKey = this.props.selected || this.props.rootComponent.key; + if ( + prevComponentKey !== componentKey || + !isSameBranchLike(prevProps.branchLike, this.props.branchLike) || + prevProps.domain !== this.props.domain + ) { + this.fetchComponent(); } } @@ -74,7 +75,8 @@ export default class MeasureOverviewContainer extends React.PureComponent<Props, this.mounted = false; } - fetchComponent = ({ branchLike, rootComponent, selected }: Props) => { + fetchComponent = () => { + const { branchLike, rootComponent, selected } = this.props; if (!selected || rootComponent.key === selected) { this.setState({ component: rootComponent }); this.updateLoading({ component: false }); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx deleted file mode 100644 index 6985137bfbd..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 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. - */ -import * as React from 'react'; -import { translate } from '../../../helpers/l10n'; -import { Alert } from '../../../components/ui/Alert'; - -// TODO seems like this component is used by never rendered in real life -export default function MetricNotFound({ className }: { className?: string }) { - return ( - <div className={className}> - <Alert variant="error">{translate('component_measures.not_found')}</Alert> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx index de3394ecc88..ad3c05ee775 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx @@ -19,44 +19,66 @@ */ import * as React from 'react'; import { shallow } from 'enzyme'; -import App from '../App'; +import { Location } from 'history'; +import { App } from '../App'; import { waitAndUpdate } from '../../../../helpers/testUtils'; +import { getMeasuresAndMeta } from '../../../../api/measures'; -const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' }; +jest.mock('../../../../api/metrics', () => ({ + getAllMetrics: jest.fn().mockResolvedValue([ + { + id: '1', + key: 'lines_to_cover', + type: 'INT', + name: 'Lines to Cover', + domain: 'Coverage' + }, + { + id: '2', + key: 'coverage', + type: 'PERCENT', + name: 'Coverage', + domain: 'Coverage' + }, + { + id: '3', + key: 'duplicated_lines_density', + type: 'PERCENT', + name: 'Duplicated Lines (%)', + domain: 'Duplications' + }, + { + id: '4', + key: 'new_bugs', + type: 'INT', + name: 'New Bugs', + domain: 'Reliability' + } + ]) +})); -const METRICS = { - lines_to_cover: { - id: '1', - key: 'lines_to_cover', - type: 'INT', - name: 'Lines to Cover', - domain: 'Coverage' - }, - coverage: { id: '2', key: 'coverage', type: 'PERCENT', name: 'Coverage', domain: 'Coverage' }, - duplicated_lines_density: { - id: '3', - key: 'duplicated_lines_density', - type: 'PERCENT', - name: 'Duplicated Lines (%)', - domain: 'Duplications' - }, - new_bugs: { id: '4', key: 'new_bugs', type: 'INT', name: 'New Bugs', domain: 'Reliability' } -}; +jest.mock('../../../../api/measures', () => ({ + getMeasuresAndMeta: jest.fn() +})); + +const COMPONENT = { key: 'foo', name: 'Foo', qualifier: 'TRK' }; const PROPS: App['props'] = { branchLike: { isMain: true, name: 'master' }, component: COMPONENT, - location: { pathname: '/component_measures', query: { metric: 'coverage' } }, - fetchMeasures: jest.fn().mockResolvedValue({ - component: COMPONENT, - measures: [{ metric: 'coverage', value: '80.0' }] - }), - fetchMetrics: jest.fn(), - metrics: METRICS, - metricsKey: ['lines_to_cover', 'coverage', 'duplicated_lines_density', 'new_bugs'], - router: { push: jest.fn() } as any + location: { pathname: '/component_measures', query: { metric: 'coverage' } } as Location, + params: {}, + router: { push: jest.fn() } as any, + routes: [] }; +beforeEach(() => { + (getMeasuresAndMeta as jest.Mock).mockResolvedValue({ + component: { measures: [{ metric: 'coverage', value: '80.0' }] }, + periods: [{ index: '1' }] + }); +}); + it('should render correctly', async () => { const wrapper = shallow(<App {...PROPS} />); expect(wrapper.find('.spinner')).toHaveLength(1); @@ -68,7 +90,7 @@ it('should render a measure overview', async () => { const wrapper = shallow( <App {...PROPS} - location={{ pathname: '/component_measures', query: { metric: 'Reliability' } }} + location={{ pathname: '/component_measures', query: { metric: 'Reliability' } } as Location} /> ); expect(wrapper.find('.spinner')).toHaveLength(1); @@ -77,8 +99,11 @@ it('should render a measure overview', async () => { }); it('should render a message when there are no measures', async () => { - const fetchMeasures = jest.fn().mockResolvedValue({ component: COMPONENT, measures: [] }); - const wrapper = shallow(<App {...PROPS} fetchMeasures={fetchMeasures} />); + (getMeasuresAndMeta as jest.Mock).mockResolvedValue({ + component: { measures: [] }, + periods: [{ index: '1' }] + }); + const wrapper = shallow(<App {...PROPS} />); await waitAndUpdate(wrapper); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx index e00c0b57912..4fdc255b5a3 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx @@ -28,13 +28,6 @@ const METRIC = { name: 'Reliability Rating' }; -const MEASURE = { - value: '3.0', - periods: [{ index: 1, value: '0.0' }], - metric: METRIC, - leak: '0.0' -}; - const LEAK_METRIC = { id: '2', key: 'new_reliability_rating', @@ -42,20 +35,11 @@ const LEAK_METRIC = { name: 'Reliability Rating on New Code' }; -const LEAK_MEASURE = { - periods: [{ index: 1, value: '3.0' }], - metric: LEAK_METRIC, - leak: '3.0' -}; +const LEAK_MEASURE = '3.0'; const SECONDARY = { value: 'java=175123;js=26382', - metric: { - id: '3', - key: 'ncloc_language_distribution', - type: 'DATA', - name: 'Lines of Code Per Language' - } + metric: 'ncloc_language_distribution' }; const PROPS = { @@ -66,7 +50,7 @@ const PROPS = { mode: 'previous_version', parameter: '6,4' } as T.Period, - measure: MEASURE, + measureValue: '3.0', metric: METRIC }; @@ -76,7 +60,7 @@ it('should render correctly', () => { it('should render correctly for leak', () => { expect( - shallow(<MeasureHeader {...PROPS} measure={LEAK_MEASURE} metric={LEAK_METRIC} />) + shallow(<MeasureHeader {...PROPS} measureValue={LEAK_MEASURE} metric={LEAK_METRIC} />) ).toMatchSnapshot(); }); @@ -94,7 +78,7 @@ it('should render with short living branch', () => { <MeasureHeader {...PROPS} branchLike={shortBranch} - measure={LEAK_MEASURE} + measureValue={LEAK_MEASURE} metric={LEAK_METRIC} /> ) @@ -121,5 +105,5 @@ it('should display secondary measure too', () => { }); it('should work with measure without value', () => { - expect(shallow(<MeasureHeader {...PROPS} measure={undefined} />)).toMatchSnapshot(); + expect(shallow(<MeasureHeader {...PROPS} measureValue={undefined} />)).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap index 56e8fe4c14f..e90f3b035ea 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap @@ -73,48 +73,13 @@ exports[`should render correctly 1`] = ` > <Component /> </ScreenPositionHelper> - <MeasureContentContainer + <MeasureContent branchLike={ Object { "isMain": true, "name": "master", } } - className="layout-page-main" - fetchMeasures={ - [MockFunction] { - "calls": Array [ - Array [ - "foo", - Array [ - "lines_to_cover", - "coverage", - "duplicated_lines_density", - "new_bugs", - ], - Object { - "isMain": true, - "name": "master", - }, - ], - ], - "results": Array [ - Object { - "isThrow": false, - "value": Promise {}, - }, - ], - } - } - metric={ - Object { - "domain": "Coverage", - "id": "2", - "key": "coverage", - "name": "Coverage", - "type": "PERCENT", - } - } metrics={ Object { "coverage": Object { @@ -147,6 +112,15 @@ exports[`should render correctly 1`] = ` }, } } + requestedMetric={ + Object { + "domain": "Coverage", + "id": "2", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } rootComponent={ Object { "key": "foo", diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx index 6b88ff48279..561368ea1a3 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx @@ -26,7 +26,6 @@ interface Props { component: T.ComponentMeasure; components: T.ComponentMeasureEnhanced[]; leakPeriod?: T.Period; - metric: T.Metric; selectedIdx?: number; updateSelected: (component: string) => void; } diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx index dc27d380146..07c249ce0ed 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx @@ -30,6 +30,7 @@ import { getProjectUrl } from '../../../helpers/urls'; import { translate } from '../../../helpers/l10n'; +import { View } from '../utils'; interface Props { branchLike?: T.BranchLike; @@ -37,6 +38,7 @@ interface Props { onClick: (component: string) => void; metric: T.Metric; rootComponent: T.ComponentMeasure; + view: View; } export default class ComponentCell extends React.PureComponent<Props> { @@ -54,33 +56,34 @@ export default class ComponentCell extends React.PureComponent<Props> { const { component } = this.props; let head = ''; let tail = component.name; - let branchComponent = null; - if (['DIR', 'FIL', 'UTS'].includes(component.qualifier) && component.path) { + if ( + this.props.view === 'list' && + ['FIL', 'UTS', 'DIR'].includes(component.qualifier) && + component.path + ) { ({ head, tail } = splitPath(component.path)); } - if (this.props.rootComponent.qualifier === 'APP') { - branchComponent = ( - <> - {component.branch ? ( - <> - <LongLivingBranchIcon className="spacer-left little-spacer-right" /> - <span className="note">{component.branch}</span> - </> - ) : ( - <span className="spacer-left outline-badge">{translate('branches.main_branch')}</span> - )} - </> - ); - } + const isApp = this.props.rootComponent.qualifier === 'APP'; + return ( <span title={componentKey}> - <QualifierIcon qualifier={component.qualifier} /> - + <QualifierIcon className="little-spacer-right" qualifier={component.qualifier} /> {head.length > 0 && <span className="note">{head}/</span>} <span>{tail}</span> - {branchComponent} + {isApp && ( + <> + {component.branch ? ( + <> + <LongLivingBranchIcon className="spacer-left little-spacer-right" /> + <span className="note">{component.branch}</span> + </> + ) : ( + <span className="spacer-left outline-badge">{translate('branches.main_branch')}</span> + )} + </> + )} </span> ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx index 1cb3fa627b6..21777417bec 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx @@ -22,6 +22,7 @@ import ComponentsListRow from './ComponentsListRow'; import EmptyResult from './EmptyResult'; import { complementary } from '../config/complementary'; import { getLocalizedMetricName } from '../../../helpers/l10n'; +import { View } from '../utils'; interface Props { branchLike?: T.BranchLike; @@ -31,6 +32,7 @@ interface Props { metrics: { [metric: string]: T.Metric }; rootComponent: T.ComponentMeasure; selectedComponent?: string; + view: View; } export default function ComponentsList({ components, metric, metrics, ...props }: Props) { @@ -40,37 +42,35 @@ export default function ComponentsList({ components, metric, metrics, ...props } const otherMetrics = (complementary[metric.key] || []).map(key => metrics[key]); return ( - <React.Fragment> - <table className="data zebra zebra-hover"> - {otherMetrics.length > 0 && ( - <thead> - <tr> - <th> </th> - <th className="text-right"> + <table className="data zebra zebra-hover"> + {otherMetrics.length > 0 && ( + <thead> + <tr> + <th> </th> + <th className="text-right"> + <span className="small">{getLocalizedMetricName(metric)}</span> + </th> + {otherMetrics.map(metric => ( + <th className="text-right" key={metric.key}> <span className="small">{getLocalizedMetricName(metric)}</span> </th> - {otherMetrics.map(metric => ( - <th className="text-right" key={metric.key}> - <span className="small">{getLocalizedMetricName(metric)}</span> - </th> - ))} - </tr> - </thead> - )} + ))} + </tr> + </thead> + )} - <tbody> - {components.map(component => ( - <ComponentsListRow - component={component} - isSelected={component.key === props.selectedComponent} - key={component.key} - metric={metric} - otherMetrics={otherMetrics} - {...props} - /> - ))} - </tbody> - </table> - </React.Fragment> + <tbody> + {components.map(component => ( + <ComponentsListRow + component={component} + isSelected={component.key === props.selectedComponent} + key={component.key} + metric={metric} + otherMetrics={otherMetrics} + {...props} + /> + ))} + </tbody> + </table> ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.tsx index 571c0191e3a..eba1ed5536a 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import * as classNames from 'classnames'; import ComponentCell from './ComponentCell'; import MeasureCell from './MeasureCell'; +import { View } from '../utils'; interface Props { branchLike?: T.BranchLike; @@ -30,6 +31,7 @@ interface Props { otherMetrics: T.Metric[]; metric: T.Metric; rootComponent: T.ComponentMeasure; + view: View; } export default function ComponentsListRow(props: Props) { @@ -49,6 +51,7 @@ export default function ComponentsListRow(props: Props) { metric={props.metric} onClick={props.onClick} rootComponent={rootComponent} + view={props.view} /> <MeasureCell component={component} metric={props.metric} /> diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx index d4f364f6f34..919bb516797 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx @@ -27,6 +27,7 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; import { isPeriodBestValue, isDiffMetric, formatMeasure } from '../../../helpers/measures'; import { scrollToElement } from '../../../helpers/scrolling'; import { Alert } from '../../../components/ui/Alert'; +import { View } from '../utils'; interface Props { branchLike?: T.BranchLike; @@ -42,12 +43,15 @@ interface Props { rootComponent: T.ComponentMeasure; selectedKey?: string; selectedIdx?: number; + view: View; } interface State { showBestMeasures: boolean; } +const keyScope = 'measures-files'; + export default class FilesView extends React.PureComponent<Props, State> { listContainer?: HTMLElement | null; @@ -79,22 +83,22 @@ export default class FilesView extends React.PureComponent<Props, State> { } attachShortcuts() { - key('up', 'measures-files', () => { + key('up', keyScope, () => { this.selectPrevious(); return false; }); - key('down', 'measures-files', () => { + key('down', keyScope, () => { this.selectNext(); return false; }); - key('right', 'measures-files', () => { + key('right', keyScope, () => { this.openSelected(); return false; }); } detachShortcuts() { - ['up', 'down', 'right'].forEach(action => key.unbind(action, 'measures-files')); + ['up', 'down', 'right'].forEach(action => key.unbind(action, keyScope)); } getVisibleComponents = (components: T.ComponentMeasureEnhanced[], showBestMeasures: boolean) => { @@ -170,6 +174,7 @@ export default class FilesView extends React.PureComponent<Props, State> { onClick={this.props.handleOpen} rootComponent={this.props.rootComponent} selectedComponent={this.props.selectedKey} + view={this.props.view} /> {hidingBestMeasures && ( <Alert className="spacer-top" variant="info"> diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx index 6ab93f9f2e5..6139ea9bee4 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx @@ -47,6 +47,7 @@ it('should renders correctly', () => { metrics={METRICS} onClick={jest.fn()} rootComponent={COMPONENTS[0]} + view="tree" /> ) ).toMatchSnapshot(); @@ -61,6 +62,7 @@ it('should renders empty', () => { metrics={METRICS} onClick={jest.fn()} rootComponent={COMPONENTS[0]} + view="tree" /> ) ).toMatchSnapshot(); @@ -75,6 +77,7 @@ it('should renders with multiple measures', () => { metrics={METRICS} onClick={jest.fn()} rootComponent={COMPONENTS[0]} + view="tree" /> ) ).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx index 425024d3e86..ae2682cefc9 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx @@ -73,6 +73,7 @@ function getWrapper(props = {}) { organization: 'foo', qualifier: 'TRK' }} + view="tree" {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap index 743f6ea0ba8..c16ae7b6dff 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap @@ -1,140 +1,138 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should renders correctly 1`] = ` -<Fragment> - <table - className="data zebra zebra-hover" - > - <tbody> - <ComponentsListRow - component={ - Object { - "key": "foo", - "measures": Array [], - "name": "Foo", - "organization": "foo", - "qualifier": "TRK", - } +<table + className="data zebra zebra-hover" +> + <tbody> + <ComponentsListRow + component={ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", } - isSelected={false} - key="foo" - metric={ - Object { - "id": "2", - "key": "new_bugs", - "name": "New Bugs", - "type": "INT", - } + } + isSelected={false} + key="foo" + metric={ + Object { + "id": "2", + "key": "new_bugs", + "name": "New Bugs", + "type": "INT", } - onClick={[MockFunction]} - otherMetrics={Array []} - rootComponent={ - Object { - "key": "foo", - "measures": Array [], - "name": "Foo", - "organization": "foo", - "qualifier": "TRK", - } + } + onClick={[MockFunction]} + otherMetrics={Array []} + rootComponent={ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", } - /> - </tbody> - </table> -</Fragment> + } + view="tree" + /> + </tbody> +</table> `; exports[`should renders empty 1`] = `<EmptyResult />`; exports[`should renders with multiple measures 1`] = ` -<Fragment> - <table - className="data zebra zebra-hover" - > - <thead> - <tr> - <th> - - </th> - <th - className="text-right" +<table + className="data zebra zebra-hover" +> + <thead> + <tr> + <th> + + </th> + <th + className="text-right" + > + <span + className="small" > - <span - className="small" - > - Coverage - </span> - </th> - <th - className="text-right" - key="uncovered_lines" + Coverage + </span> + </th> + <th + className="text-right" + key="uncovered_lines" + > + <span + className="small" > - <span - className="small" - > - Lines - </span> - </th> - <th - className="text-right" - key="uncovered_conditions" + Lines + </span> + </th> + <th + className="text-right" + key="uncovered_conditions" + > + <span + className="small" > - <span - className="small" - > - Conditions - </span> - </th> - </tr> - </thead> - <tbody> - <ComponentsListRow - component={ - Object { - "key": "foo", - "measures": Array [], - "name": "Foo", - "organization": "foo", - "qualifier": "TRK", - } + Conditions + </span> + </th> + </tr> + </thead> + <tbody> + <ComponentsListRow + component={ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", } - isSelected={false} - key="foo" - metric={ - Object { - "id": "1", - "key": "coverage", - "name": "Coverage", - "type": "PERCENT", - } - } - onClick={[MockFunction]} - otherMetrics={ - Array [ - Object { - "id": "3", - "key": "uncovered_lines", - "name": "Lines", - "type": "INT", - }, - Object { - "id": "4", - "key": "uncovered_conditions", - "name": "Conditions", - "type": "INT", - }, - ] + } + isSelected={false} + key="foo" + metric={ + Object { + "id": "1", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", } - rootComponent={ + } + onClick={[MockFunction]} + otherMetrics={ + Array [ Object { - "key": "foo", - "measures": Array [], - "name": "Foo", - "organization": "foo", - "qualifier": "TRK", - } + "id": "3", + "key": "uncovered_lines", + "name": "Lines", + "type": "INT", + }, + Object { + "id": "4", + "key": "uncovered_conditions", + "name": "Conditions", + "type": "INT", + }, + ] + } + rootComponent={ + Object { + "key": "foo", + "measures": Array [], + "name": "Foo", + "organization": "foo", + "qualifier": "TRK", } - /> - </tbody> - </table> -</Fragment> + } + view="tree" + /> + </tbody> +</table> `; diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap index e9c91714593..0efe85f4ba8 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap @@ -42,6 +42,7 @@ exports[`should render with best values hidden 1`] = ` "qualifier": "TRK", } } + view="tree" /> <Alert className="spacer-top" @@ -100,6 +101,7 @@ exports[`should renders correctly 1`] = ` "qualifier": "TRK", } } + view="tree" /> <ListFooter count={1} diff --git a/server/sonar-web/src/main/js/apps/component-measures/routes.ts b/server/sonar-web/src/main/js/apps/component-measures/routes.ts index 045d1686c1b..7b26d3dc843 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/routes.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/routes.ts @@ -22,7 +22,7 @@ import { lazyLoad } from '../../components/lazyLoad'; const routes = [ { - indexRoute: { component: lazyLoad(() => import('./components/AppContainer')) } + indexRoute: { component: lazyLoad(() => import('./components/App')) } }, { path: 'domain/:domainName', diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx index 0086d1f4e76..c6a3e599d70 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import ProjectOverviewFacet from './ProjectOverviewFacet'; import DomainFacet from './DomainFacet'; -import { getDefaultView, groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils'; +import { groupByDomains, KNOWN_DOMAINS, PROJECT_OVERVEW, Query } from '../utils'; interface Props { hasOverview: boolean; @@ -34,31 +34,12 @@ interface State { } export default class Sidebar extends React.PureComponent<Props, State> { - constructor(props: Props) { - super(props); - this.state = { openFacets: this.getOpenFacets({}, props) }; + static getDerivedStateFromProps(props: Props, state: State) { + return { openFacets: getOpenFacets(state.openFacets, props) }; } - componentWillReceiveProps(nextProps: Props) { - if (nextProps.selectedMetric !== this.props.selectedMetric) { - this.setState(({ openFacets }) => ({ - openFacets: this.getOpenFacets(openFacets, nextProps) - })); - } - } - - getOpenFacets = ( - openFacets: { [metric: string]: boolean }, - { measures, selectedMetric }: Props - ) => { - const newOpenFacets = { ...openFacets }; - const measure = measures.find(measure => measure.metric.key === selectedMetric); - if (measure && measure.metric && measure.metric.domain) { - newOpenFacets[measure.metric.domain] = true; - } else if (KNOWN_DOMAINS.includes(selectedMetric)) { - newOpenFacets[selectedMetric] = true; - } - return newOpenFacets; + state: State = { + openFacets: {} }; toggleFacet = (name: string) => { @@ -67,10 +48,9 @@ export default class Sidebar extends React.PureComponent<Props, State> { })); }; - resetSelection = (metric: string) => ({ selected: undefined, view: getDefaultView(metric) }); - - changeMetric = (metric: string) => - this.props.updateQuery({ metric, ...this.resetSelection(metric) }); + changeMetric = (metric: string) => { + this.props.updateQuery({ metric }); + }; render() { const { hasOverview } = this.props; @@ -98,3 +78,17 @@ export default class Sidebar extends React.PureComponent<Props, State> { ); } } + +function getOpenFacets( + openFacets: { [metric: string]: boolean }, + { measures, selectedMetric }: Props +) { + const newOpenFacets = { ...openFacets }; + const measure = measures.find(measure => measure.metric.key === selectedMetric); + if (measure && measure.metric && measure.metric.domain) { + newOpenFacets[measure.metric.domain] = true; + } else if (KNOWN_DOMAINS.includes(selectedMetric)) { + newOpenFacets[selectedMetric] = true; + } + return newOpenFacets; +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.ts b/server/sonar-web/src/main/js/apps/component-measures/utils.ts index 49b0b7a6775..52e39e4e059 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/utils.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/utils.ts @@ -87,7 +87,7 @@ export function addMeasureCategories(domainName: string, measures: T.MeasureEnha export function enhanceComponent( component: T.ComponentMeasure, - metric: T.Metric | undefined, + metric: Pick<T.Metric, 'key'> | undefined, metrics: { [key: string]: T.Metric } ): T.ComponentMeasureEnhanced { if (!component.measures) { @@ -124,13 +124,6 @@ export const groupByDomains = memoize((measures: T.MeasureEnhanced[]) => { ]); }); -export function getDefaultView(metric: string): View { - if (!hasList(metric)) { - return 'tree'; - } - return DEFAULT_VIEW; -} - export function hasList(metric: string): boolean { return !['releasability_rating', 'releasability_effort'].includes(metric); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx index 4a7c46e3754..5d3a86e8ca4 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx @@ -27,7 +27,6 @@ import Coverage from '../main/Coverage'; import Duplications from '../main/Duplications'; import MetaContainer from '../meta/MetaContainer'; import QualityGate from '../qualityGate/QualityGate'; -import throwGlobalError from '../../../app/utils/throwGlobalError'; import { getMeasuresAndMeta } from '../../../api/measures'; import { getAllTimeMachineData } from '../../../api/time-machine'; import { parseDate } from '../../../helpers/dates'; @@ -150,8 +149,7 @@ export class OverviewApp extends React.PureComponent<Props, State> { }); } }, - error => { - throwGlobalError(error); + () => { if (this.mounted) { this.setState({ loading: false }); } |