Skip to content

Commit

Permalink
[Spaces] Create Default Space on startup (#19177)
Browse files Browse the repository at this point in the history
When Kibana starts, a Default Space will be created if one does not already exist. The Default Space will contain all objects that are not currently assigned to any other space (i.e., objects created prior to Spaces being enabled, or objects created directly within the Default Space).

To enable this, this PR also introduces the concept of Reserved Spaces, which is denoted by a `_reserved` property on the `space` object. Reserved Spaces:
1. Cannot be deleted
2. Cannot have their URL Context Changed

This PR does not address:
1. Disabling the UI for reserved roles - this will be enabled via #19035 once this PR is merged into it.
  • Loading branch information
legrego authored May 21, 2018
1 parent 50c73b4 commit 7db020e
Show file tree
Hide file tree
Showing 12 changed files with 666 additions and 6 deletions.
7 changes: 7 additions & 0 deletions x-pack/plugins/spaces/common/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* 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 const DEFAULT_SPACE_ID = `default`;
17 changes: 17 additions & 0 deletions x-pack/plugins/spaces/common/is_reserved_space.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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 { get } from 'lodash';

/**
* Returns whether the given Space is reserved or not.
*
* @param space the space
* @returns boolean
*/
export function isReservedSpace(space) {
return get(space, '_reserved', false);
}
25 changes: 25 additions & 0 deletions x-pack/plugins/spaces/common/is_reserved_space.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 { isReservedSpace } from './is_reserved_space';

test('it returns true for reserved spaces', () => {
const space = {
_reserved: true
};

expect(isReservedSpace(space)).toEqual(true);
});

test('it returns false for non-reserved spaces', () => {
const space = {};

expect(isReservedSpace(space)).toEqual(false);
});

test('it handles empty imput', () => {
expect(isReservedSpace()).toEqual(false);
});
3 changes: 3 additions & 0 deletions x-pack/plugins/spaces/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { validateConfig } from './server/lib/validate_config';
import { checkLicense } from './server/lib/check_license';
import { initSpacesApi } from './server/routes/api/v1/spaces';
import { initSpacesRequestInterceptors } from './server/lib/space_request_interceptors';
import { createDefaultSpace } from './server/lib/create_default_space';
import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status';
import { getActiveSpace } from './server/lib/get_active_space';
import { wrapError } from './server/lib/errors';
Expand Down Expand Up @@ -76,5 +77,7 @@ export const spaces = (kibana) => new kibana.Plugin({
initSpacesApi(server);

initSpacesRequestInterceptors(server);

await createDefaultSpace(server);
}
});
11 changes: 10 additions & 1 deletion x-pack/plugins/spaces/mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
"space": {
"properties": {
"name": {
"type": "text"
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
},
"urlContext": {
"type": "keyword"
},
"description": {
"type": "text"
},
"_reserved": {
"type": "boolean"
}
}
}
Expand Down
43 changes: 43 additions & 0 deletions x-pack/plugins/spaces/server/lib/create_default_space.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 { getClient } from '../../../../server/lib/get_client_shield';
import { DEFAULT_SPACE_ID } from '../../common/constants';

export async function createDefaultSpace(server) {
const { callWithInternalUser: callCluster } = getClient(server);

const savedObjectsClient = server.savedObjectsClientFactory({ callCluster });

const defaultSpaceExists = await doesDefaultSpaceExist(savedObjectsClient);

if (defaultSpaceExists) {
return;
}

const options = {
id: DEFAULT_SPACE_ID
};

await savedObjectsClient.create('space', {
name: 'Default Space',
description: 'This is your Default Space!',
urlContext: '',
_reserved: true
}, options);
}

async function doesDefaultSpaceExist(savedObjectsClient) {
try {
await savedObjectsClient.get('space', DEFAULT_SPACE_ID);
return true;
} catch (e) {
if (savedObjectsClient.errors.isNotFoundError(e)) {
return false;
}
throw e;
}
}
98 changes: 98 additions & 0 deletions x-pack/plugins/spaces/server/lib/create_default_space.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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 Boom from 'boom';
import { getClient } from '../../../../server/lib/get_client_shield';
import { createDefaultSpace } from './create_default_space';

jest.mock('../../../../server/lib/get_client_shield', () => ({
getClient: jest.fn()
}));

let mockCallWithRequest;
beforeEach(() => {
mockCallWithRequest = jest.fn();
getClient.mockReturnValue({
callWithRequest: mockCallWithRequest
});
});

const createMockServer = (settings = {}) => {

const {
defaultExists = false
} = settings;

const mockServer = {
config: jest.fn().mockReturnValue({
get: jest.fn()
}),
savedObjectsClientFactory: jest.fn().mockReturnValue({
get: jest.fn().mockImplementation(() => {
if (defaultExists) {
return;
}
throw Boom.notFound('unit test: default space not found');
}),
create: jest.fn().mockReturnValue(),
errors: {
isNotFoundError: (e) => e.message === 'unit test: default space not found'
}
})
};

mockServer.config().get.mockImplementation(key => {
return settings[key];
});

return mockServer;
};

test(`it creates the default space when one does not exist`, async () => {
const server = createMockServer({
defaultExists: false
});

await createDefaultSpace(server);

const client = server.savedObjectsClientFactory();

expect(client.get).toHaveBeenCalledTimes(1);
expect(client.create).toHaveBeenCalledTimes(1);
expect(client.create).toHaveBeenCalledWith(
'space',
{ "_reserved": true, "description": "This is your Default Space!", "name": "Default Space", "urlContext": "" },
{ "id": "default" }
);
});

test(`it does not attempt to recreate the default space if it already exists`, async () => {
const server = createMockServer({
defaultExists: true
});

await createDefaultSpace(server);

const client = server.savedObjectsClientFactory();

expect(client.get).toHaveBeenCalledTimes(1);
expect(client.create).toHaveBeenCalledTimes(0);
});

test(`it throws all other errors from the saved objects client`, async () => {
const server = createMockServer({
defaultExists: true,
});

const client = server.savedObjectsClientFactory();
client.get = () => { throw new Error('unit test: unexpected exception condition'); };

try {
await createDefaultSpace(server);
throw new Error(`Expected error to be thrown!`);
} catch (e) {
expect(e.message).toEqual('unit test: unexpected exception condition');
}
});
24 changes: 23 additions & 1 deletion x-pack/plugins/spaces/server/lib/space_request_interceptors.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@ export function initSpacesRequestInterceptors(server) {
type: 'space'
});

if (total === 1) {
// If only one space is available, then send user there directly.
// No need for an interstitial screen where there is only one possible outcome.
const space = spaceObjects[0];
const {
urlContext
} = space.attributes;

const config = server.config();
const basePath = config.get('server.basePath');
const defaultRoute = config.get('server.defaultRoute');

let destination;
if (urlContext) {
destination = `${basePath}/s/${urlContext}${defaultRoute}`;
} else {
destination = `${basePath}${defaultRoute}`;
}

return reply.redirect(destination);
}

if (total > 0) {
// render spaces selector instead of home page
const app = server.getHiddenUiAppById('space_selector');
Expand All @@ -61,7 +83,7 @@ export function initSpacesRequestInterceptors(server) {
});
}

} catch(e) {
} catch (e) {
return reply(wrapError(e));
}
}
Expand Down
Loading

0 comments on commit 7db020e

Please sign in to comment.