--- /dev/null
+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
+ <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>;
+ }
+});
--- /dev/null
+export function getLocalizedDashboardName (baseName) {
+ var l10nKey = 'dashboard.' + baseName + '.name';
+ var l10nLabel = window.t(l10nKey);
+ return l10nLabel !== l10nKey ? l10nLabel : baseName;
+}
}
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);
+}
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';
}
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');
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() {
return (
<li className={className}>
<a className="dropdown-toggle navbar-admin-link" data-toggle="dropdown" href="#">
- {window.t('layout.settings')} <i className="icon-dropdown"/></a>
+ {window.t('layout.settings')}
+ <i className="icon-dropdown"/>
+ </a>
<ul className="dropdown-menu">
{this.renderSettingsLink()}
{this.renderProfilesLink()}
});
},
- renderMore() {
- return (
- <li className="dropdown">
- <a className="dropdown-toggle" data-toggle="dropdown" href="#">
- {window.t('more')} <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>
);
}
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>
);
@import "components/columns";
@import "components/workspace";
@import "components/search";
+@import "components/pills";
color: @secondFontColor;
font-size: @smallFontSize;
}
+
+.navbar-side {
+ padding: 10px;
+ border-bottom: 1px solid @barBorderColor;
+ background-color: #e5f1f9;
+}
.page {
.clearfix;
position: relative;
- padding: 10px;
+ padding: 10px 20px;
}
.page-container {
--- /dev/null
+@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;
+}
<%= 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 %>
<%= 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">