diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2018-11-28 13:52:56 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-01-16 09:43:02 +0100 |
commit | f686185f25130fe4d84ae3b9307b784895414d5a (patch) | |
tree | 013d70e882920927030226b80dcf03822b681691 | |
parent | cf1c8d76184de334921362e9abd7dd64d56e5f16 (diff) | |
download | sonarqube-f686185f25130fe4d84ae3b9307b784895414d5a.tar.gz sonarqube-f686185f25130fe4d84ae3b9307b784895414d5a.zip |
SONAR-11478 Update the tree view on the Measures page (#982)
21 files changed, 246 insertions, 295 deletions
diff --git a/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts index a1c7505e495..cecbc7dd5df 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/component-measures/__tests__/utils-test.ts @@ -121,16 +121,17 @@ describe('parseQuery', () => { describe('serializeQuery', () => { it('should correctly serialize the query', () => { - expect(utils.serializeQuery({ metric: '', selected: '', view: 'list' })).toEqual({}); + expect(utils.serializeQuery({ metric: '', selected: '', view: 'list' })).toEqual({ + view: 'list' + }); expect(utils.serializeQuery({ metric: 'foo', selected: 'bar', view: 'tree' })).toEqual({ metric: 'foo', - selected: 'bar', - view: 'tree' + selected: 'bar' }); }); it('should be memoized', () => { - const query = { metric: 'foo', selected: 'bar', view: 'tree' }; + const query: utils.Query = { metric: 'foo', selected: 'bar', view: 'tree' }; expect(utils.serializeQuery(query)).toBe(utils.serializeQuery(query)); }); }); 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 2d233e2e2a3..51de9019d2f 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 @@ -62,7 +62,6 @@ import '../style.css'; interface Props { branchLike?: T.BranchLike; component: T.ComponentMeasure; - currentUser: T.CurrentUser; location: { pathname: string; query: RawQuery }; fetchMeasures: ( component: string, @@ -200,7 +199,6 @@ export default class App extends React.PureComponent<Props, State> { <MeasureOverviewContainer branchLike={branchLike} className="layout-page-main" - currentUser={this.props.currentUser} domain={query.metric} leakPeriod={leakPeriod} metrics={metrics} @@ -234,7 +232,6 @@ export default class App extends React.PureComponent<Props, State> { <MeasureContentContainer branchLike={branchLike} className="layout-page-main" - currentUser={this.props.currentUser} fetchMeasures={fetchMeasures} leakPeriod={leakPeriod} metric={metric} 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 index 9a06175a775..e7ea87f126e 100644 --- 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 @@ -22,7 +22,7 @@ import { connect } from 'react-redux'; import { withRouter, WithRouterProps } from 'react-router'; import App from './App'; import throwGlobalError from '../../../app/utils/throwGlobalError'; -import { getCurrentUser, getMetrics, getMetricsKey } from '../../../store/rootReducer'; +import { getMetrics, getMetricsKey } from '../../../store/rootReducer'; import { fetchMetrics } from '../../../store/rootActions'; import { getMeasuresAndMeta } from '../../../api/measures'; import { getLeakPeriod } from '../../../helpers/periods'; @@ -30,7 +30,6 @@ import { enhanceMeasure } from '../../../components/measure/utils'; import { getBranchLikeQuery } from '../../../helpers/branches'; interface StateToProps { - currentUser: T.CurrentUser; metrics: { [metric: string]: T.Metric }; metricsKey: string[]; } @@ -54,7 +53,6 @@ interface OwnProps { } const mapStateToProps = (state: any): StateToProps => ({ - currentUser: getCurrentUser(state), metrics: getMetrics(state), metricsKey: getMetricsKey(state) }); 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 c254f67dd65..86982a68e0a 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 @@ -21,7 +21,7 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { InjectedRouter } from 'react-router'; import Breadcrumbs from './Breadcrumbs'; -import MeasureFavoriteContainer from './MeasureFavoriteContainer'; +import MeasureContentHeader from './MeasureContentHeader'; import MeasureHeader from './MeasureHeader'; import MeasureViewSelect from './MeasureViewSelect'; import MetricNotFound from './MetricNotFound'; @@ -31,19 +31,17 @@ import CodeView from '../drilldown/CodeView'; import TreeMapView from '../drilldown/TreeMapView'; import { getComponentTree } from '../../../api/components'; import { complementary } from '../config/complementary'; -import { enhanceComponent, isFileType, isViewType } from '../utils'; +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 { RequestData } from '../../../helpers/request'; -import { isLoggedIn } from '../../../helpers/users'; interface Props { branchLike?: T.BranchLike; className?: string; component: T.ComponentMeasure; - currentUser: T.CurrentUser; loading: boolean; loadingMore: boolean; leakPeriod?: T.Period; @@ -55,8 +53,8 @@ interface Props { secondaryMeasure?: T.MeasureEnhanced; updateLoading: (param: { [key: string]: boolean }) => void; updateSelected: (component: string) => void; - updateView: (view: string) => void; - view: string; + updateView: (view: View) => void; + view: View; } interface State { @@ -64,7 +62,6 @@ interface State { metric?: T.Metric; paging?: T.Paging; selected?: string; - view?: string; } export default class MeasureContent extends React.PureComponent<Props, State> { @@ -99,37 +96,47 @@ export default class MeasureContent extends React.PureComponent<Props, State> { return index !== -1 ? index : undefined; }; - getComponentRequestParams = (view: string, metric: T.Metric, options: Object = {}) => { + getComponentRequestParams = (view: View, metric: T.Metric, options: Object = {}) => { const strategy = view === 'list' ? 'leaves' : 'children'; const metricKeys = [metric.key]; const opts: RequestData = { ...getBranchLikeQuery(this.props.branchLike), additionalFields: 'metrics', - metricSortFilter: 'withMeasuresOnly' + ps: 500 }; - const isDiff = isDiffMetric(metric.key); - if (isDiff) { - opts.metricPeriodSort = 1; - } - if (view === 'treemap') { - const sizeMetric = isDiff ? 'new_lines' : 'ncloc'; - metricKeys.push(sizeMetric); - opts.metricSort = sizeMetric; + + const setMetricSort = () => { + const isDiff = isDiffMetric(metric.key); opts.s = isDiff ? 'metricPeriod' : 'metric'; - opts.asc = false; - } else { + opts.metricSortFilter = 'withMeasuresOnly'; + if (isDiff) { + opts.metricPeriodSort = 1; + } + }; + + const isDiff = isDiffMetric(metric.key); + if (view === 'tree') { + metricKeys.push(...(complementary[metric.key] || [])); + opts.asc = true; + opts.s = 'qualifier,name'; + } else if (view === 'list') { metricKeys.push(...(complementary[metric.key] || [])); opts.asc = metric.direction === 1; - opts.ps = 100; opts.metricSort = metric.key; - opts.s = isDiff ? 'metricPeriod' : 'metric'; + setMetricSort(); + } else if (view === 'treemap') { + const sizeMetric = isDiff ? 'new_lines' : 'ncloc'; + metricKeys.push(sizeMetric); + opts.asc = false; + opts.metricSort = sizeMetric; + setMetricSort(); } + return { metricKeys, opts: { ...opts, ...options }, strategy }; }; fetchComponents = ({ component, metric, metrics, view }: Props) => { if (isFileType(component)) { - this.setState({ metric: undefined, view: undefined }); return; } @@ -178,9 +185,9 @@ export default class MeasureContent extends React.PureComponent<Props, State> { ...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, - view + paging: r.paging })); } this.props.updateLoading({ moreComponents: false }); @@ -228,45 +235,44 @@ export default class MeasureContent extends React.PureComponent<Props, State> { } renderMeasure() { - const { metric, view } = this.state; - if (metric !== undefined) { - if (!view || ['list', 'tree'].includes(view)) { - const selectedIdx = this.getSelectedIndex(); - return ( - <FilesView - branchLike={this.props.branchLike} - components={this.state.components} - fetchMore={this.fetchMoreComponents} - handleOpen={this.onOpenComponent} - handleSelect={this.onSelectComponent} - loadingMore={this.props.loadingMore} - metric={metric} - metrics={this.props.metrics} - paging={this.state.paging} - rootComponent={this.props.rootComponent} - selectedIdx={selectedIdx} - selectedKey={selectedIdx !== undefined ? this.state.selected : undefined} - /> - ); - } - - if (view === 'treemap') { - return ( - <TreeMapView - branchLike={this.props.branchLike} - components={this.state.components} - handleSelect={this.onOpenComponent} - metric={metric} - /> - ); - } + const { view } = this.props; + const { metric } = this.state; + if (!metric) { + return null; + } + if (view === 'tree' || view === 'list') { + const selectedIdx = this.getSelectedIndex(); + return ( + <FilesView + branchLike={this.props.branchLike} + components={this.state.components} + defaultShowBestMeasures={view === 'tree'} + fetchMore={this.fetchMoreComponents} + handleOpen={this.onOpenComponent} + handleSelect={this.onSelectComponent} + loadingMore={this.props.loadingMore} + metric={metric} + metrics={this.props.metrics} + paging={this.state.paging} + rootComponent={this.props.rootComponent} + selectedIdx={selectedIdx} + selectedKey={selectedIdx !== undefined ? this.state.selected : undefined} + /> + ); + } else { + return ( + <TreeMapView + branchLike={this.props.branchLike} + components={this.state.components} + handleSelect={this.onOpenComponent} + metric={metric} + /> + ); } - - return null; } render() { - const { branchLike, component, currentUser, measure, metric, rootComponent, view } = this.props; + const { branchLike, component, measure, metric, rootComponent, view } = this.props; const isFile = isFileType(component); const selectedIdx = this.getSelectedIndex(); return ( @@ -276,38 +282,40 @@ export default class MeasureContent extends React.PureComponent<Props, State> { <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"> - <Breadcrumbs - backToFirst={view === 'list'} - branchLike={branchLike} - className="measure-breadcrumbs spacer-right text-ellipsis" - component={component} - handleSelect={this.onOpenComponent} - rootComponent={rootComponent} - /> - {component.key !== rootComponent.key && - isLoggedIn(currentUser) && ( - <MeasureFavoriteContainer + <MeasureContentHeader + left={ + <Breadcrumbs + backToFirst={view === 'list'} branchLike={branchLike} - className="measure-favorite spacer-right" - component={component.key} + className="text-ellipsis flex-1" + component={component} + handleSelect={this.onOpenComponent} + rootComponent={rootComponent} /> - )} - {!isFile && ( - <MeasureViewSelect - className="measure-view-select" - handleViewChange={this.props.updateView} - metric={metric} - view={view} - /> - )} - <PageActions - current={ - selectedIdx !== undefined && view !== 'treemap' ? selectedIdx + 1 : undefined } - isFile={isFile} - paging={this.state.paging} - totalLoadedComponents={this.state.components.length} - view={view} + right={ + <div className="display-flex-center"> + {!isFile && ( + <MeasureViewSelect + className="measure-view-select big-spacer-right" + handleViewChange={this.props.updateView} + metric={metric} + view={view} + /> + )} + <PageActions + current={ + selectedIdx !== undefined && view !== 'treemap' + ? selectedIdx + 1 + : undefined + } + isFile={isFile} + paging={this.state.paging} + totalLoadedComponents={this.state.components.length} + view={view} + /> + </div> + } /> </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 index 2695dc15b32..cd6edf18d8b 100644 --- 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 @@ -20,12 +20,11 @@ import * as React from 'react'; import { InjectedRouter } from 'react-router'; import MeasureContent from './MeasureContent'; -import { Query } from '../utils'; +import { Query, View } from '../utils'; interface Props { branchLike?: T.BranchLike; className?: string; - currentUser: T.CurrentUser; rootComponent: T.ComponentMeasure; fetchMeasures: ( component: string, @@ -38,7 +37,7 @@ interface Props { router: InjectedRouter; selected?: string; updateQuery: (query: Partial<Query>) => void; - view: string; + view: View; } interface LoadingState { @@ -111,7 +110,9 @@ export default class MeasureContentContainer extends React.PureComponent<Props, }); }; - updateView = (view: string) => this.props.updateQuery({ view }); + updateView = (view: View) => { + this.props.updateQuery({ view }); + }; render() { if (!this.state.component) { @@ -123,7 +124,6 @@ export default class MeasureContentContainer extends React.PureComponent<Props, branchLike={this.props.branchLike} className={this.props.className} component={this.state.component} - currentUser={this.props.currentUser} leakPeriod={this.props.leakPeriod} loading={this.state.loading.measure || this.state.loading.components} loadingMore={this.state.loading.moreComponents} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx new file mode 100644 index 00000000000..a930afdf624 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContentHeader.tsx @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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'; + +interface Props { + left: React.ReactNode; + right: React.ReactNode; +} + +export default function MeasureContentHeader({ left, right }: Props) { + return ( + <div className="measure-content-header"> + <div className="measure-content-header-left">{left}</div> + <div className="measure-content-header-right">{right}</div> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx deleted file mode 100644 index 3ad335a4d31..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx +++ /dev/null @@ -1,85 +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 Favorite from '../../../components/controls/Favorite'; -import { getComponentForSourceViewer } from '../../../api/components'; -import { isMainBranch } from '../../../helpers/branches'; - -type FavComponent = Pick<T.SourceViewerFile, 'canMarkAsFavorite' | 'fav' | 'key' | 'q'>; - -interface Props { - branchLike?: T.BranchLike; - className?: string; - component: string; -} - -interface State { - component?: FavComponent; -} - -export default class MeasureFavoriteContainer extends React.PureComponent<Props, State> { - mounted = false; - state: State = {}; - - componentDidMount() { - this.mounted = true; - this.fetchComponentFavorite(); - } - - componentDidUpdate(prevProps: Props) { - if (prevProps.component !== this.props.component) { - this.fetchComponentFavorite(); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchComponentFavorite() { - getComponentForSourceViewer({ component: this.props.component }).then( - component => { - if (this.mounted) { - this.setState({ component }); - } - }, - () => {} - ); - } - - render() { - const { component } = this.state; - if ( - !component || - !component.canMarkAsFavorite || - (this.props.branchLike && !isMainBranch(this.props.branchLike)) - ) { - return null; - } - return ( - <Favorite - className={this.props.className} - component={component.key} - favorite={component.fav || false} - qualifier={component.q} - /> - ); - } -} 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 f41816f6a21..e18c6821108 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 @@ -20,7 +20,7 @@ import * as React from 'react'; import Breadcrumbs from './Breadcrumbs'; import LeakPeriodLegend from './LeakPeriodLegend'; -import MeasureFavoriteContainer from './MeasureFavoriteContainer'; +import MeasureContentHeader from './MeasureContentHeader'; import PageActions from './PageActions'; import BubbleChart from '../drilldown/BubbleChart'; import SourceViewer from '../../../components/SourceViewer/SourceViewer'; @@ -33,7 +33,6 @@ interface Props { branchLike?: T.BranchLike; className?: string; component: T.ComponentMeasure; - currentUser: T.CurrentUser; domain: string; leakPeriod?: T.Period; loading: boolean; @@ -133,33 +132,31 @@ export default class MeasureOverview extends React.PureComponent<Props, State> { } render() { - const { branchLike, component, currentUser, leakPeriod, rootComponent } = this.props; - const isLoggedIn = currentUser && currentUser.isLoggedIn; + const { branchLike, component, leakPeriod, rootComponent } = this.props; const isFile = isFileType(component); return ( <div className={this.props.className}> <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"> - <Breadcrumbs - backToFirst={true} - branchLike={branchLike} - className="measure-breadcrumbs spacer-right text-ellipsis" - component={component} - handleSelect={this.props.updateSelected} - rootComponent={rootComponent} - /> - {component.key !== rootComponent.key && - isLoggedIn && ( - <MeasureFavoriteContainer - className="measure-favorite spacer-right" - component={component.key} + <MeasureContentHeader + left={ + <Breadcrumbs + backToFirst={true} + branchLike={branchLike} + className="text-ellipsis" + component={component} + handleSelect={this.props.updateSelected} + rootComponent={rootComponent} + /> + } + right={ + <PageActions + current={this.state.components.length} + isFile={isFile} + paging={this.state.paging} /> - )} - <PageActions - current={this.state.components.length} - isFile={isFile} - paging={this.state.paging} + } /> </div> </div> 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 3f2dbfbba2e..200fce078a2 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 @@ -28,7 +28,6 @@ import { getBranchLikeQuery } from '../../../helpers/branches'; interface Props { branchLike?: T.BranchLike; className?: string; - currentUser: T.CurrentUser; domain: string; leakPeriod?: T.Period; metrics: { [metric: string]: T.Metric }; @@ -119,7 +118,6 @@ export default class MeasureOverviewContainer extends React.PureComponent<Props, branchLike={this.props.branchLike} className={this.props.className} component={this.state.component} - currentUser={this.props.currentUser} domain={this.props.domain} leakPeriod={this.props.leakPeriod} loading={this.state.loading.component || this.state.loading.bubbles} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.tsx index ec8730eb5ac..236c1217645 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.tsx @@ -22,27 +22,20 @@ import ListIcon from '../../../components/icons-components/ListIcon'; import TreeIcon from '../../../components/icons-components/TreeIcon'; import TreemapIcon from '../../../components/icons-components/TreemapIcon'; import Select from '../../../components/controls/Select'; -import { hasList, hasTree, hasTreemap } from '../utils'; +import { hasList, hasTree, hasTreemap, View } from '../utils'; import { translate } from '../../../helpers/l10n'; interface Props { className?: string; metric: T.Metric; - handleViewChange: (view: string) => void; - view: string; + handleViewChange: (view: View) => void; + view: View; } export default class MeasureViewSelect extends React.PureComponent<Props> { getOptions = () => { const { metric } = this.props; const options = []; - if (hasList(metric.key)) { - options.push({ - icon: <ListIcon />, - label: translate('component_measures.tab.list'), - value: 'list' - }); - } if (hasTree(metric.key)) { options.push({ icon: <TreeIcon />, @@ -50,6 +43,13 @@ export default class MeasureViewSelect extends React.PureComponent<Props> { value: 'tree' }); } + if (hasList(metric.key)) { + options.push({ + icon: <ListIcon />, + label: translate('component_measures.tab.list'), + value: 'list' + }); + } if (hasTreemap(metric.key, metric.type)) { options.push({ icon: <TreemapIcon />, @@ -61,7 +61,7 @@ export default class MeasureViewSelect extends React.PureComponent<Props> { }; handleChange = (option: { value: string }) => { - return this.props.handleViewChange(option.value); + return this.props.handleViewChange(option.value as View); }; renderOption = (option: { icon: JSX.Element; label: string }) => { diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.tsx index e2c57b91269..32dc2706fe7 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.tsx @@ -20,23 +20,24 @@ import * as React from 'react'; import FilesCounter from './FilesCounter'; import { translate } from '../../../helpers/l10n'; +import { View } from '../utils'; interface Props { current?: number; isFile?: boolean; paging?: T.Paging; totalLoadedComponents?: number; - view?: string; + view?: View; } export default function PageActions(props: Props) { const { isFile, paging, totalLoadedComponents } = props; const showShortcuts = props.view && ['list', 'tree'].includes(props.view); return ( - <div className="pull-right"> + <div className="display-flex-center"> {!isFile && showShortcuts && renderShortcuts()} {isFile && paging && renderFileShortcuts()} - <div className="measure-details-page-actions"> + <div className="measure-details-page-actions nowrap"> {paging != null && ( <FilesCounter className="spacer-left" @@ -51,7 +52,7 @@ export default function PageActions(props: Props) { function renderShortcuts() { return ( - <span className="note big-spacer-right"> + <span className="note big-spacer-right nowrap"> <span className="big-spacer-right"> <span className="shortcut-button little-spacer-right">↑</span> <span className="shortcut-button little-spacer-right">↓</span> @@ -69,7 +70,7 @@ function renderShortcuts() { function renderFileShortcuts() { return ( - <span className="note spacer-right"> + <span className="note spacer-right nowrap"> <span> <span className="shortcut-button little-spacer-right">j</span> <span className="shortcut-button little-spacer-right">k</span> 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 939481b7480..de3394ecc88 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 @@ -46,7 +46,6 @@ const METRICS = { const PROPS: App['props'] = { branchLike: { isMain: true, name: 'master' }, component: COMPONENT, - currentUser: { isLoggedIn: false }, location: { pathname: '/component_measures', query: { metric: 'coverage' } }, fetchMeasures: jest.fn().mockResolvedValue({ component: COMPONENT, 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 df50df20427..56e8fe4c14f 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 @@ -81,11 +81,6 @@ exports[`should render correctly 1`] = ` } } className="layout-page-main" - currentUser={ - Object { - "isLoggedIn": false, - } - } fetchMeasures={ [MockFunction] { "calls": Array [ @@ -166,7 +161,7 @@ exports[`should render correctly 1`] = ` } selected="" updateQuery={[Function]} - view="list" + view="tree" /> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap index 43cd04e0644..bc440003070 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap @@ -9,16 +9,16 @@ exports[`should display correctly with treemap option 1`] = ` options={ Array [ Object { - "icon": <ListIcon />, - "label": "component_measures.tab.list", - "value": "list", - }, - Object { "icon": <TreeIcon />, "label": "component_measures.tab.tree", "value": "tree", }, Object { + "icon": <ListIcon />, + "label": "component_measures.tab.list", + "value": "list", + }, + Object { "icon": <TreemapIcon />, "label": "component_measures.tab.treemap", "value": "treemap", diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.tsx.snap index 9849310b4c2..002f43d4c7e 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.tsx.snap @@ -2,20 +2,20 @@ exports[`should display correctly for a file 1`] = ` <div - className="pull-right" + className="display-flex-center" > <div - className="measure-details-page-actions" + className="measure-details-page-actions nowrap" /> </div> `; exports[`should display correctly for a file 2`] = ` <div - className="pull-right" + className="display-flex-center" > <span - className="note spacer-right" + className="note spacer-right nowrap" > <span> <span @@ -32,7 +32,7 @@ exports[`should display correctly for a file 2`] = ` </span> </span> <div - className="measure-details-page-actions" + className="measure-details-page-actions nowrap" > <FilesCounter className="spacer-left" @@ -44,10 +44,10 @@ exports[`should display correctly for a file 2`] = ` exports[`should display correctly for a project 1`] = ` <div - className="pull-right" + className="display-flex-center" > <span - className="note big-spacer-right" + className="note big-spacer-right nowrap" > <span className="big-spacer-right" @@ -79,17 +79,17 @@ exports[`should display correctly for a project 1`] = ` </span> </span> <div - className="measure-details-page-actions" + className="measure-details-page-actions nowrap" /> </div> `; exports[`should display the total of files 1`] = ` <div - className="pull-right" + className="display-flex-center" > <div - className="measure-details-page-actions" + className="measure-details-page-actions nowrap" > <FilesCounter className="spacer-left" @@ -102,10 +102,10 @@ exports[`should display the total of files 1`] = ` exports[`should display the total of files 2`] = ` <div - className="pull-right" + className="display-flex-center" > <span - className="note spacer-right" + className="note spacer-right nowrap" > <span> <span @@ -122,7 +122,7 @@ exports[`should display the total of files 2`] = ` </span> </span> <div - className="measure-details-page-actions" + className="measure-details-page-actions nowrap" > <FilesCounter className="spacer-left" @@ -135,10 +135,10 @@ exports[`should display the total of files 2`] = ` exports[`should not display shortcuts for treemap 1`] = ` <div - className="pull-right" + className="display-flex-center" > <div - className="measure-details-page-actions" + className="measure-details-page-actions nowrap" /> </div> `; 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 e9621984a29..d4f364f6f34 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 @@ -31,6 +31,7 @@ import { Alert } from '../../../components/ui/Alert'; interface Props { branchLike?: T.BranchLike; components: T.ComponentMeasureEnhanced[]; + defaultShowBestMeasures: boolean; fetchMore: () => void; handleSelect: (component: string) => void; handleOpen: (component: string) => void; @@ -47,12 +48,12 @@ interface State { showBestMeasures: boolean; } -export default class ListView extends React.PureComponent<Props, State> { +export default class FilesView extends React.PureComponent<Props, State> { listContainer?: HTMLElement | null; constructor(props: Props) { super(props); - this.state = { showBestMeasures: false }; + this.state = { showBestMeasures: props.defaultShowBestMeasures }; this.selectNext = throttle(this.selectNext, 100); this.selectPrevious = throttle(this.selectPrevious, 100); } @@ -69,7 +70,7 @@ export default class ListView extends React.PureComponent<Props, State> { this.scrollToElement(); } if (prevProps.metric.key !== this.props.metric.key) { - this.setState({ showBestMeasures: false }); + this.setState({ showBestMeasures: this.props.defaultShowBestMeasures }); } } 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 8001e681cbd..425024d3e86 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 @@ -58,6 +58,7 @@ function getWrapper(props = {}) { return shallow( <FilesView components={COMPONENTS} + defaultShowBestMeasures={false} fetchMore={jest.fn()} handleOpen={jest.fn()} handleSelect={jest.fn()} diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx index 2da615d12e7..c81765cde2f 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.tsx @@ -35,7 +35,8 @@ import { getLocalizedCategoryMetricName, getLocalizedMetricDomain, getLocalizedMetricName, - translate + translate, + hasMessage } from '../../../helpers/l10n'; interface Props { @@ -148,12 +149,12 @@ export default class DomainFacet extends React.PureComponent<Props> { render() { const { domain } = this.props; - const helper = `component_measures.domain_facets.${domain.name}.help`; - const translatedHelper = translate(helper); + const helperMessageKey = `component_measures.domain_facets.${domain.name}.help`; + const helper = hasMessage(helperMessageKey) ? translate(helperMessageKey) : undefined; return ( <FacetBox property={domain.name}> <FacetHeader - helper={helper !== translatedHelper ? translatedHelper : undefined} + helper={helper} name={getLocalizedMetricDomain(domain.name)} onClick={this.handleHeaderClick} open={this.props.open} diff --git a/server/sonar-web/src/main/js/apps/component-measures/style.css b/server/sonar-web/src/main/js/apps/component-measures/style.css index e6689fb2877..853316d50e1 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/style.css +++ b/server/sonar-web/src/main/js/apps/component-measures/style.css @@ -156,10 +156,20 @@ border-top-right-radius: 4px; } -.measure-breadcrumbs { - display: inline-block; - max-width: 60%; - vertical-align: middle; +.measure-content-header { + display: flex; + align-items: center; +} + +.measure-content-header-left { + flex: 1; + min-width: 0; + white-space: nowrap; +} + +.measure-content-header-right { + margin-left: calc(2 * var(--gridSize)); + white-space: nowrap; } .measure-favorite svg { 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 988897e731a..49b0b7a6775 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 @@ -26,8 +26,10 @@ import { cleanQuery, parseAsString, RawQuery, serializeString } from '../../help import { isLongLivingBranch, isMainBranch } from '../../helpers/branches'; import { getDisplayMetrics } from '../../helpers/measures'; +export type View = 'list' | 'tree' | 'treemap'; + export const PROJECT_OVERVEW = 'project_overview'; -export const DEFAULT_VIEW = 'list'; +export const DEFAULT_VIEW: View = 'tree'; export const DEFAULT_METRIC = PROJECT_OVERVEW; export const KNOWN_DOMAINS = [ 'Releasability', @@ -122,7 +124,7 @@ export const groupByDomains = memoize((measures: T.MeasureEnhanced[]) => { ]); }); -export function getDefaultView(metric: string): string { +export function getDefaultView(metric: string): View { if (!hasList(metric)) { return 'tree'; } @@ -196,35 +198,37 @@ export function isProjectOverview(metric: string) { return metric === PROJECT_OVERVEW; } -const parseView = (metric: string, rawView?: string) => { - const view = parseAsString(rawView) || DEFAULT_VIEW; +function parseView(metric: string, rawView?: string): View { + const view = (parseAsString(rawView) || DEFAULT_VIEW) as View; if (!hasTree(metric)) { return 'list'; } else if (view === 'list' && !hasList(metric)) { return 'tree'; } return view; -}; +} export interface Query { metric: string; selected?: string; - view: string; + view: View; } -export const parseQuery = memoize((urlQuery: RawQuery) => { - const metric = parseAsString(urlQuery['metric']) || DEFAULT_METRIC; - return { - metric, - selected: parseAsString(urlQuery['selected']), - view: parseView(metric, urlQuery['view']) - }; -}); +export const parseQuery = memoize( + (urlQuery: RawQuery): Query => { + const metric = parseAsString(urlQuery['metric']) || DEFAULT_METRIC; + return { + metric, + selected: parseAsString(urlQuery['selected']), + view: parseView(metric, urlQuery['view']) + }; + } +); export const serializeQuery = memoize((query: Query) => { return cleanQuery({ - metric: query.metric === DEFAULT_METRIC ? null : serializeString(query.metric), + metric: query.metric === DEFAULT_METRIC ? undefined : serializeString(query.metric), selected: serializeString(query.selected), - view: query.view === DEFAULT_VIEW ? null : serializeString(query.view) + view: query.view === DEFAULT_VIEW ? undefined : serializeString(query.view) }); }); diff --git a/server/sonar-web/src/main/js/helpers/l10n.ts b/server/sonar-web/src/main/js/helpers/l10n.ts index 4da10579dd1..6e13e95de91 100644 --- a/server/sonar-web/src/main/js/helpers/l10n.ts +++ b/server/sonar-web/src/main/js/helpers/l10n.ts @@ -147,12 +147,6 @@ export function installGlobal() { (window as any).requestMessages = requestMessages; } -export function getLocalizedDashboardName(baseName: string) { - const l10nKey = `dashboard.${baseName}.name`; - const l10nLabel = translate(l10nKey); - return l10nLabel !== l10nKey ? l10nLabel : baseName; -} - export function getLocalizedMetricName( metric: { key: string; name?: string }, short?: boolean @@ -160,24 +154,21 @@ export function getLocalizedMetricName( const bundleKey = `metric.${metric.key}.${short ? 'short_name' : 'name'}`; if (hasMessage(bundleKey)) { return translate(bundleKey); + } else if (short) { + return getLocalizedMetricName(metric); } else { - if (short) { - return getLocalizedMetricName(metric); - } return metric.name || metric.key; } } export function getLocalizedCategoryMetricName(metric: { key: string; name?: string }) { const bundleKey = `metric.${metric.key}.extra_short_name`; - const fromBundle = translate(bundleKey); - return fromBundle === bundleKey ? getLocalizedMetricName(metric, true) : fromBundle; + return hasMessage(bundleKey) ? translate(bundleKey) : getLocalizedMetricName(metric, true); } export function getLocalizedMetricDomain(domainName: string) { const bundleKey = `metric_domain.${domainName}`; - const fromBundle = translate(bundleKey); - return fromBundle !== bundleKey ? fromBundle : domainName; + return hasMessage(bundleKey) ? translate(bundleKey) : domainName; } export function getCurrentLocale() { |