aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2015-11-26 15:58:04 +0100
committerStas Vilchik <vilchiks@gmail.com>2015-11-26 16:02:44 +0100
commit7c2cc24167ef1463f58668941f1424fcfb1185f4 (patch)
treeecfd0105c1df5886ba61d61cb0c82eef43225f46
parent30966aeb6cda4fc24b0f6f0701a9507f3c623397 (diff)
downloadsonarqube-7c2cc24167ef1463f58668941f1424fcfb1185f4.tar.gz
sonarqube-7c2cc24167ef1463f58668941f1424fcfb1185f4.zip
SONAR-7059 Improve navigation between dashboards inside the project space
-rw-r--r--server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda3335870
-rw-r--r--server/sonar-web/src/main/js/components/dashboards/dashboard-sidebar.js124
-rw-r--r--server/sonar-web/src/main/js/helpers/l10n.js5
-rw-r--r--server/sonar-web/src/main/js/helpers/urls.js38
-rw-r--r--server/sonar-web/src/main/js/main/nav/app.js21
-rw-r--r--server/sonar-web/src/main/js/main/nav/component/component-nav-menu.js64
-rw-r--r--server/sonar-web/src/main/js/main/nav/links-mixin.js2
-rw-r--r--server/sonar-web/src/main/less/components.less1
-rw-r--r--server/sonar-web/src/main/less/components/navbar.less6
-rw-r--r--server/sonar-web/src/main/less/components/page.less2
-rw-r--r--server/sonar-web/src/main/less/components/pills.less38
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb3
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb1
13 files changed, 248 insertions, 57 deletions
diff --git a/server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587 b/server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587
+++ /dev/null
diff --git a/server/sonar-web/src/main/js/components/dashboards/dashboard-sidebar.js b/server/sonar-web/src/main/js/components/dashboards/dashboard-sidebar.js
new file mode 100644
index 00000000000..f68aa7b9c89
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/dashboards/dashboard-sidebar.js
@@ -0,0 +1,124 @@
+import qs from 'querystring';
+import _ from 'underscore';
+import classNames from 'classnames';
+import React from 'react';
+
+import { getLocalizedDashboardName } from '../../helpers/l10n';
+import { getComponentDashboardUrl, getComponentFixedDashboardUrl, getComponentDashboardManagementUrl } from '../../helpers/urls';
+
+
+const FIXED_DASHBOARDS = [
+ { link: '', name: 'overview.page' },
+ { link: '/issues', name: 'overview.domain.debt' },
+ { link: '/tests', name: 'overview.domain.coverage' },
+ { link: '/duplications', name: 'overview.domain.duplications' },
+ { link: '/size', name: 'overview.domain.size' }
+];
+
+const CUSTOM_DASHBOARDS_LIMIT = 1;
+
+
+export const DashboardSidebar = React.createClass({
+ propTypes: {
+ component: React.PropTypes.object.isRequired,
+ customDashboards: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+ },
+
+ periodParameter() {
+ let params = qs.parse(window.location.search.substr(1));
+ return params.period ? `&period=${params.period}` : '';
+ },
+
+ getPeriod() {
+ let params = qs.parse(window.location.search.substr(1));
+ return params.period;
+ },
+
+ isFixedDashboardActive(fixedDashboard) {
+ let path = window.location.pathname;
+ return path === `${window.baseUrl}/overview${fixedDashboard.link}`;
+ },
+
+ isCustomDashboardActive(customDashboard) {
+ let path = window.location.pathname,
+ params = qs.parse(window.location.search.substr(1));
+ return path.indexOf(`${window.baseUrl}/dashboard`) === 0 && params['did'] === `${customDashboard.key}`;
+ },
+
+ isMoreCustomDashboardsActive () {
+ let dashboards = _.rest(this.props.customDashboards, CUSTOM_DASHBOARDS_LIMIT);
+ return _.any(dashboards, this.isCustomDashboardActive);
+ },
+
+ isDashboardManagementActive () {
+ let path = window.location.pathname;
+ return path.indexOf(`${window.baseUrl}/dashboards`) === 0;
+ },
+
+ renderFixedDashboards() {
+ return FIXED_DASHBOARDS.map(fixedDashboard => {
+ let key = 'fixed-dashboard-' + fixedDashboard.link.substr(1);
+ let url = getComponentFixedDashboardUrl(this.props.component.key, fixedDashboard.link);
+ let name = window.t(fixedDashboard.name);
+ let className = classNames({ active: this.isFixedDashboardActive(fixedDashboard) });
+ return <li key={key} className={className}>
+ <a href={url}>{name}</a>
+ </li>;
+ });
+ },
+
+ renderCustomDashboards() {
+ let dashboards = _.first(this.props.customDashboards, CUSTOM_DASHBOARDS_LIMIT);
+ return dashboards.map(this.renderCustomDashboard);
+ },
+
+ renderCustomDashboard(customDashboard) {
+ let key = 'custom-dashboard-' + customDashboard.key;
+ let url = getComponentDashboardUrl(this.props.component.key, customDashboard.key, this.getPeriod());
+ let name = getLocalizedDashboardName(customDashboard.name);
+ let className = classNames({ active: this.isCustomDashboardActive(customDashboard) });
+ return <li key={key} className={className}>
+ <a href={url}>{name}</a>
+ </li>;
+ },
+
+ renderMoreCustomDashboards() {
+ if (this.props.customDashboards.length <= CUSTOM_DASHBOARDS_LIMIT) {
+ return null;
+ }
+ let dashboards = _.rest(this.props.customDashboards, CUSTOM_DASHBOARDS_LIMIT)
+ .map(this.renderCustomDashboard);
+ let className = classNames('dropdown', { active: this.isMoreCustomDashboardsActive() });
+ return <li className={className}>
+ <a className="dropdown-toggle" data-toggle="dropdown" href="#">
+ More&nbsp;
+ <i className="icon-dropdown"/>
+ </a>
+ <ul className="dropdown-menu">{dashboards}</ul>
+ </li>;
+ },
+
+ renderDashboardsManagementLink() {
+ if (!window.SS.user) {
+ return null;
+ }
+ let key = 'dashboard-management';
+ let url = getComponentDashboardManagementUrl(this.props.component.key);
+ let name = window.t('dashboard.manage_dashboards');
+ let className = classNames('pill-right', { active: this.isDashboardManagementActive() });
+ return <li key={key} className={className}>
+ <a className="note" href={url}>{name}</a>
+ </li>;
+ },
+
+ render() {
+ return <nav className="navbar-side">
+ <ul className="pills">
+ {this.renderFixedDashboards()}
+ {this.renderCustomDashboards()}
+ {this.renderMoreCustomDashboards()}
+ {this.renderDashboardsManagementLink()}
+ </ul>
+ </nav>;
+ }
+});
diff --git a/server/sonar-web/src/main/js/helpers/l10n.js b/server/sonar-web/src/main/js/helpers/l10n.js
new file mode 100644
index 00000000000..de950a94b93
--- /dev/null
+++ b/server/sonar-web/src/main/js/helpers/l10n.js
@@ -0,0 +1,5 @@
+export function getLocalizedDashboardName (baseName) {
+ var l10nKey = 'dashboard.' + baseName + '.name';
+ var l10nLabel = window.t(l10nKey);
+ return l10nLabel !== l10nKey ? l10nLabel : baseName;
+}
diff --git a/server/sonar-web/src/main/js/helpers/urls.js b/server/sonar-web/src/main/js/helpers/urls.js
index 8280af1c6ea..2dc10eacb31 100644
--- a/server/sonar-web/src/main/js/helpers/urls.js
+++ b/server/sonar-web/src/main/js/helpers/urls.js
@@ -41,3 +41,41 @@ export function getComponentDrilldownUrl (componentKey, metric, period, highligh
}
return url;
}
+
+
+/**
+ * Generate URL for a component's dashboard
+ * @param {string} componentKey
+ * @param {string} dashboardKey
+ * @param {string} [period]
+ * @returns {string}
+ */
+export function getComponentDashboardUrl (componentKey, dashboardKey, period) {
+ let url = window.baseUrl + '/dashboard?id=' + encodeURIComponent(componentKey) +
+ '&did=' + encodeURIComponent(dashboardKey);
+ if (period) {
+ url += '&period=' + period;
+ }
+ return url;
+}
+
+
+/**
+ * Generate URL for a fixed component's dashboard (overview)
+ * @param {string} componentKey
+ * @param {string} dashboardKey
+ * @returns {string}
+ */
+export function getComponentFixedDashboardUrl (componentKey, dashboardKey) {
+ return window.baseUrl + '/overview' + dashboardKey + '?id=' + encodeURIComponent(componentKey);
+}
+
+
+/**
+ * Generate URL for a component's dashboards management page
+ * @param {string} componentKey
+ * @returns {string}
+ */
+export function getComponentDashboardManagementUrl (componentKey) {
+ return window.baseUrl + '/dashboards?resource=' + encodeURIComponent(componentKey);
+}
diff --git a/server/sonar-web/src/main/js/main/nav/app.js b/server/sonar-web/src/main/js/main/nav/app.js
index af45c03ff02..f4f9644be13 100644
--- a/server/sonar-web/src/main/js/main/nav/app.js
+++ b/server/sonar-web/src/main/js/main/nav/app.js
@@ -1,9 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
+
import GlobalNav from './global/global-nav';
import ComponentNav from './component/component-nav';
import SettingsNav from './settings/settings-nav';
-import {getGlobalNavigation, getComponentNavigation, getSettingsNavigation} from '../../api/nav';
+import { getGlobalNavigation, getComponentNavigation, getSettingsNavigation } from '../../api/nav';
+import { DashboardSidebar } from '../../components/dashboards/dashboard-sidebar';
import '../../components/workspace/main';
import '../../helpers/handlebars-helpers';
@@ -44,15 +46,26 @@ export default class App {
}
static renderComponentNav (options) {
- return getComponentNavigation(options.componentKey).then(r => {
+ return getComponentNavigation(options.componentKey).then(component => {
const el = document.getElementById('context-navigation');
if (el) {
- ReactDOM.render(<ComponentNav component={r} conf={r.configuration || {}}/>, el);
+ ReactDOM.render(<ComponentNav component={component} conf={component.configuration || {}}/>, el);
}
- return r;
+ this.renderSidebarNav(component);
+ return component;
});
}
+ static renderSidebarNav (component) {
+ let shouldRender =
+ window.location.pathname.indexOf(window.baseUrl + '/overview') === 0 ||
+ window.location.pathname.indexOf(window.baseUrl + '/dashboard') === 0;
+ let el = document.getElementById('sidebar');
+ if (shouldRender && el) {
+ ReactDOM.render(<DashboardSidebar component={component} customDashboards={component.dashboards}/>, el);
+ }
+ }
+
static renderSettingsNav (options) {
return getSettingsNavigation().then(r => {
let el = document.getElementById('context-navigation');
diff --git a/server/sonar-web/src/main/js/main/nav/component/component-nav-menu.js b/server/sonar-web/src/main/js/main/nav/component/component-nav-menu.js
index 28818b16c54..a73e466e35a 100644
--- a/server/sonar-web/src/main/js/main/nav/component/component-nav-menu.js
+++ b/server/sonar-web/src/main/js/main/nav/component/component-nav-menu.js
@@ -17,9 +17,14 @@ export default React.createClass({
return params.period ? `&period=${params.period}` : '';
},
- renderOverviewLink() {
+ renderDashboardLink() {
let url = `/overview?id=${encodeURIComponent(this.props.component.key)}`;
- return this.renderLink(url, window.t('overview.page'), '/overview');
+ return this.renderLink(url, window.t('layout.dashboards'), () => {
+ let cond =
+ window.location.pathname.indexOf(window.baseUrl + '/overview') === 0 ||
+ window.location.pathname.indexOf(window.baseUrl + '/dashboard') === 0;
+ return cond ? 'active' : null;
+ });
},
renderComponentsLink() {
@@ -55,7 +60,9 @@ export default React.createClass({
return (
<li className={className}>
<a className="dropdown-toggle navbar-admin-link" data-toggle="dropdown" href="#">
- {window.t('layout.settings')}&nbsp;<i className="icon-dropdown"/></a>
+ {window.t('layout.settings')}&nbsp;
+ <i className="icon-dropdown"/>
+ </a>
<ul className="dropdown-menu">
{this.renderSettingsLink()}
{this.renderProfilesLink()}
@@ -169,69 +176,30 @@ export default React.createClass({
});
},
- renderMore() {
- return (
- <li className="dropdown">
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- {window.t('more')}&nbsp;<i className="icon-dropdown"></i>
- </a>
- <ul className="dropdown-menu">
- {this.renderDashboards()}
- {this.renderDashboardManagementLink()}
- {this.renderTools()}
- </ul>
- </li>
- );
- },
-
- renderDashboards() {
- let dashboards = (this.props.component.dashboards || []).map(d => {
- let url = `/dashboard?id=${encodeURIComponent(this.props.component.key)}&did=${d.key}${this.periodParameter()}`;
- let name = this.getLocalizedDashboardName(d.name);
- return this.renderLink(url, name);
- });
- return [<li key="0" className="dropdown-header">{window.t('layout.dashboards')}</li>].concat(dashboards);
- },
-
- renderDashboardManagementLink() {
- if (!window.SS.user) {
- return null;
- }
- let url = `/dashboards?resource=${encodeURIComponent(this.props.component.key)}`;
- let name = window.t('dashboard.manage_dashboards');
- return [
- <li key="dashboard-divider" className="small-divider"></li>,
- this.renderLink(url, name, '/dashboards')
- ];
- },
-
renderTools() {
let component = this.props.component;
if (!component.isComparable && !_.size(component.extensions)) {
return null;
}
- let tools = [
- <li key="tools-divider" className="divider"></li>,
- <li key="tools" className="dropdown-header">Tools</li>
- ];
+ let tools = [];
+ (component.extensions || []).forEach(e => {
+ tools.push(this.renderLink(e.url, e.name));
+ });
if (component.isComparable) {
let compareUrl = `/comparison/index?resource=${component.key}`;
tools.push(this.renderLink(compareUrl, window.t('comparison.page')));
}
- (component.extensions || []).forEach(e => {
- tools.push(this.renderLink(e.url, e.name));
- });
return tools;
},
render() {
return (
<ul className="nav navbar-nav nav-tabs">
- {this.renderOverviewLink()}
+ {this.renderDashboardLink()}
{this.renderComponentsLink()}
{this.renderComponentIssuesLink()}
{this.renderAdministration()}
- {this.renderMore()}
+ {this.renderTools()}
</ul>
);
}
diff --git a/server/sonar-web/src/main/js/main/nav/links-mixin.js b/server/sonar-web/src/main/js/main/nav/links-mixin.js
index d2f07300261..73b2a69ba5f 100644
--- a/server/sonar-web/src/main/js/main/nav/links-mixin.js
+++ b/server/sonar-web/src/main/js/main/nav/links-mixin.js
@@ -11,7 +11,7 @@ export default {
let fullUrl = window.baseUrl + url;
let check = _.isFunction(highlightUrl) ? highlightUrl : this.activeLink;
return (
- <li key={highlightUrl} className={check(highlightUrl)}>
+ <li key={url} className={check(highlightUrl)}>
<a href={fullUrl}>{title}</a>
</li>
);
diff --git a/server/sonar-web/src/main/less/components.less b/server/sonar-web/src/main/less/components.less
index 34d748dc806..464b4260cdf 100644
--- a/server/sonar-web/src/main/less/components.less
+++ b/server/sonar-web/src/main/less/components.less
@@ -23,3 +23,4 @@
@import "components/columns";
@import "components/workspace";
@import "components/search";
+@import "components/pills";
diff --git a/server/sonar-web/src/main/less/components/navbar.less b/server/sonar-web/src/main/less/components/navbar.less
index 25f4552bb7e..1685c6635d9 100644
--- a/server/sonar-web/src/main/less/components/navbar.less
+++ b/server/sonar-web/src/main/less/components/navbar.less
@@ -219,3 +219,9 @@
color: @secondFontColor;
font-size: @smallFontSize;
}
+
+.navbar-side {
+ padding: 10px;
+ border-bottom: 1px solid @barBorderColor;
+ background-color: #e5f1f9;
+}
diff --git a/server/sonar-web/src/main/less/components/page.less b/server/sonar-web/src/main/less/components/page.less
index 77d560efb23..bb510b2ff85 100644
--- a/server/sonar-web/src/main/less/components/page.less
+++ b/server/sonar-web/src/main/less/components/page.less
@@ -12,7 +12,7 @@ body {
.page {
.clearfix;
position: relative;
- padding: 10px;
+ padding: 10px 20px;
}
.page-container {
diff --git a/server/sonar-web/src/main/less/components/pills.less b/server/sonar-web/src/main/less/components/pills.less
new file mode 100644
index 00000000000..88cc5a3c619
--- /dev/null
+++ b/server/sonar-web/src/main/less/components/pills.less
@@ -0,0 +1,38 @@
+@import (reference) '../variables';
+
+.pills {
+ display: flex;
+}
+
+.pills > li {
+
+}
+
+.pills > li + li {
+ margin-left: 2px;
+}
+
+.pills > li > a {
+ display: block;
+ height: @formControlHeight;
+ max-width: 135px;
+ line-height: @formControlHeight;
+ padding: 0 10px;
+ border: none;
+ border-radius: @formControlHeight;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ transition: none;
+}
+
+.pills > li.active > a,
+.pills > li > a:hover,
+.pills > li > a:focus {
+ background-color: @darkBlue;
+ color: #fff;
+}
+
+.pill-right {
+ margin-left: auto !important;
+}
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb
index b055bb15f90..3c2425053b5 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb
@@ -21,9 +21,6 @@
<%= link_to h(dashboard.name(true)), {:controller => :dashboard, :action => :index, :did => dashboard.id, :id => (resource_id unless dashboard.global?)},
:id => "view-#{u dashboard.name}" %>
<div class="description"><%= h dashboard.description -%></div>
- <% if index == 0 %>
- <div class="note spacer-top"><%= h message('dashboard.default_dashboard') -%></div>
- <% end %>
</td>
<td class="shared">
<% if (dashboard.shared) %><i class="icon-check" id='<%= "dashboard-#{index}-shared" -%>'></i><% end %>
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
index b793d31198e..2af9eb9a60c 100644
--- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
+++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb
@@ -17,6 +17,7 @@
<%= yield :header -%>
<div id="body" class="page-container">
+ <div id="sidebar"></div>
<div id="content">
<div class="panel hidden" id="messages-panel">
<div class="alert alert-danger hidden" id="error">