From 544f801417477cfea59d1ec4067d4d2484aa2816 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Thu, 21 Sep 2017 15:13:58 +0200 Subject: [PATCH] SONAR-9689 Replaced file navigation button with j/k shortcut in measures page --- .../components/MeasureContent.js | 31 ++-- .../components/MeasureHeader.js | 175 ++++++------------ .../components/PageActions.js | 16 +- .../__tests__/MeasureHeader-test.js | 2 +- .../components/__tests__/PageActions-test.js | 28 ++- .../__snapshots__/MeasureHeader-test.js.snap | 45 +---- .../__snapshots__/PageActions-test.js.snap | 73 +++++++- .../component-measures/drilldown/CodeView.js | 103 +++++++++++ .../component-measures/drilldown/FilesView.js | 23 ++- .../resources/org/sonar/l10n/core.properties | 2 +- 10 files changed, 299 insertions(+), 199 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/CodeView.js diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js index 47675037a7b..16ba5d4ba82 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js @@ -20,6 +20,7 @@ // @flow import React from 'react'; import classNames from 'classnames'; +import CodeView from '../drilldown/CodeView'; import Breadcrumbs from './Breadcrumbs'; import FilesView from '../drilldown/FilesView'; import MeasureFavoriteContainer from './MeasureFavoriteContainer'; @@ -27,14 +28,12 @@ import MeasureHeader from './MeasureHeader'; import MeasureViewSelect from './MeasureViewSelect'; import MetricNotFound from './MetricNotFound'; import PageActions from './PageActions'; -import SourceViewer from '../../../components/SourceViewer/SourceViewer'; import TreeMapView from '../drilldown/TreeMapView'; import { getComponentTree } from '../../../api/components'; import { complementary } from '../config/complementary'; import { enhanceComponent, isFileType, isViewType } from '../utils'; import { getProjectUrl } from '../../../helpers/urls'; import { isDiffMetric } from '../../../helpers/measures'; -import { parseDate } from '../../../helpers/dates'; /*:: import type { Component, ComponentEnhanced, Paging, Period } from '../types'; */ /*:: import type { MeasureEnhanced } from '../../../components/measure/types'; */ /*:: import type { Metric } from '../../../store/metrics/actions'; */ @@ -223,24 +222,17 @@ export default class MeasureContent extends React.PureComponent { onSelectComponent = (componentKey /*: string */) => this.setState({ selected: componentKey }); renderCode() { - const { branch, component, leakPeriod } = this.props; - const leakPeriodDate = - isDiffMetric(this.props.metric.key) && leakPeriod != null ? parseDate(leakPeriod.date) : null; - - let filterLine; - if (leakPeriodDate != null) { - filterLine = line => { - if (line.scmDate) { - const scmDate = parseDate(line.scmDate); - return scmDate >= leakPeriodDate; - } else { - return false; - } - }; - } return (
- +
); } @@ -322,6 +314,7 @@ export default class MeasureContent extends React.PureComponent { loading={this.props.loading} isFile={isFile} paging={this.state.paging} + totalLoadedComponents={this.state.components.length} view={view} /> @@ -337,11 +330,9 @@ export default class MeasureContent extends React.PureComponent { branch={branch} component={component} components={this.state.components} - handleSelect={this.props.updateSelected} leakPeriod={this.props.leakPeriod} measure={measure} secondaryMeasure={this.props.secondaryMeasure} - selectedIdx={selectedIdx} /> {isFileType(component) ? this.renderCode() : this.renderMeasure()} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js index 49f039c35d1..81853659e39 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js @@ -27,8 +27,7 @@ import LanguageDistributionContainer from '../../../components/charts/LanguageDi import LeakPeriodLegend from './LeakPeriodLegend'; import Measure from '../../../components/measure/Measure'; import Tooltip from '../../../components/controls/Tooltip'; -import { isFileType } from '../utils'; -import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; +import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; import { getMeasureHistoryUrl } from '../../../helpers/urls'; import { isDiffMetric } from '../../../helpers/measures'; /*:: import type { Component, Period } from '../types'; */ @@ -39,124 +38,70 @@ import { isDiffMetric } from '../../../helpers/measures'; component: Component, components: Array, leakPeriod?: Period, - handleSelect: string => void, measure: MeasureEnhanced, - secondaryMeasure: ?MeasureEnhanced, - selectedIdx: ?number + secondaryMeasure: ?MeasureEnhanced |}; */ -export default class MeasureHeader extends React.PureComponent { - /*:: props: Props; */ - - handleSelectPrevious = (e /*: Event & { target: HTMLElement } */) => { - e.target.blur(); - if (this.props.selectedIdx != null) { - const prevComponent = this.props.components[this.props.selectedIdx - 1]; - if (prevComponent) { - this.props.handleSelect(prevComponent.key); - } - } - }; - - handleSelectNext = (e /*: Event & { target: HTMLElement } */) => { - e.target.blur(); - if (this.props.selectedIdx != null) { - const prevComponent = this.props.components[this.props.selectedIdx + 1]; - if (prevComponent) { - this.props.handleSelect(prevComponent.key); - } - } - }; - - renderFileNav() { - const { components, selectedIdx } = this.props; - if (selectedIdx == null) { - return null; - } - const hasPrevious = selectedIdx > 0; - const hasNext = selectedIdx < components.length - 1; - return ( -
- {components.length > 0 && ( - - {translateWithParameters( - 'component_measures.x_of_y', - selectedIdx + 1, - components.length - )} +export default function MeasureHeader(props /*: Props*/) { + const { branch, component, leakPeriod, measure, secondaryMeasure } = props; + const metric = measure.metric; + const isDiff = isDiffMetric(metric.key); + const hasHistory = !isDiff && ['TRK', 'VW', 'SVW', 'APP'].includes(component.qualifier); + return ( +
+
+
+ + {getLocalizedMetricName(metric)} + + + {isDiff ? ( + + ) : ( + + )} + - )} -
- {hasPrevious && } - {hasNext && } + {hasHistory && ( + + + + + + )}
-
- ); - } - - render() { - const { branch, component, components, leakPeriod, measure, secondaryMeasure } = this.props; - const metric = measure.metric; - const isDiff = isDiffMetric(metric.key); - const hasHistory = !isDiff && ['TRK', 'VW', 'SVW', 'APP'].includes(component.qualifier); - const hasComponents = components && components.length > 1; - return ( -
-
-
- - {getLocalizedMetricName(metric)} - - - {isDiff ? ( - - ) : ( - - )} - - - {hasHistory && ( - - - - - - )} -
-
- {hasComponents && isFileType(component) && this.renderFileNav()} - {leakPeriod != null && ( - - )} -
+
+ {leakPeriod != null && ( + + )}
- {secondaryMeasure && - secondaryMeasure.metric.key === 'ncloc_language_distribution' && ( -
- -
- )} - {secondaryMeasure && - secondaryMeasure.metric.key === 'function_complexity_distribution' && ( -
- -
- )} - {secondaryMeasure && - secondaryMeasure.metric.key === 'file_complexity_distribution' && ( -
- -
- )}
- ); - } + {secondaryMeasure && + secondaryMeasure.metric.key === 'ncloc_language_distribution' && ( +
+ +
+ )} + {secondaryMeasure && + secondaryMeasure.metric.key === 'function_complexity_distribution' && ( +
+ +
+ )} + {secondaryMeasure && + secondaryMeasure.metric.key === 'file_complexity_distribution' && ( +
+ +
+ )} +
+ ); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js b/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js index eb76a1bd635..2eb70789932 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js @@ -29,22 +29,27 @@ import { translate } from '../../../helpers/l10n'; loading: boolean, isFile: ?boolean, paging: ?Paging, + totalLoadedComponents?: number, view?: string |}; */ export default function PageActions(props /*: Props */) { - const { isFile, paging } = props; + const { isFile, paging, totalLoadedComponents } = props; const showShortcuts = ['list', 'tree'].includes(props.view); return (
{!isFile && showShortcuts && renderShortcuts()} - {isFile && renderFileShortcuts()} + {isFile && paging && renderFileShortcuts()}
{paging != null && ( - + )}
@@ -73,8 +78,9 @@ function renderFileShortcuts() { return ( - ← - {translate('component_measures.to_navigate_back')} + j + k + {translate('component_measures.to_navigate_files')} ); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js index 8164a603717..4f4e2020f67 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js @@ -85,7 +85,7 @@ it('should display secondary measure too', () => { expect(wrapper.find('Connect(LanguageDistribution)')).toHaveLength(1); }); -it('shohuld display correctly for open file', () => { +it('should display correctly for open file', () => { const wrapper = shallow( { - expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('should display correctly for a file', () => { - expect(shallow()).toMatchSnapshot(); + const wrapper = shallow( + + ); + expect(wrapper).toMatchSnapshot(); + wrapper.setProps({ paging: { total: 100 } }); + expect(wrapper).toMatchSnapshot(); }); it('should not display shortcuts for treemap', () => { - expect(shallow()).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); }); it('should display the total of files', () => { @@ -41,6 +50,19 @@ it('should display the total of files', () => { loading={true} isFile={false} view="treemap" + totalLoadedComponents={20} + paging={{ total: 120 }} + /> + ) + ).toMatchSnapshot(); + expect( + shallow( + ) diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap index 52e73da3bc3..ec3b0d5395c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap @@ -1,32 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`shohuld display correctly for open file 1`] = ` +exports[`should display correctly for open file 1`] = `
-
- - component_measures.x_of_y.2.3 - -
- - -
-
`; -exports[`shohuld display correctly for open file 2`] = ` +exports[`should display correctly for open file 2`] = `
-
- - component_measures.x_of_y.2.2 - -
- -
-
+
+ + + +
+
+`; + +exports[`should display correctly for a file 2`] = `
@@ -11,9 +30,14 @@ exports[`should display correctly for a file 1`] = ` - ← + j + + + k - component_measures.to_navigate_back + component_measures.to_navigate_files
+
`; @@ -106,6 +134,47 @@ exports[`should display the total of files 1`] = `
`; +exports[`should display the total of files 2`] = ` +
+ + + + j + + + k + + component_measures.to_navigate_files + + +
+ + + + +
+
+`; + exports[`should not display shortcuts for treemap 1`] = `
, + leakPeriod?: Period, + metric: Metric, + selectedIdx: ?number, + updateSelected: string => void, +|}; */ + +export default class CodeView extends React.PureComponent { + /*:: props: Props; */ + + componentDidMount() { + this.attachShortcuts(); + } + + componentWillUnmount() { + this.detachShortcuts(); + } + + attachShortcuts() { + key('j', 'measures-files', () => { + this.selectNext(); + return false; + }); + key('k', 'measures-files', () => { + this.selectPrevious(); + return false; + }); + } + + detachShortcuts() { + ['j', 'k'].map(action => key.unbind(action, 'measures-files')); + } + + selectPrevious = () => { + const { selectedIdx } = this.props; + if (selectedIdx != null && selectedIdx > 0) { + const prevComponent = this.props.components[selectedIdx - 1]; + if (prevComponent) { + this.props.updateSelected(prevComponent.key); + } + } + }; + + selectNext = () => { + const { components, selectedIdx } = this.props; + if (selectedIdx != null && selectedIdx < components.length - 1) { + const nextComponent = components[selectedIdx + 1]; + if (nextComponent) { + this.props.updateSelected(nextComponent.key); + } + } + }; + + render() { + const { branch, component, leakPeriod } = this.props; + const leakPeriodDate = + isDiffMetric(this.props.metric.key) && leakPeriod != null ? parseDate(leakPeriod.date) : null; + + let filterLine; + if (leakPeriodDate != null) { + filterLine = line => { + if (line.scmDate) { + const scmDate = parseDate(line.scmDate); + return scmDate >= leakPeriodDate; + } else { + return false; + } + }; + } + return ; + } +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js index e13ec374232..1d7be5e5b3b 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js +++ b/server/sonar-web/src/main/js/apps/component-measures/drilldown/FilesView.js @@ -52,18 +52,14 @@ export default class ListView extends React.PureComponent { componentDidMount() { this.attachShortcuts(); + if (this.props.selectedKey != null) { + this.scrollToElement(); + } } componentDidUpdate(prevProps /*: Props */) { - if ( - this.listContainer && - this.props.selectedKey != null && - prevProps.selectedKey !== this.props.selectedKey - ) { - const elem = this.listContainer.getElementsByClassName('selected')[0]; - if (elem) { - scrollToElement(elem, { topOffset: 215, bottomOffset: 100 }); - } + if (this.props.selectedKey != null && prevProps.selectedKey !== this.props.selectedKey) { + this.scrollToElement(); } } @@ -114,6 +110,15 @@ export default class ListView extends React.PureComponent { } }; + scrollToElement = () => { + if (this.listContainer) { + const elem = this.listContainer.getElementsByClassName('selected')[0]; + if (elem) { + scrollToElement(elem, { topOffset: 215, bottomOffset: 100 }); + } + } + }; + render() { return (
(this.listContainer = elem)}> diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index ec8038ea2ec..4ac106215fe 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2965,7 +2965,7 @@ component_measures.no_history=There is no historical data. component_measures.not_found=The requested measure was not found. component_measures.to_select_files=to select files component_measures.to_navigate=to navigate -component_measures.to_navigate_back=to navigate back +component_measures.to_navigate_files=to next/previous file component_measures.overview.project_overview.facet=Project Overview component_measures.overview.project_overview.title=Risk -- 2.39.5