Skip to content

Commit

Permalink
[Spaces] Space Avatar with selector in main Kibana menu (#18609)
Browse files Browse the repository at this point in the history
Adds Space Avatar with selector in main Kibana menu
  • Loading branch information
legrego committed May 21, 2018
1 parent 980c202 commit 50c73b4
Show file tree
Hide file tree
Showing 28 changed files with 806 additions and 875 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"url": "https://github.com/elastic/kibana.git"
},
"dependencies": {
"@elastic/eui": "v0.0.44",
"@elastic/eui": "v0.0.47",
"@elastic/filesaver": "1.1.2",
"@elastic/numeral": "2.3.2",
"@elastic/ui-ace": "0.2.3",
Expand Down
2 changes: 1 addition & 1 deletion x-pack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"yargs": "4.7.1"
},
"dependencies": {
"@elastic/eui": "0.0.44",
"@elastic/eui": "0.0.47",
"@elastic/node-crypto": "0.1.2",
"@elastic/node-phantom-simple": "2.2.4",
"@elastic/numeral": "2.3.2",
Expand Down
22 changes: 0 additions & 22 deletions x-pack/plugins/spaces/common/mock_spaces.js

This file was deleted.

44 changes: 44 additions & 0 deletions x-pack/plugins/spaces/common/spaces_url_parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.
*/

export function getSpaceUrlContext(basePath = '/', defaultContext = null) {
// Look for `/s/space-url-context` in the base path
const matchResult = basePath.match(/\/s\/([a-z0-9\-]+)/);

if (!matchResult || matchResult.length === 0) {
return defaultContext;
}

// Ignoring first result, we only want the capture group result at index 1
const [, urlContext = defaultContext] = matchResult;

return urlContext;
}

export function stripSpaceUrlContext(basePath = '/') {
const currentSpaceUrlContext = getSpaceUrlContext(basePath);

let basePathWithoutSpace;
if (currentSpaceUrlContext) {
const indexOfSpaceContext = basePath.indexOf(`/s/${currentSpaceUrlContext}`);

const startsWithSpace = indexOfSpaceContext === 0;

if (startsWithSpace) {
basePathWithoutSpace = '/';
} else {
basePathWithoutSpace = basePath.substring(0, indexOfSpaceContext);
}
} else {
basePathWithoutSpace = basePath;
}

if (basePathWithoutSpace.endsWith('/')) {
return basePathWithoutSpace.substr(0, -1);
}

return basePathWithoutSpace;
}
39 changes: 39 additions & 0 deletions x-pack/plugins/spaces/common/spaces_url_parser.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 { stripSpaceUrlContext, getSpaceUrlContext } from './spaces_url_parser';

test('it removes the space url context from the base path when the space is not at the root', () => {
const basePath = `/foo/s/my-space`;
expect(stripSpaceUrlContext(basePath)).toEqual('/foo');
});

test('it removes the space url context from the base path when the space is the root', () => {
const basePath = `/s/my-space`;
expect(stripSpaceUrlContext(basePath)).toEqual('');
});

test(`it doesn't change base paths without a space url context`, () => {
const basePath = `/this/is/a-base-path/ok`;
expect(stripSpaceUrlContext(basePath)).toEqual(basePath);
});

test('it accepts no parameters', () => {
expect(stripSpaceUrlContext()).toEqual('');
});

test('it remove the trailing slash', () => {
expect(stripSpaceUrlContext('/')).toEqual('');
});

test('it identifies the space url context', () => {
const basePath = `/this/is/a/crazy/path/s/my-awesome-space-lives-here`;
expect(getSpaceUrlContext(basePath)).toEqual('my-awesome-space-lives-here');
});

test('it handles base url without a space url context', () => {
const basePath = `/this/is/a/crazy/path/s`;
expect(getSpaceUrlContext(basePath)).toEqual(null);
});
20 changes: 19 additions & 1 deletion x-pack/plugins/spaces/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { checkLicense } from './server/lib/check_license';
import { initSpacesApi } from './server/routes/api/v1/spaces';
import { initSpacesRequestInterceptors } from './server/lib/space_request_interceptors';
import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
import { getActiveSpace } from './server/lib/get_active_space';
import { wrapError } from './server/lib/errors';
import mappings from './mappings.json';

export const spaces = (kibana) => new kibana.Plugin({
Expand All @@ -31,15 +33,31 @@ export const spaces = (kibana) => new kibana.Plugin({
id: 'space_selector',
title: 'Spaces',
main: 'plugins/spaces/views/space_selector',
url: 'space_selector',
hidden: true,
}],
hacks: [],
mappings,
home: ['plugins/spaces/register_feature'],
injectDefaultVars: function () {
return {
spaces: []
spaces: [],
activeSpace: null
};
},
replaceInjectedVars: async function (vars, request) {
try {
vars.activeSpace = {
valid: true,
space: await getActiveSpace(request.getSavedObjectsClient(), request.getBasePath())
};
} catch(e) {
vars.activeSpace = {
valid: false,
error: wrapError(e).output.payload
};
}
return vars;
}
},

Expand Down
38 changes: 38 additions & 0 deletions x-pack/plugins/spaces/public/lib/spaces_manager.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.
*/

export class SpacesManager {
constructor(httpAgent, chrome) {
this._httpAgent = httpAgent;
this._baseUrl = chrome.addBasePath(`/api/spaces/v1`);
}

async getSpaces() {
return await this._httpAgent
.get(`${this._baseUrl}/spaces`)
.then(response => response.data);
}

async getSpace(id) {
return await this._httpAgent
.get(`${this._baseUrl}/space/${id}`);
}

async createSpace(space) {
return await this._httpAgent
.post(`${this._baseUrl}/space`, space);
}

async updateSpace(space) {
return await this._httpAgent
.put(`${this._baseUrl}/space/${space.id}?overwrite=true`, space);
}

async deleteSpace(space) {
return await this._httpAgent
.delete(`${this._baseUrl}/space/${space.id}`);
}
}
40 changes: 40 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,40 @@
/*
* 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,
EuiText
} from '@elastic/eui';
import './space_card.less';

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

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

function renderSpaceTitle(space) {
return (
<div className="spaceCardTitle">
<EuiText><h3>{space.name}</h3></EuiText>
</div>
);
}

function renderSpaceDescription(space) {
return space.description;
}
9 changes: 9 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,9 @@
.spaceCard, .euiCard.euiCard--isClickable.spaceCard {
width: 310px;
height: 230px;
min-height: 200px;
}

.spaceCardDescription {
margin-bottom: 10px;
}
32 changes: 32 additions & 0 deletions x-pack/plugins/spaces/public/views/components/space_card.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 { shallow, mount } from 'enzyme';
import { SpaceCard } from './space_card';

test('it renders without crashing', () => {
const space = {
name: 'space name',
description: 'space description'
};

shallow(<SpaceCard space={space} />);
});

test('it is clickable', () => {
const space = {
name: 'space name',
description: 'space description'
};

const clickHandler = jest.fn();

const wrapper = mount(<SpaceCard space={space} onClick={clickHandler} />);
wrapper.simulate('click');

expect(clickHandler).toHaveBeenCalledTimes(1);
});
58 changes: 58 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,58 @@
/*
* 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, Fragment } from 'react';
import PropTypes from 'prop-types';
import chrome from 'ui/chrome';
import { SpaceCard } from './space_card';
import { stripSpaceUrlContext } from '../../../common/spaces_url_parser';
import {
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
} from '@elastic/eui';
import { chunk } from 'lodash';

export class SpaceCards extends Component {
render() {
const maxSpacesPerRow = 3;
const rows = chunk(this.props.spaces, maxSpacesPerRow);

return (
<Fragment>
{
rows.map((row, idx) => (
<Fragment key={idx}>
<EuiFlexGroup gutterSize="l" justifyContent="spaceEvenly">
{row.map(this.renderSpace)}
</EuiFlexGroup>
<EuiSpacer />
</Fragment>
))
}
</Fragment>

);
}

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

createSpaceClickHandler = (space) => {
return () => {
const baseUrlWithoutSpace = stripSpaceUrlContext(chrome.getBasePath());

window.location = `${baseUrlWithoutSpace}/s/${space.urlContext}`;
};
};
}

SpaceCards.propTypes = {
spaces: PropTypes.array.isRequired,
};
46 changes: 46 additions & 0 deletions x-pack/plugins/spaces/public/views/components/space_cards.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 { shallow, mount } from 'enzyme';
import { SpaceCards } from './space_cards';
import { EuiFlexGroup } from '@elastic/eui';
import { SpaceCard } from './space_card';

test('it renders without crashing', () => {
const space = {
id: 'space-id',
name: 'space name',
description: 'space description'
};

shallow(<SpaceCards spaces={[space]} />);
});

test('it renders spaces in groups of 3', () => {
function buildSpace(name) {
return {
id: `id-${name}`,
name,
description: `desc-${name}`
};
}

const spaces = [
buildSpace(1),
buildSpace(2),
buildSpace(3),
buildSpace(4)
];

const wrapper = mount(<SpaceCards spaces={spaces} />);

const groups = wrapper.find(EuiFlexGroup);
expect(groups).toHaveLength(2);

expect(groups.at(0).find(SpaceCard)).toHaveLength(3);
expect(groups.at(1).find(SpaceCard)).toHaveLength(1);
});
Loading

0 comments on commit 50c73b4

Please sign in to comment.