diff --git a/web/src/App.jsx b/web/src/App.jsx index 6a5c6e010e..9a0caf5512 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -32,6 +32,8 @@ import { ButtonVariant, Dropdown, DropdownItem, + DropdownToggle, + DropdownSeparator, KebabToggle, Modal, Nav, @@ -54,6 +56,7 @@ import { import { BellIcon, BookIcon, + ChevronDownIcon, CodeIcon, ServiceIcon, UsersIcon, @@ -67,6 +70,7 @@ import ConfigModal from './containers/config/Config' import logo from './images/logo.svg' import { clearNotification } from './actions/notifications' import { fetchConfigErrorsAction, clearConfigErrorsAction } from './actions/configErrors' +import { fetchTenantsIfNeeded } from './actions/tenants' import { routes } from './routes' import { setTenantAction } from './actions/tenant' import { configureAuthFromTenant, configureAuthFromInfo } from './actions/auth' @@ -81,6 +85,7 @@ class App extends React.Component { configErrorsReady: PropTypes.bool, info: PropTypes.object, tenant: PropTypes.object, + tenants: PropTypes.object, timezone: PropTypes.string, location: PropTypes.object, history: PropTypes.object, @@ -93,6 +98,7 @@ class App extends React.Component { state = { showErrors: false, + isTenantDropdownOpen: false, } renderMenu() { @@ -199,6 +205,7 @@ class App extends React.Component { } else if (!info.tenant) { // Multi tenant, look for tenant name in url whiteLabel = false + this.props.dispatch(fetchTenantsIfNeeded()) const match = matchPath( this.props.location.pathname, { path: '/t/:tenant' }) @@ -368,6 +375,91 @@ class App extends React.Component { ) } + renderTenantDropdown() { + const { tenant, tenants } = this.props + const { isTenantDropdownOpen } = this.state + + if (tenant.whiteLabel) { + return ( + + Tenant {tenant.name} + + ) + } else { + const tenantLink = (_tenant) => { + const currentPath = this.props.location.pathname + let suffix + switch (currentPath) { + case '/t/' + tenant.name + '/projects': + suffix = '/projects' + break + case '/t/' + tenant.name + '/jobs': + suffix = '/jobs' + break + case '/t/' + tenant.name + '/labels': + suffix = '/labels' + break + case '/t/' + tenant.name + '/nodes': + suffix = '/nodes' + break + case '/t/' + tenant.name + '/autoholds': + suffix = '/autoholds' + break + case '/t/' + tenant.name + '/builds': + suffix = '/builds' + break + case '/t/' + tenant.name + '/buildsets': + suffix = '/buildsets' + break + case '/t/' + tenant.name + '/status': + default: + // all other paths point to tenant-specific resources that would most likely result in a 404 + suffix = '/status' + break + } + return {_tenant.name} + } + + const options = tenants.tenants.filter( + (_tenant) => (_tenant.name !== tenant.name) + ).map( + (_tenant, idx) => { + return ( + + ) + }) + options.push( + , + Go to tenants page} /> + ) + + return (tenants.isFetching ? + + Loading tenants ... + : + <> + + { this.setState({ isTenantDropdownOpen: isOpen }) }} + toggleIndicator={ChevronDownIcon} + > + Tenant {tenant.name} + } + onSelect={() => { this.setState({ isTenantDropdownOpen: !isTenantDropdownOpen }) }} + dropdownItems={options} + /> + + ) + } + } + render() { const { isKebabDropdownOpen } = this.state const { notifications, configErrors, tenant, info, auth } = this.props @@ -406,7 +498,7 @@ class App extends React.Component { key="tenant" onClick={event => this.handleTenantLink(event)} > - Tenant + Tenants ) } @@ -445,15 +537,7 @@ class App extends React.Component { - {tenant.name && ( - - - - - - )} + {tenant.name && (this.renderTenantDropdown())} {/* this kebab dropdown replaces the icon buttons and is hidden for @@ -521,6 +605,7 @@ export default withRouter(connect( configErrorsReady: state.configErrors.ready, info: state.info, tenant: state.tenant, + tenants: state.tenants, timezone: state.timezone, user: state.user, auth: state.auth, diff --git a/web/src/App.test.jsx b/web/src/App.test.jsx index a1d0234d93..519980c7ac 100644 --- a/web/src/App.test.jsx +++ b/web/src/App.test.jsx @@ -135,8 +135,9 @@ it('renders single tenant', async () => { // Link should be white-label scoped const topMenuLinks = application.root.findAllByType(Link) expect(topMenuLinks[0].props.to).toEqual('/status') - expect(topMenuLinks[3].props.to.pathname).toEqual('/status') - expect(topMenuLinks[4].props.to.pathname).toEqual('/projects') + expect(topMenuLinks[1].props.to).toEqual('/openapi') + expect(topMenuLinks[2].props.to.pathname).toEqual('/status') + expect(topMenuLinks[3].props.to.pathname).toEqual('/projects') // Location should be /status expect(location.pathname).toEqual('/status') // Info should tell white label tenant openstack diff --git a/web/src/containers/timezone/SelectTz.jsx b/web/src/containers/timezone/SelectTz.jsx index aaa585336f..576645f6c7 100644 --- a/web/src/containers/timezone/SelectTz.jsx +++ b/web/src/containers/timezone/SelectTz.jsx @@ -12,9 +12,9 @@ import PropTypes from 'prop-types' import React from 'react' -import Select from 'react-select' +import Select, { components } from 'react-select' import moment from 'moment-timezone' -import { OutlinedClockIcon } from '@patternfly/react-icons' +import { OutlinedClockIcon, ChevronDownIcon } from '@patternfly/react-icons' import { connect } from 'react-redux' import { setTimezoneAction } from '../../actions/timezone' @@ -58,7 +58,7 @@ class SelectTz extends React.Component { } render() { - const textColor = '#d1d1d1' + const textColor = '#fff' const containerStyles= { border: 'solid #2b2b2b', borderWidth: '0 0 0 1px', @@ -83,7 +83,11 @@ class SelectTz extends React.Component { }), dropdownIndicator:(provided) => ({ ...provided, - padding: '3px' + color: '#fff', + padding: '3px', + ':hover': { + color: '#fff' + } }), indicatorSeparator: () => {}, menu: (provided) => ({ @@ -93,12 +97,22 @@ class SelectTz extends React.Component { top: '22px', }) } + + const DropdownIndicator = (props) => { + return ( + + + + ) + } + return (