diff options
Diffstat (limited to 'server')
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 ( <div className={this.props.className}> <div className="layout-page-header-panel layout-page-main-header issues-main-header"> @@ -269,7 +271,7 @@ export default class MeasureContent extends React.PureComponent { view={view} />} <PageActions - current={this.getSelectedIndex() + 1} + current={selectedIdx + 1} loading={this.props.loading} isFile={isFile} paging={this.state.paging} @@ -284,9 +286,12 @@ export default class MeasureContent extends React.PureComponent { <div className="layout-page-main-inner"> <MeasureHeader component={component} + components={this.state.components} + handleSelect={this.props.updateSelected} leakPeriod={this.props.leakPeriod} measure={measure} secondaryMeasure={this.props.secondaryMeasure} + selectedIdx={selectedIdx} /> {this.renderContent()} </div>} 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<Component>, 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 ( - <div className="measure-details-header big-spacer-bottom"> - <div className="measure-details-primary"> - <div className="measure-details-metric"> - <IssueTypeIcon query={metric.key} className="little-spacer-right text-text-bottom" /> - {getLocalizedMetricName(metric)} - <span className="measure-details-value spacer-left"> - <strong> - {isDiff - ? <Measure className="domain-measures-leak" measure={measure} metric={metric} /> - : <Measure measure={measure} metric={metric} />} - </strong> - </span> - {!isDiff && - hasHistory && - <Tooltip - placement="right" - overlay={translate('component_measures.show_metric_history')}> - <Link - className="js-show-history spacer-left button button-small button-compact" - to={getComponentMeasureHistory(component.key, metric.key)}> - <HistoryIcon /> - </Link> - </Tooltip>} +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 ( + <div className="display-inline-block"> + {components.length > 0 && + <span className="note spacer-right"> + {translateWithParameters( + 'component_measures.x_of_y', + selectedIdx + 1, + components.length + )} + </span>} + <div className="button-group"> + {hasPrevious && <button onClick={this.handleSelectPrevious}><</button>} + {hasNext && <button onClick={this.handleSelectNext}>></button>} + </div> + </div> + ); + } + + 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 ( + <div className="measure-details-header big-spacer-bottom"> + <div className="measure-details-primary"> + <div className="measure-details-metric"> + <IssueTypeIcon query={metric.key} className="little-spacer-right text-text-bottom" /> + {getLocalizedMetricName(metric)} + <span className="measure-details-value spacer-left"> + <strong> + {isDiff + ? <Measure className="domain-measures-leak" measure={measure} metric={metric} /> + : <Measure measure={measure} metric={metric} />} + </strong> + </span> + {!isDiff && + hasHistory && + <Tooltip + placement="right" + overlay={translate('component_measures.show_metric_history')}> + <Link + className="js-show-history spacer-left button button-small button-compact" + to={getComponentMeasureHistory(component.key, metric.key)}> + <HistoryIcon /> + </Link> + </Tooltip>} + </div> + <div className="measure-details-primary-actions"> + {hasComponents && isFileType(component) && this.renderFileNav()} + {leakPeriod != null && + <LeakPeriodLegend + className="spacer-left" + component={component} + period={leakPeriod} + />} + </div> </div> - {leakPeriod != null && <LeakPeriodLegend component={component} period={leakPeriod} />} + {secondaryMeasure && + secondaryMeasure.metric.key === 'ncloc_language_distribution' && + <div className="measure-details-secondary"> + <LanguageDistribution alignTicks={true} distribution={secondaryMeasure.value} /> + </div>} + {secondaryMeasure && + secondaryMeasure.metric.key === 'function_complexity_distribution' && + <div className="measure-details-secondary"> + <ComplexityDistribution distribution={secondaryMeasure.value} of="function" /> + </div>} + {secondaryMeasure && + secondaryMeasure.metric.key === 'file_complexity_distribution' && + <div className="measure-details-secondary"> + <ComplexityDistribution distribution={secondaryMeasure.value} of="file" /> + </div>} </div> - {secondaryMeasure && - secondaryMeasure.metric.key === 'ncloc_language_distribution' && - <div className="measure-details-secondary"> - <LanguageDistribution alignTicks={true} distribution={secondaryMeasure.value} /> - </div>} - {secondaryMeasure && - secondaryMeasure.metric.key === 'function_complexity_distribution' && - <div className="measure-details-secondary"> - <ComplexityDistribution distribution={secondaryMeasure.value} of="function" /> - </div>} - {secondaryMeasure && - secondaryMeasure.metric.key === 'file_complexity_distribution' && - <div className="measure-details-secondary"> - <ComplexityDistribution distribution={secondaryMeasure.value} of="file" /> - </div>} - </div> - ); + ); + } } 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(<MeasureHeader {...PROPS} secondaryMeasure={SECONDARY} />); expect(wrapper.find('LanguageDistribution')).toHaveLength(1); }); + +it('shohuld display correctly for open file', () => { + const wrapper = shallow( + <MeasureHeader + {...PROPS} + component={{ key: 'bar', qualifier: 'FIL' }} + components={[{ key: 'foo' }, { key: 'bar' }, { key: 'baz' }]} + selectedIdx={1} + /> + ); + 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`] = ` +<div + className="measure-details-primary-actions" +> + <div + className="display-inline-block" + > + <span + className="note spacer-right" + > + component_measures.x_of_y.2.3 + </span> + <div + className="button-group" + > + <button + onClick={[Function]} + > + < + </button> + <button + onClick={[Function]} + > + > + </button> + </div> + </div> + <LeakPeriodLegend + className="spacer-left" + component={ + Object { + "key": "bar", + "qualifier": "FIL", + } + } + period={ + Object { + "date": "2017-05-16T13:50:02+0200", + "index": 1, + "mode": "previous_version", + "parameter": "6,4", + } + } + /> +</div> +`; + +exports[`shohuld display correctly for open file 2`] = ` +<div + className="measure-details-primary-actions" +> + <div + className="display-inline-block" + > + <span + className="note spacer-right" + > + component_measures.x_of_y.2.2 + </span> + <div + className="button-group" + > + <button + onClick={[Function]} + > + < + </button> + </div> + </div> + <LeakPeriodLegend + className="spacer-left" + component={ + Object { + "key": "bar", + "qualifier": "FIL", + } + } + period={ + Object { + "date": "2017-05-16T13:50:02+0200", + "index": 1, + "mode": "previous_version", + "parameter": "6,4", + } + } + /> +</div> +`; + exports[`should render correctly 1`] = ` <div className="measure-details-header big-spacer-bottom" @@ -70,22 +159,27 @@ exports[`should render correctly 1`] = ` </Link> </Tooltip> </div> - <LeakPeriodLegend - component={ - Object { - "key": "foo", - "qualifier": "TRK", + <div + className="measure-details-primary-actions" + > + <LeakPeriodLegend + className="spacer-left" + component={ + Object { + "key": "foo", + "qualifier": "TRK", + } } - } - period={ - Object { - "date": "2017-05-16T13:50:02+0200", - "index": 1, - "mode": "previous_version", - "parameter": "6,4", + period={ + Object { + "date": "2017-05-16T13:50:02+0200", + "index": 1, + "mode": "previous_version", + "parameter": "6,4", + } } - } - /> + /> + </div> </div> </div> `; @@ -138,22 +232,27 @@ exports[`should render correctly for leak 1`] = ` </strong> </span> </div> - <LeakPeriodLegend - component={ - Object { - "key": "foo", - "qualifier": "TRK", + <div + className="measure-details-primary-actions" + > + <LeakPeriodLegend + className="spacer-left" + component={ + Object { + "key": "foo", + "qualifier": "TRK", + } } - } - period={ - Object { - "date": "2017-05-16T13:50:02+0200", - "index": 1, - "mode": "previous_version", - "parameter": "6,4", + period={ + Object { + "date": "2017-05-16T13:50:02+0200", + "index": 1, + "mode": "previous_version", + "parameter": "6,4", + } } - } - /> + /> + </div> </div> </div> `; 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; |