aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-08-02 14:25:57 +0200
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>2017-08-14 11:44:44 +0200
commit5be60c5d3348076336e5a79e6308104db52f27dc (patch)
tree498f4b70fbbe58d9b84406301b687265814cf267 /server/sonar-web/src/main/js/apps
parentd7b669175e4e40341f6f1553ebe8ed84a9980ce2 (diff)
downloadsonarqube-5be60c5d3348076336e5a79e6308104db52f27dc.tar.gz
sonarqube-5be60c5d3348076336e5a79e6308104db52f27dc.zip
SONAR-9608 SONAR-9613 Add the page actions and a select to switch between views
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/App.js3
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.js67
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js95
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.js125
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureHeader.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.js95
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js76
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.js56
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.js70
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.js30
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js34
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.js.snap1
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.js.snap23
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.js.snap93
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.js.snap2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.js.snap81
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.js.snap93
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ComponentCell.js12
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ListView.js38
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js2
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap8
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/style.css34
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/utils.js3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/PageActions.js2
27 files changed, 973 insertions, 78 deletions
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/App.js b/server/sonar-web/src/main/js/apps/component-measures/components/App.js
index 7b9ac879874..2524de80259 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/App.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/App.js
@@ -32,6 +32,7 @@ import '../style.css';
type Props = {|
component: Component,
+ currentUser: { isLoggedIn: boolean },
location: { pathname: string, query: RawQuery },
fetchMeasures: (
Component,
@@ -154,6 +155,7 @@ export default class App extends React.PureComponent {
{metric != null &&
<MeasureContent
className="layout-page-main-inner"
+ currentUser={this.props.currentUser}
rootComponent={this.props.component}
fetchMeasures={this.props.fetchMeasures}
leakPeriod={this.state.leakPeriod}
@@ -161,6 +163,7 @@ export default class App extends React.PureComponent {
metrics={this.props.metrics}
selected={query.selected}
updateQuery={this.updateQuery}
+ view={query.view}
/>}
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js
index f8979c7eea7..653380d1ad7 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/AppContainer.js
@@ -24,6 +24,7 @@ import App from './App';
import throwGlobalError from '../../../app/utils/throwGlobalError';
import {
getComponent,
+ getCurrentUser,
getMetrics,
getMetricByKey,
getMetricsKey
@@ -37,6 +38,7 @@ import type { Measure, MeasureEnhanced } from '../../../components/measure/types
const mapStateToProps = (state, ownProps) => ({
component: getComponent(state, ownProps.location.query.id),
+ currentUser: getCurrentUser(state),
metrics: getMetrics(state),
metricsKey: getMetricsKey(state)
});
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.js b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.js
new file mode 100644
index 00000000000..2be60f7c918
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumb.js
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import Tooltip from '../../../components/controls/Tooltip';
+import { collapsePath, limitComponentName } from '../../../helpers/path';
+import type { Component } from '../types';
+
+type Props = {
+ canBrowse: boolean,
+ component: Component,
+ isLast: boolean,
+ handleSelect: Component => void
+};
+
+export default class Breadcrumb extends React.PureComponent {
+ props: Props;
+
+ handleClick = (e: Event & { target: HTMLElement }) => {
+ e.preventDefault();
+ e.target.blur();
+ this.props.handleSelect(this.props.component);
+ };
+
+ render() {
+ const { canBrowse, component, isLast } = this.props;
+ const isPath = component.qualifier === 'DIR';
+ const componentName = isPath
+ ? collapsePath(component.name, 15)
+ : limitComponentName(component.name);
+ const breadcrumbItem = canBrowse
+ ? <a href="#" onClick={this.handleClick}>
+ {componentName}
+ </a>
+ : <span>
+ {componentName}
+ </span>;
+
+ return (
+ <span>
+ {component.name !== componentName
+ ? <Tooltip overlay={component.name} placement="bottom">
+ {breadcrumbItem}
+ </Tooltip>
+ : breadcrumbItem}
+ {!isLast && <span className="slash-separator" />}
+ </span>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js
new file mode 100644
index 00000000000..320a9ea93e9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/Breadcrumbs.js
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import Breadcrumb from './Breadcrumb';
+import { getBreadcrumbs } from '../../../api/components';
+import type { Component } from '../types';
+
+type Props = {
+ className?: string,
+ component: Component,
+ handleSelect: Component => void,
+ rootComponent: Component,
+ view: string
+};
+
+type State = {
+ breadcrumbs: Array<Component>
+};
+
+export default class Breadcrumbs extends React.PureComponent {
+ mounted: boolean;
+ props: Props;
+ state: State = {
+ breadcrumbs: []
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchBreadcrumbs(this.props);
+ }
+
+ componentWillReceiveProps(nextProps: Props) {
+ if (this.props.component !== nextProps.component) {
+ this.fetchBreadcrumbs(nextProps);
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchBreadcrumbs = ({ component, rootComponent, view }: Props) => {
+ const isRoot = component.key === rootComponent.key;
+ if (isRoot || view === 'list') {
+ if (this.mounted) {
+ this.setState({ breadcrumbs: isRoot ? [component] : [rootComponent, component] });
+ }
+ return;
+ }
+ getBreadcrumbs(component.key).then(breadcrumbs => {
+ if (this.mounted) {
+ this.setState({ breadcrumbs });
+ }
+ });
+ };
+
+ render() {
+ const { breadcrumbs } = this.state;
+ if (breadcrumbs.length <= 0) {
+ return null;
+ }
+ const lastItem = breadcrumbs[breadcrumbs.length - 1];
+ return (
+ <div className={this.props.className}>
+ {breadcrumbs.map(component =>
+ <Breadcrumb
+ key={component.key}
+ canBrowse={component.key !== lastItem.key}
+ component={component}
+ isLast={component.key === lastItem.key}
+ handleSelect={this.props.handleSelect}
+ />
+ )}
+ </div>
+ );
+ }
+}
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 68bc0bb3eac..a0447e10030 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
@@ -19,16 +19,23 @@
*/
// @flow
import React from 'react';
-import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import moment from 'moment';
+import Breadcrumbs from './Breadcrumbs';
+import Favorite from '../../../components/controls/Favorite';
import ListView from './drilldown/ListView';
import MeasureHeader from './MeasureHeader';
+import MeasureViewSelect from './MeasureViewSelect';
import MetricNotFound from './MetricNotFound';
+import PageActions from './PageActions';
+import SourceViewer from '../../../components/SourceViewer/SourceViewer';
+import { isDiffMetric } from '../../../helpers/measures';
import type { Component, Period, Query } from '../types';
import type { MeasureEnhanced } from '../../../components/measure/types';
import type { Metric } from '../../../store/metrics/actions';
type Props = {
className?: string,
+ currentUser: { isLoggedIn: boolean },
rootComponent: Component,
fetchMeasures: (
Component,
@@ -38,7 +45,8 @@ type Props = {
metric: Metric,
metrics: { [string]: Metric },
selected: ?string,
- updateQuery: Query => void
+ updateQuery: Query => void,
+ view: string
};
type State = {
@@ -109,7 +117,10 @@ export default class MeasureContent extends React.PureComponent {
);
};
- handleSelect = (component: Component) => this.props.updateQuery({ selected: component.key });
+ handleSelect = (component: Component) =>
+ this.props.updateQuery({
+ selected: component.key !== this.props.rootComponent.key ? component.key : null
+ });
updateLoading = (loading: { [string]: boolean }) => {
if (this.mounted) {
@@ -117,43 +128,105 @@ export default class MeasureContent extends React.PureComponent {
}
};
- render() {
- const { metric } = this.props;
- const { loading, measure } = this.state;
+ updateView = (view: string) => this.props.updateQuery({ view });
+
+ renderContent() {
+ const { component } = this.state;
+ if (!component) {
+ return null;
+ }
+
+ const { leakPeriod, metric, rootComponent, view } = this.props;
+ const isFile = component.key !== rootComponent.key && component.qualifier === 'FIL';
+
+ if (isFile) {
+ const leakPeriodDate =
+ isDiffMetric(metric.key) && leakPeriod != null ? moment(leakPeriod.date).toDate() : null;
+
+ let filterLine;
+ if (leakPeriodDate != null) {
+ filterLine = line => {
+ if (line.scmDate) {
+ const scmDate = moment(line.scmDate).toDate();
+ return scmDate >= leakPeriodDate;
+ } else {
+ return false;
+ }
+ };
+ }
+ return (
+ <div className="measure-details-viewer">
+ <SourceViewer component={component.key} filterLine={filterLine} />
+ </div>
+ );
+ }
+ if (view === 'list') {
+ return (
+ <ListView
+ component={component}
+ handleSelect={this.handleSelect}
+ metric={metric}
+ metrics={this.props.metrics}
+ updateLoading={this.updateLoading}
+ />
+ );
+ }
+ }
+
+ render() {
+ const { currentUser, metric, rootComponent, view } = this.props;
+ const { component, loading, measure } = this.state;
+ const isLoggedIn = currentUser && currentUser.isLoggedIn;
return (
<div className="layout-page-main">
<div className="layout-page-header-panel layout-page-main-header issues-main-header">
<div className="layout-page-header-panel-inner layout-page-main-header-inner">
- <div className="layout-page-main-inner">
- Page Actions
- <DeferredSpinner
- className="pull-right"
+ <div className="layout-page-main-inner clearfix">
+ {component &&
+ <Breadcrumbs
+ className="measure-breadcrumbs spacer-right text-ellipsis"
+ component={component}
+ handleSelect={this.handleSelect}
+ rootComponent={rootComponent}
+ view={view}
+ />}
+ {component &&
+ component.key !== rootComponent.key &&
+ isLoggedIn &&
+ <Favorite
+ favorite={component.isFavorite === true}
+ component={component.key}
+ className="measure-favorite spacer-right"
+ />}
+ <MeasureViewSelect
+ className="measure-view-select"
+ metric={this.props.metric}
+ handleViewChange={this.updateView}
+ view={view}
+ />
+ <PageActions
loading={loading.measure || loading.components}
+ isFile={component && component.qualifier === 'FIL'}
+ view={view}
/>
</div>
</div>
</div>
- {metric != null && measure != null
- ? <div className="layout-page-main-inner">
+ {metric == null && <MetricNotFound className="layout-page-main-inner" />}
+ {metric != null &&
+ measure != null &&
+ <div className="layout-page-main-inner">
+ {component &&
<MeasureHeader
- component={this.state.component}
+ component={component}
leakPeriod={this.props.leakPeriod}
measure={measure}
secondaryMeasure={this.state.secondaryMeasure}
- />
- <ListView
- component={this.state.component}
- handleSelect={this.handleSelect}
- leakPeriod={this.props.leakPeriod}
- loading={loading.components}
- metric={metric}
- metrics={this.props.metrics}
- selectedComponent={this.props.selected}
- updateLoading={this.updateLoading}
- />
- </div>
- : <MetricNotFound className="layout-page-main-inner" />}
+ updateQuery={this.props.updateQuery}
+ />}
+ {this.renderContent()}
+ </div>}
</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 c58748cda20..a29d87081cd 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
@@ -42,6 +42,7 @@ type Props = {
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-metric">
@@ -55,6 +56,7 @@ export default function MeasureHeader({ component, leakPeriod, measure, secondar
</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"
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.js
new file mode 100644
index 00000000000..42b22ac2213
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureViewSelect.js
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import Select from 'react-select';
+import ListIcon from '../../../components/icons-components/ListIcon';
+import TreeIcon from '../../../components/icons-components/TreeIcon';
+import TreemapIcon from '../../../components/icons-components/TreemapIcon';
+import { hasTreemap } from '../utils';
+import { translate } from '../../../helpers/l10n';
+import type { Metric } from '../../../store/metrics/actions';
+
+type Props = {
+ className?: string,
+ metric: Metric,
+ handleViewChange: (view: string) => void,
+ view: string
+};
+
+export default class MeasureViewSelect extends React.PureComponent {
+ props: Props;
+
+ getOptions = () => {
+ const { metric } = this.props;
+ const options = [];
+ options.push({
+ value: 'list',
+ label: (
+ <div>
+ <ListIcon className="little-spacer-right" />
+ {translate('component_measures.tab.list')}
+ </div>
+ ),
+ icon: <ListIcon />
+ });
+ options.push({
+ value: 'tree',
+ label: (
+ <div>
+ <TreeIcon className="little-spacer-right" />
+ {translate('component_measures.tab.tree')}
+ </div>
+ ),
+ icon: <TreeIcon />
+ });
+ if (hasTreemap(metric.type)) {
+ options.push({
+ value: 'treemap',
+ label: (
+ <div>
+ <TreemapIcon className="little-spacer-right" />
+ {translate('component_measures.tab.treemap')}
+ </div>
+ ),
+ icon: <TreemapIcon />
+ });
+ }
+ return options;
+ };
+
+ handleChange = (option: { value: string }) => this.props.handleViewChange(option.value);
+
+ renderValue = (value: { icon: Element<*> }) => value.icon;
+
+ render() {
+ return (
+ <Select
+ className={this.props.className}
+ clearable={false}
+ searchable={false}
+ value={this.props.view}
+ valueRenderer={this.renderValue}
+ options={this.getOptions()}
+ onChange={this.handleChange}
+ />
+ );
+ }
+}
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
new file mode 100644
index 00000000000..22df52e5ea9
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/PageActions.js
@@ -0,0 +1,76 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import { translate } from '../../../helpers/l10n';
+
+type Props = {|
+ loading: boolean,
+ isFile: ?boolean,
+ view: string
+|};
+
+export default class PageActions extends React.PureComponent {
+ props: Props;
+
+ renderShortcuts() {
+ return (
+ <span className="note big-spacer-right">
+ <span className="big-spacer-right">
+ <span className="shortcut-button little-spacer-right">↑</span>
+ <span className="shortcut-button little-spacer-right">↓</span>
+ {translate('component_measures.to_select_files')}
+ </span>
+
+ <span>
+ <span className="shortcut-button little-spacer-right">←</span>
+ <span className="shortcut-button little-spacer-right">→</span>
+ {translate('component_measures.to_navigate')}
+ </span>
+ </span>
+ );
+ }
+
+ renderFileShortcuts() {
+ return (
+ <span className="note big-spacer-right">
+ <span>
+ <span className="shortcut-button little-spacer-right">←</span>
+ {translate('component_measures.to_navigate_back')}
+ </span>
+ </span>
+ );
+ }
+
+ render() {
+ const { isFile, view } = this.props;
+ const showShortcuts = ['list', 'tree'].includes(view);
+ return (
+ <div className="pull-right">
+ {!isFile && showShortcuts && this.renderShortcuts()}
+ {isFile && this.renderFileShortcuts()}
+ <div className="measure-details-page-spinner">
+ <DeferredSpinner className="pull-right" loading={this.props.loading} />
+ </div>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.js
new file mode 100644
index 00000000000..fd5da228036
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.js
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 React from 'react';
+import { shallow } from 'enzyme';
+import Breadcrumb from '../Breadcrumb';
+
+it('should show the last element without clickable link', () => {
+ expect(
+ shallow(
+ <Breadcrumb
+ canBrowse={false}
+ component={{
+ key: 'foo',
+ name: 'Foo',
+ qualifier: 'TRK'
+ }}
+ isLast={true}
+ handleSelect={() => {}}
+ />
+ )
+ ).toMatchSnapshot();
+});
+
+it('should correctly show a middle element', () => {
+ expect(
+ shallow(
+ <Breadcrumb
+ canBrowse={true}
+ component={{
+ key: 'foo',
+ name: 'Foo',
+ qualifier: 'TRK'
+ }}
+ isLast={false}
+ handleSelect={() => {}}
+ />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.js
new file mode 100644
index 00000000000..22018da2057
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.js
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 React from 'react';
+import { mount } from 'enzyme';
+import Breadcrumbs from '../Breadcrumbs';
+import { doAsync } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/components', () => ({
+ getBreadcrumbs: () =>
+ Promise.resolve([
+ { key: 'anc1', name: 'Ancestor1' },
+ { key: 'anc2', name: 'Ancestor2' },
+ { key: 'bar', name: 'Bar' }
+ ])
+}));
+
+it('should display correctly for the list view', () => {
+ const wrapper = mount(
+ <Breadcrumbs
+ component={{ key: 'bar', name: 'Bar' }}
+ handleSelect={() => {}}
+ rootComponent={{ key: 'foo', name: 'Foo' }}
+ view="list"
+ />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should display only the root component', () => {
+ const wrapper = mount(
+ <Breadcrumbs
+ component={{ key: 'foo', name: 'Foo' }}
+ handleSelect={() => {}}
+ rootComponent={{ key: 'foo', name: 'Foo' }}
+ view="tree"
+ />
+ );
+ expect(wrapper.state()).toMatchSnapshot();
+});
+
+it.only('should load the breadcrumb from the api', () => {
+ const wrapper = mount(
+ <Breadcrumbs
+ component={{ key: 'bar', name: 'Bar' }}
+ handleSelect={() => {}}
+ rootComponent={{ key: 'foo', name: 'Foo' }}
+ view="tree"
+ />
+ );
+ return doAsync(() => {
+ expect(wrapper.state()).toMatchSnapshot();
+ });
+});
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 8914cf0a86e..34ea5cd64d1 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
@@ -53,7 +53,7 @@ const SECONDARY = {
};
const PROPS = {
- component: { key: 'foo' },
+ component: { key: 'foo', qualifier: 'TRK' },
leakPeriod: {
date: '2017-05-16T13:50:02+0200',
index: 1,
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.js
new file mode 100644
index 00000000000..607b30330a2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.js
@@ -0,0 +1,30 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 React from 'react';
+import { shallow } from 'enzyme';
+import MeasureViewSelect from '../MeasureViewSelect';
+
+it('should display correctly with treemap option', () => {
+ expect(
+ shallow(
+ <MeasureViewSelect metric={{ type: 'PERCENT' }} handleViewChange={() => {}} view="tree" />
+ )
+ ).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js
new file mode 100644
index 00000000000..d3770a5fa0e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/PageActions-test.js
@@ -0,0 +1,34 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 React from 'react';
+import { shallow } from 'enzyme';
+import PageActions from '../PageActions';
+
+it('should display correctly for a project', () => {
+ expect(shallow(<PageActions loading={true} isFile={false} view="list" />)).toMatchSnapshot();
+});
+
+it('should display correctly for a file', () => {
+ expect(shallow(<PageActions loading={false} isFile={true} view="tree" />)).toMatchSnapshot();
+});
+
+it('should not display shortcuts for treemap', () => {
+ expect(shallow(<PageActions loading={true} isFile={false} view="treemap" />)).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.js.snap
index 1eeed263913..99335765bdc 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/App-test.js.snap
@@ -82,6 +82,7 @@ exports[`should render correctly 1`] = `
}
selected=""
updateQuery={[Function]}
+ view="list"
/>
</div>
`;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.js.snap
new file mode 100644
index 00000000000..e116a3a254c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.js.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly show a middle element 1`] = `
+<span>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ Foo
+ </a>
+ <span
+ className="slash-separator"
+ />
+</span>
+`;
+
+exports[`should show the last element without clickable link 1`] = `
+<span>
+ <span>
+ Foo
+ </span>
+</span>
+`;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.js.snap
new file mode 100644
index 00000000000..63bab85ba77
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.js.snap
@@ -0,0 +1,93 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display correctly for the list view 1`] = `
+<Breadcrumbs
+ component={
+ Object {
+ "key": "bar",
+ "name": "Bar",
+ }
+ }
+ handleSelect={[Function]}
+ rootComponent={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ view="list"
+>
+ <div>
+ <Breadcrumb
+ canBrowse={true}
+ component={
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ }
+ }
+ handleSelect={[Function]}
+ isLast={false}
+ >
+ <span>
+ <a
+ href="#"
+ onClick={[Function]}
+ >
+ Foo
+ </a>
+ <span
+ className="slash-separator"
+ />
+ </span>
+ </Breadcrumb>
+ <Breadcrumb
+ canBrowse={false}
+ component={
+ Object {
+ "key": "bar",
+ "name": "Bar",
+ }
+ }
+ handleSelect={[Function]}
+ isLast={true}
+ >
+ <span>
+ <span>
+ Bar
+ </span>
+ </span>
+ </Breadcrumb>
+ </div>
+</Breadcrumbs>
+`;
+
+exports[`should display only the root component 1`] = `
+Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "foo",
+ "name": "Foo",
+ },
+ ],
+}
+`;
+
+exports[`should load the breadcrumb from the api 1`] = `
+Object {
+ "breadcrumbs": Array [
+ Object {
+ "key": "anc1",
+ "name": "Ancestor1",
+ },
+ Object {
+ "key": "anc2",
+ "name": "Ancestor2",
+ },
+ Object {
+ "key": "bar",
+ "name": "Bar",
+ },
+ ],
+}
+`;
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 3e66e8fa4fc..cd412e31862 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
@@ -71,6 +71,7 @@ exports[`should render correctly 1`] = `
component={
Object {
"key": "foo",
+ "qualifier": "TRK",
}
}
period={
@@ -134,6 +135,7 @@ exports[`should render correctly for leak 1`] = `
component={
Object {
"key": "foo",
+ "qualifier": "TRK",
}
}
period={
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.js.snap
new file mode 100644
index 00000000000..7d51b2dd127
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.js.snap
@@ -0,0 +1,81 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display correctly with treemap option 1`] = `
+<Select
+ addLabelText="Add \\"{label}\\"?"
+ arrowRenderer={[Function]}
+ autosize={true}
+ backspaceRemoves={true}
+ backspaceToRemoveMessage="Press backspace to remove {label}"
+ clearAllText="Clear all"
+ clearRenderer={[Function]}
+ clearValueText="Clear value"
+ clearable={false}
+ deleteRemoves={true}
+ delimiter=","
+ disabled={false}
+ escapeClearsValue={true}
+ filterOptions={[Function]}
+ ignoreAccents={true}
+ ignoreCase={true}
+ inputProps={Object {}}
+ isLoading={false}
+ joinValues={false}
+ labelKey="label"
+ matchPos="any"
+ matchProp="any"
+ menuBuffer={0}
+ menuRenderer={[Function]}
+ multi={false}
+ noResultsText="No results found"
+ onBlurResetsInput={true}
+ onChange={[Function]}
+ onCloseResetsInput={true}
+ optionComponent={[Function]}
+ options={
+ Array [
+ Object {
+ "icon": <ListIcon />,
+ "label": <div>
+ <ListIcon
+ className="little-spacer-right"
+ />
+ component_measures.tab.list
+ </div>,
+ "value": "list",
+ },
+ Object {
+ "icon": <TreeIcon />,
+ "label": <div>
+ <TreeIcon
+ className="little-spacer-right"
+ />
+ component_measures.tab.tree
+ </div>,
+ "value": "tree",
+ },
+ Object {
+ "icon": <TreemapIcon />,
+ "label": <div>
+ <TreemapIcon
+ className="little-spacer-right"
+ />
+ component_measures.tab.treemap
+ </div>,
+ "value": "treemap",
+ },
+ ]
+ }
+ pageSize={5}
+ placeholder="Select..."
+ required={false}
+ scrollMenuIntoView={true}
+ searchable={false}
+ simpleValue={false}
+ tabSelectsValue={true}
+ value="tree"
+ valueComponent={[Function]}
+ valueKey="value"
+ valueRenderer={[Function]}
+/>
+`;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.js.snap
new file mode 100644
index 00000000000..26a060b6fb2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/PageActions-test.js.snap
@@ -0,0 +1,93 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should display correctly for a file 1`] = `
+<div
+ className="pull-right"
+>
+ <span
+ className="note big-spacer-right"
+ >
+ <span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ ←
+ </span>
+ component_measures.to_navigate_back
+ </span>
+ </span>
+ <div
+ className="measure-details-page-spinner"
+ >
+ <DeferredSpinner
+ className="pull-right"
+ loading={false}
+ timeout={100}
+ />
+ </div>
+</div>
+`;
+
+exports[`should display correctly for a project 1`] = `
+<div
+ className="pull-right"
+>
+ <span
+ className="note big-spacer-right"
+ >
+ <span
+ className="big-spacer-right"
+ >
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ ↑
+ </span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ ↓
+ </span>
+ component_measures.to_select_files
+ </span>
+ <span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ ←
+ </span>
+ <span
+ className="shortcut-button little-spacer-right"
+ >
+ →
+ </span>
+ component_measures.to_navigate
+ </span>
+ </span>
+ <div
+ className="measure-details-page-spinner"
+ >
+ <DeferredSpinner
+ className="pull-right"
+ loading={true}
+ timeout={100}
+ />
+ </div>
+</div>
+`;
+
+exports[`should not display shortcuts for treemap 1`] = `
+<div
+ className="pull-right"
+>
+ <div
+ className="measure-details-page-spinner"
+ >
+ <DeferredSpinner
+ className="pull-right"
+ loading={true}
+ timeout={100}
+ />
+ </div>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ComponentCell.js b/server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ComponentCell.js
index a3b54e1955e..8d723304ce8 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ComponentCell.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ComponentCell.js
@@ -74,15 +74,9 @@ export default class ComponentCell extends React.PureComponent {
});
return (
- <td style={{ maxWidth: 0 }}>
- <div
- style={{
- maxWidth: '100%',
- whiteSpace: 'nowrap',
- overflow: 'hidden',
- textOverflow: 'ellipsis'
- }}>
- {component.refId == null || component.qualifier === 'DEV_PRJ'
+ <td className="measure-details-component-cell">
+ <div className="text-ellipsis">
+ {component.refId == null
? <a
id={'component-measures-component-link-' + component.key}
className={linkClassName}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ListView.js b/server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ListView.js
index d567f772759..b9f94f2c715 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ListView.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/components/drilldown/ListView.js
@@ -19,25 +19,20 @@
*/
// @flow
import React from 'react';
-import moment from 'moment';
import ComponentsList from './ComponentsList';
import ListFooter from '../../../../components/controls/ListFooter';
-import SourceViewer from '../../../../components/SourceViewer/SourceViewer';
import { getComponentTree } from '../../../../api/components';
import { complementary } from '../../config/complementary';
-import { isDiffMetric } from '../../../../helpers/measures';
import { enhanceComponent } from '../../utils';
-import type { Component, ComponentEnhanced, Paging, Period } from '../../types';
+import { isDiffMetric } from '../../../../helpers/measures';
+import type { Component, ComponentEnhanced, Paging } from '../../types';
import type { Metric } from '../../../../store/metrics/actions';
type Props = {
component: Component,
handleSelect: Component => void,
- leakPeriod?: Period,
- loading: boolean,
metric: Metric,
metrics: { [string]: Metric },
- selectedComponent: ?string,
updateLoading: ({ [string]: boolean }) => void
};
@@ -94,11 +89,7 @@ export default class ListView extends React.PureComponent {
return { metricKeys, opts: { ...opts, ...options } };
};
- fetchComponents = ({ component, metric, selectedComponent }: Props) => {
- if (selectedComponent) {
- this.setState({ metric });
- return;
- }
+ fetchComponents = ({ component, metric }: Props) => {
const { metricKeys, opts } = this.getComponentRequestParams(metric);
this.props.updateLoading({ components: true });
getComponentTree('leaves', component.key, metricKeys, opts).then(
@@ -150,29 +141,6 @@ export default class ListView extends React.PureComponent {
return null;
}
- const { leakPeriod, selectedComponent } = this.props;
- if (selectedComponent) {
- const leakPeriodDate =
- isDiffMetric(metric.key) && leakPeriod != null ? moment(leakPeriod.date).toDate() : null;
-
- let filterLine;
- if (leakPeriodDate != null) {
- filterLine = line => {
- if (line.scmDate) {
- const scmDate = moment(line.scmDate).toDate();
- return scmDate >= leakPeriodDate;
- } else {
- return false;
- }
- };
- }
- return (
- <div className="measure-details-viewer">
- <SourceViewer component={selectedComponent} filterLine={filterLine} />
- </div>
- );
- }
-
return (
<div>
<ComponentsList
diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js
index c7e8dd68b21..5df2b938c07 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js
@@ -63,7 +63,7 @@ export default class DomainFacet extends React.PureComponent {
disabled={false}
key={measure.metric.key}
name={
- <Tooltip overlay={getLocalizedMetricName(measure.metric)} mouseEnterDelay={1}>
+ <Tooltip overlay={getLocalizedMetricName(measure.metric)} mouseEnterDelay={0.5}>
<span id={`measure-${measure.metric.key}-name`}>
<IssueTypeIcon query={measure.metric.key} className="little-spacer-right" />
{getLocalizedMetricName(measure.metric)}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js
index 58c0326c9dc..74ef48f3c63 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/Sidebar.js
@@ -49,7 +49,7 @@ export default class Sidebar extends React.PureComponent {
}));
};
- changeMetric = (metric: string) => this.props.updateQuery({ metric });
+ changeMetric = (metric: string) => this.props.updateQuery({ metric, selected: null });
render() {
return (
diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap
index a55b82c48d7..f883847f9ac 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap
+++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap
@@ -15,7 +15,7 @@ exports[`should display facet item list 1`] = `
halfWidth={false}
name={
<Tooltip
- mouseEnterDelay={1}
+ mouseEnterDelay={0.5}
overlay="Bugs"
placement="bottom"
>
@@ -61,7 +61,7 @@ exports[`should display facet item list 1`] = `
halfWidth={false}
name={
<Tooltip
- mouseEnterDelay={1}
+ mouseEnterDelay={0.5}
overlay="New Bugs"
placement="bottom"
>
@@ -119,7 +119,7 @@ exports[`should display facet item list with bugs selected 1`] = `
halfWidth={false}
name={
<Tooltip
- mouseEnterDelay={1}
+ mouseEnterDelay={0.5}
overlay="Bugs"
placement="bottom"
>
@@ -165,7 +165,7 @@ exports[`should display facet item list with bugs selected 1`] = `
halfWidth={false}
name={
<Tooltip
- mouseEnterDelay={1}
+ mouseEnterDelay={0.5}
overlay="New Bugs"
placement="bottom"
>
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 1e3aa63a269..238b36f5be3 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
@@ -16,6 +16,13 @@
white-space: nowrap;
}
+.measure-details-page-spinner {
+ display: inline-block;
+ min-width: 20px;
+ text-align: right;
+ vertical-align: text-bottom;
+}
+
.measure-details-header {
display: flex;
flex-wrap: nowrap;
@@ -38,6 +45,14 @@
margin-top: -10px;
}
+.measure-details-component-cell {
+ max-width: 0;
+}
+
+.measure-details-component-cell > div {
+ max-width: 100%;
+}
+
.domain-measures-value .rating,
.measure-details-value .rating {
width: 18px;
@@ -47,3 +62,22 @@
margin-bottom: -2px;
font-size: 12px;
}
+
+.measure-view-select {
+ width: 50px;
+}
+
+.measure-view-select .Select-menu-outer {
+ width: 100px;
+ border-top-right-radius: 4px;
+}
+
+.measure-breadcrumbs {
+ display: inline-block;
+ max-width: 60%;
+ vertical-align: middle;
+}
+
+.measure-favorite svg {
+ vertical-align: middle;
+}
diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.js b/server/sonar-web/src/main/js/apps/component-measures/utils.js
index f6fcfdabc3d..7e3a1d07cb5 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/utils.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/utils.js
@@ -99,6 +99,9 @@ export const groupByDomains = memoize((measures: Array<MeasureEnhanced>): Array<
]);
});
+export const hasTreemap = (metricType: string): boolean =>
+ ['PERCENT', 'RATING', 'LEVEL'].includes(metricType);
+
export const hasBubbleChart = (domainName: string): boolean => bubbles[domainName] != null;
export const parseQuery = memoize((urlQuery: RawQuery): Query => ({
diff --git a/server/sonar-web/src/main/js/apps/issues/components/PageActions.js b/server/sonar-web/src/main/js/apps/issues/components/PageActions.js
index d8591e0e6a3..467ba68edfc 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/PageActions.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/PageActions.js
@@ -61,7 +61,7 @@ export default class PageActions extends React.PureComponent {
<div className="issues-page-actions">
{this.props.loading
- ? <i className="issues-main-header-spinner spinner" />
+ ? <i className="issues-main-header-spinner spinner spacer-right" />
: <ReloadButton className="spacer-right" onClick={this.props.onReload} />}
{paging != null && <IssuesCounter current={selectedIndex} total={paging.total} />}
</div>