aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2018-12-03 13:50:17 +0100
committersonartech <sonartech@sonarsource.com>2019-01-16 09:43:04 +0100
commit6208b5a001ccfa21697463e0a780b9b3b3fee6e3 (patch)
treeef86ae6b72ea6bf2bd1fae16152ba6c25730840a /server/sonar-web/src/main/js
parent10e2a7a4152933a0c8a533d78255f737ed69c6da (diff)
downloadsonarqube-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')
-rw-r--r--server/sonar-web/src/main/js/api/components.ts1
-rw-r--r--server/sonar-web/src/main/js/api/measures.ts7
-rw-r--r--server/sonar-web/src/main/js/apps/code/utils.ts52
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/App.tsx145
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.tsx101
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.tsx16
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx301
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentContainer.tsx143
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverviewContainer.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MetricNotFound.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/App-test.tsx89
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.tsx.snap46
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentCell.tsx41
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsList.tsx58
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/ComponentsListRow.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap240
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/routes.ts2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.tsx50
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/utils.ts9
-rw-r--r--server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx4
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} />
- &nbsp;
+ <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>&nbsp;</th>
- <th className="text-right">
+ <table className="data zebra zebra-hover">
+ {otherMetrics.length > 0 && (
+ <thead>
+ <tr>
+ <th>&nbsp;</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 });
}