aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js15
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js160
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js20
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap155
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/style.css6
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}>&lt;</button>}
+ {hasNext && <button onClick={this.handleSelectNext}>&gt;</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]}
+ >
+ &lt;
+ </button>
+ <button
+ onClick={[Function]}
+ >
+ &gt;
+ </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]}
+ >
+ &lt;
+ </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;