Skip to content

Commit

Permalink
Adds Space Avatar with selector in main Kibana menu
Browse files Browse the repository at this point in the history
  • Loading branch information
legrego committed Apr 26, 2018
1 parent 0b58acf commit e906128
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import Boom from 'boom';
import { canRedirectRequest } from '../../can_redirect_request';
import { canRedirectRequest } from '../../../../../../server/lib/can_redirect_request';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import Boom from 'boom';
import { canRedirectRequest } from '../../can_redirect_request';
import { canRedirectRequest } from '../../../../../../server/lib/can_redirect_request';
import { AuthenticationResult } from '../authentication_result';
import { DeauthenticationResult } from '../deauthentication_result';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Boom from 'boom';
import Joi from 'joi';
import { wrapError } from '../../../lib/errors';
import { BasicCredentials } from '../../../../server/lib/authentication/providers/basic';
import { canRedirectRequest } from '../../../lib/can_redirect_request';
import { canRedirectRequest } from '../../../../../../server/lib/can_redirect_request';

export function initAuthenticateApi(server) {
server.route({
Expand Down
29 changes: 28 additions & 1 deletion x-pack/plugins/spaces/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { validateConfig } from './server/lib/validate_config';
import { checkLicense } from './server/lib/check_license';
import { initSpacesApi } from './server/routes/api/v1/spaces';
import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
import { mockSpaces } from './common/mock_spaces';
import { canRedirectRequest } from '../../server/lib/can_redirect_request';

export const spaces = (kibana) => new kibana.Plugin({
id: 'spaces',
Expand All @@ -29,12 +31,15 @@ export const spaces = (kibana) => new kibana.Plugin({
id: 'space_selector',
title: 'Spaces',
main: 'plugins/spaces/views/space_selector',
url: 'space_selector',
hidden: true,
}],
hacks: [],
home: ['plugins/spaces/register_feature'],
injectDefaultVars: function () {
return { };
return {
spaces: mockSpaces
};
}
},

Expand All @@ -50,6 +55,28 @@ export const spaces = (kibana) => new kibana.Plugin({
const config = server.config();
validateConfig(config, message => server.log(['spaces', 'warning'], message));

if (!config.get('xpack.spaces.enabled')) {
return;
}

initSpacesApi(server);

server.ext('onRequest', (request, reply) => {
const appId = `space_selector`;

const canRedirect = canRedirectRequest(request);

const requiresSpaceIdentifier = request.path.startsWith(`/app/`) && !request.path.startsWith(`/app/${appId}`);

// TODO(legrego) - rendering space selector app is prompting browser for credentials (basic auth dialog) after logging in...

if (requiresSpaceIdentifier && canRedirect) {
const selectorApp = server.getHiddenUiAppById(appId);
console.log('rendering space selector');
return reply.renderApp(selectorApp);
}

return reply.continue();
});
}
});
38 changes: 38 additions & 0 deletions x-pack/plugins/spaces/public/views/components/space_card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React from 'react';
import {
EuiCard,
EuiAvatar,
EuiText
} from '@elastic/eui';
import './space_card.less';

export const SpaceCard = (props) => {
const {
space,
onClick
} = props;

return (
<EuiCard
className="spaceCard"
title={renderSpaceTitle(space)}
description={space.description}
onClick={onClick}
/>
);
};

function renderSpaceTitle(space) {
return (
<div className="spaceCardTitle">
<EuiAvatar name={space.name} size="m" />
<EuiText><h3>{space.name}</h3></EuiText>
</div>
);
}
3 changes: 3 additions & 0 deletions x-pack/plugins/spaces/public/views/components/space_card.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.spaceCard {
width: 225px;
}
33 changes: 33 additions & 0 deletions x-pack/plugins/spaces/public/views/components/space_cards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { SpaceCard } from './space_card';
import {
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';

export class SpaceCards extends Component {
render() {
return (
<EuiFlexGroup gutterSize="l" justifyContent="spaceEvenly" className="spacesGroup">
{this.props.spaces.map(this.renderSpace)}
</EuiFlexGroup>
);
}

renderSpace = (space) => (
<EuiFlexItem key={space.id}>
<SpaceCard space={space} onClick={() => {}} />
</EuiFlexItem>
);
}

SpaceCards.propTypes = {
spaces: PropTypes.array.isRequired
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
<div ng-controller="spacesNavController">
<global-nav-link
kbn-route="route"
icon="'plugins/spaces/images/demo.png'"
tooltip-content="'Clicking this should do something'"
label="'Engineering Space'"
></global-nav-link>
<div id="spacesNavReactRoot" />
</div>
15 changes: 14 additions & 1 deletion x-pack/plugins/spaces/public/views/nav_control/nav_control.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { constant } from 'lodash';
import { chromeNavControlsRegistry } from 'ui/registry/chrome_nav_controls';
import { uiModules } from 'ui/modules';
import template from 'plugins/spaces/views/nav_control/nav_control.html';
import 'plugins/spaces/views/nav_control/nav_control.less';

import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { NavControlModal } from 'plugins/spaces/views/nav_control/nav_control_modal';

chromeNavControlsRegistry.register(constant({
name: 'spaces',
Expand All @@ -17,6 +22,14 @@ chromeNavControlsRegistry.register(constant({

const module = uiModules.get('spaces', ['kibana']);

module.controller('spacesNavController', () => {
module.controller('spacesNavController', ($scope, chrome, spaces) => {
const domNode = document.getElementById(`spacesNavReactRoot`);

render(<NavControlModal spaces={spaces} />, domNode);

// unmount react on controller destroy
$scope.$on('$destroy', () => {
unmountComponentAtNode(domNode);
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.global-nav-link__icon .euiAvatar {
margin-top: 0.5em;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Component } from 'react';
import {
EuiModal,
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalBody,
EuiOverlayMask,
EuiAvatar,
} from '@elastic/eui';
import { SpaceCards } from '../components/space_cards';

export class NavControlModal extends Component {
state = {
isOpen: false
}

render() {
const button = (
<div className="global-nav-link">
<a className="global-nav-link__anchor" onClick={this.togglePortal}>
<div className="global-nav-link__icon"><EuiAvatar size={'s'} name={'Engineering'} /></div>
<div className="global-nav-link__title">Engineering</div>
</a>
</div>
);

let modal;
if (this.state.isOpen) {
modal = (
<EuiOverlayMask>
<EuiModal onClose={this.closePortal}>
<EuiModalHeader><EuiModalHeaderTitle>Select a space</EuiModalHeaderTitle></EuiModalHeader>
<EuiModalBody>
<SpaceCards spaces={this.props.spaces} />
</EuiModalBody>
</EuiModal>
</EuiOverlayMask>
);
}

return (
<div>{button}{modal}</div>
);
}

togglePortal = () => {
this.setState({
isOpen: !this.state.isOpen
});
}

closePortal = () => {
this.setState({
isOpen: false
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import {
EuiPageBody,
EuiPageContent,
EuiPageHeaderSection,
EuiCard,
EuiIcon,
EuiText,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { SpaceCards } from '../components/space_cards';

export const SpaceSelector = (props) => (
<EuiPage>
Expand All @@ -32,9 +30,7 @@ export const SpaceSelector = (props) => (
<p className="welcomeMedium">Select a space to begin.</p>
</EuiText>

<EuiFlexGroup gutterSize="l" justifyContent="spaceEvenly" className="spacesGroup">
{props.spaces.map(renderSpace)}
</EuiFlexGroup>
<SpaceCards spaces={props.spaces} />

<EuiText className="spaceProfileText">
<p>You can change your workspace at anytime by accessing your profile within Kibana.</p>
Expand All @@ -43,25 +39,3 @@ export const SpaceSelector = (props) => (
</EuiPageBody>
</EuiPage>
);

function renderSpace(space) {
return (
<EuiFlexItem key={space.id} grow={false} className="spaceCard">
<EuiCard
icon={<EuiIcon size="xxl" type={space.logo} />}
title={renderSpaceTitle(space)}
description={space.description}
onClick={() => window.alert('Card clicked')}
/>
</EuiFlexItem>
);
}

function renderSpaceTitle(space) {
return (
<div className="spaceCardTitle">
<span>{space.name}</span>
{/* <EuiIcon type={'starEmpty'} size="s" /> */}
</div>
);
}
22 changes: 22 additions & 0 deletions x-pack/server/lib/__tests__/__fixtures__/request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import url from 'url';
export function requestFixture({
headers = { accept: 'something/html' },
path = '/wat',
search = '',
payload
} = {}) {
return {
raw: { req: { headers } },
headers,
url: { path, search },
query: search ? url.parse(search, { parseQueryString: true }).query : {},
payload,
state: { user: 'these are the contents of the user client cookie' }
};
}
File renamed without changes.

0 comments on commit e906128

Please sign in to comment.