From cde3f86411732cfe93d92d25ae6035c3a5aad519 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 9 Aug 2017 09:17:18 +0200 Subject: [PATCH] SONAR-9614 Add buttons to navigate through open files on measures page --- .../components/MeasureContent.js | 15 +- .../components/MeasureHeader.js | 160 ++++++++++++------ .../__tests__/MeasureHeader-test.js | 20 ++- .../__snapshots__/MeasureHeader-test.js.snap | 155 ++++++++++++++--- .../main/js/apps/component-measures/style.css | 6 + 5 files changed, 274 insertions(+), 82 deletions(-) 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 66e9fe47756..8cde6abfd8e 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 @@ -89,9 +89,10 @@ export default class MeasureContent extends React.PureComponent { } getSelectedIndex = (): ?number => { - const index = this.state.components.findIndex( - component => component.key === this.state.selected - ); + 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 : null; }; @@ -121,7 +122,7 @@ export default class MeasureContent extends React.PureComponent { fetchComponents = ({ component, metric, metrics, view }: Props) => { if (isFileType(component)) { - return this.setState({ components: [], metric: null, paging: null, view: null }); + return this.setState({ metric: null, view: null }); } const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, metric); @@ -244,6 +245,7 @@ export default class MeasureContent extends React.PureComponent { const { component, currentUser, measure, metric, rootComponent, view } = this.props; const isLoggedIn = currentUser && currentUser.isLoggedIn; const isFile = isFileType(component); + const selectedIdx = this.getSelectedIndex(); return (
@@ -269,7 +271,7 @@ export default class MeasureContent extends React.PureComponent { view={view} />} {this.renderContent()}
} 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 b08974be9b6..5c3a3a18282 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 @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +// @flow import React from 'react'; import { Link } from 'react-router'; import ComplexityDistribution from '../../../components/shared/ComplexityDistribution'; @@ -26,7 +27,8 @@ import LanguageDistribution from '../../../components/charts/LanguageDistributio import LeakPeriodLegend from './LeakPeriodLegend'; import Measure from '../../../components/measure/Measure'; import Tooltip from '../../../components/controls/Tooltip'; -import { getLocalizedMetricName, translate } from '../../../helpers/l10n'; +import { isFileType } from '../utils'; +import { getLocalizedMetricName, translate, translateWithParameters } from '../../../helpers/l10n'; import { getComponentMeasureHistory } from '../../../helpers/urls'; import { isDiffMetric } from '../../../helpers/measures'; import type { Component, Period } from '../types'; @@ -34,57 +36,119 @@ import type { MeasureEnhanced } from '../../../components/measure/types'; type Props = {| component: Component, + components: Array, leakPeriod?: Period, + handleSelect: string => void, measure: MeasureEnhanced, - secondaryMeasure: ?MeasureEnhanced + secondaryMeasure: ?MeasureEnhanced, + selectedIdx: ?number |}; -export default function MeasureHeader({ component, leakPeriod, measure, secondaryMeasure }: Props) { - const metric = measure.metric; - const isDiff = isDiffMetric(metric.key); - const hasHistory = ['TRK', 'VW', 'SVW', 'APP'].includes(component.qualifier); - return ( -
-
-
- - {getLocalizedMetricName(metric)} - - - {isDiff - ? - : } - - - {!isDiff && - hasHistory && - - - - - } +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 + )} + } +
+ {hasPrevious && } + {hasNext && } +
+
+ ); + } + + render() { + const { component, components, leakPeriod, measure, secondaryMeasure } = this.props; + const metric = measure.metric; + const isDiff = isDiffMetric(metric.key); + const hasHistory = ['TRK', 'VW', 'SVW', 'APP'].includes(component.qualifier); + const hasComponents = components && components.length > 1; + return ( +
+
+
+ + {getLocalizedMetricName(metric)} + + + {isDiff + ? + : } + + + {!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/__tests__/MeasureHeader-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js index 34ea5cd64d1..46734ad83ca 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 @@ -54,6 +54,8 @@ const SECONDARY = { const PROPS = { component: { key: 'foo', qualifier: 'TRK' }, + components: [], + handleSelect: () => {}, leakPeriod: { date: '2017-05-16T13:50:02+0200', index: 1, @@ -61,7 +63,9 @@ const PROPS = { parameter: '6,4' }, measure: MEASURE, - secondaryMeasure: null + paging: null, + secondaryMeasure: null, + selectedIdx: null }; it('should render correctly', () => { @@ -76,3 +80,17 @@ it('should display secondary measure too', () => { const wrapper = shallow(); expect(wrapper.find('LanguageDistribution')).toHaveLength(1); }); + +it('shohuld display correctly for open file', () => { + const wrapper = shallow( + + ); + expect(wrapper.find('.measure-details-primary-actions')).toMatchSnapshot(); + wrapper.setProps({ components: [{ key: 'foo' }, { key: 'bar' }] }); + expect(wrapper.find('.measure-details-primary-actions')).toMatchSnapshot(); +}); 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 775d3af72a4..fae4fa473aa 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,5 +1,94 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`shohuld display correctly for open file 1`] = ` +
+
+ + component_measures.x_of_y.2.3 + +
+ + +
+
+ +
+`; + +exports[`shohuld display correctly for open file 2`] = ` +
+
+ + component_measures.x_of_y.2.2 + +
+ +
+
+ +
+`; + exports[`should render correctly 1`] = `
- + + /> +
`; @@ -138,22 +232,27 @@ exports[`should render correctly for leak 1`] = ` - + + /> + `; 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 2dba101338e..699bd21b96e 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 @@ -19,6 +19,7 @@ } .domain-measures-leak-header { + display: inline-block; background-color: #fbf3d5; border: 1px solid #eae3c7; padding: 4px 10px; @@ -46,6 +47,11 @@ align-items: center; } +.measure-details-primary-actions { + display: flex; + align-items: center; +} + .measure-details-secondary { display: inline-block; width: 260px; -- 2.39.5