]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7059 Improve navigation between dashboards inside the project space
authorStas Vilchik <vilchiks@gmail.com>
Thu, 26 Nov 2015 14:58:04 +0000 (15:58 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Thu, 26 Nov 2015 15:02:44 +0000 (16:02 +0100)
13 files changed:
server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587 [deleted file]
server/sonar-web/src/main/js/components/dashboards/dashboard-sidebar.js [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/l10n.js [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/urls.js
server/sonar-web/src/main/js/main/nav/app.js
server/sonar-web/src/main/js/main/nav/component/component-nav-menu.js
server/sonar-web/src/main/js/main/nav/links-mixin.js
server/sonar-web/src/main/less/components.less
server/sonar-web/src/main/less/components/navbar.less
server/sonar-web/src/main/less/components/page.less
server/sonar-web/src/main/less/components/pills.less [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/app/views/dashboards/_my_dashboards.html.erb
server/sonar-web/src/main/webapp/WEB-INF/app/views/layouts/_layout.html.erb

diff --git a/server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587 b/server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587
deleted file mode 100644 (file)
index e69de29..0000000
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 (file)
index 0000000..f68aa7b
--- /dev/null
@@ -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 (file)
index 0000000..de950a9
--- /dev/null
@@ -0,0 +1,5 @@
+export function getLocalizedDashboardName (baseName) {
+  var l10nKey = 'dashboard.' + baseName + '.name';
+  var l10nLabel = window.t(l10nKey);
+  return l10nLabel !== l10nKey ? l10nLabel : baseName;
+}
index 8280af1c6ea28e5ed21184b8557d30d4e5f1b601..2dc10eacb311da611126db056b5e262e216e5b0f 100644 (file)
@@ -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);
+}
index af45c03ff02179383779edcfc1ae6f90f8792f41..f4f9644be1350cbba8dbfe28478ec7100e43bec6 100644 (file)
@@ -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');
index 28818b16c54a7b249f176d7f657ee2cdba907f4b..a73e466e35ace3fd0535f1adfd478368927e2ab3 100644 (file)
@@ -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>
     );
   }
index d2f07300261f02d47313e21e5a464630e4d3b78d..73b2a69ba5f62c6ec615b6181b5dee2f58eb6d81 100644 (file)
@@ -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>
     );
index 34d748dc806f979c728b37b0caddc6ae49cff4cd..464b4260cdf031502bbd9d36a5596a599e2fd350 100644 (file)
@@ -23,3 +23,4 @@
 @import "components/columns";
 @import "components/workspace";
 @import "components/search";
+@import "components/pills";
index 25f4552bb7ebec1f55187d68984c979f408175d3..1685c6635d9f8a51f38982f9c20e43d5c16b1735 100644 (file)
   color: @secondFontColor;
   font-size: @smallFontSize;
 }
+
+.navbar-side {
+  padding: 10px;
+  border-bottom: 1px solid @barBorderColor;
+  background-color: #e5f1f9;
+}
index 77d560efb237933cce35fbd61eb089bee37e9d54..bb510b2ff85940ea82dcfc45809a6461c4cd37eb 100644 (file)
@@ -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 (file)
index 0000000..88cc5a3
--- /dev/null
@@ -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;
+}
index b055bb15f90bb607bb4b6204c26bf6f32afa0d3c..3c2425053b5741fbd742497ed0b4379c1e862191 100644 (file)
@@ -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 %>
index b793d31198ebae15dde40949abfd9996b23cddcd..2af9eb9a60cb9ceb616d8f7b2168885a27faba3b 100644 (file)
@@ -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">