Use single ES document type (#12794)

Signed-off-by: Tyler Smalley <tyler.smalley@elastic.co>
This commit is contained in:
Tyler Smalley 2017-07-19 09:27:16 -07:00 committed by GitHub
parent 52e5d45f69
commit 3c0c0ff441
99 changed files with 3393 additions and 3115 deletions

View file

@ -159,8 +159,7 @@ export default function (kibana) {
}
);
// Set up the health check service and start it.
const mappings = kibana.uiExports.mappings.getCombined();
const { start, waitUntilReady } = healthCheck(this, server, { mappings });
const { start, waitUntilReady } = healthCheck(this, server);
server.expose('waitUntilReady', waitUntilReady);
start();
}

View file

@ -23,6 +23,7 @@ describe('plugins/elasticsearch', function () {
config = function () { return { get: get }; };
_.set(server, 'config', config);
_.set(server, 'getKibanaIndexMappingsDsl', sinon.stub().returns(mappings));
callWithInternalUser = sinon.stub();
cluster = { callWithInternalUser: callWithInternalUser };
@ -37,14 +38,14 @@ describe('plugins/elasticsearch', function () {
});
it('should check cluster.health upon successful index creation', function () {
const fn = createKibanaIndex(server, mappings);
const fn = createKibanaIndex(server);
return fn.then(function () {
sinon.assert.calledOnce(callWithInternalUser.withArgs('cluster.health', sinon.match.any));
});
});
it('should be created with mappings for config.buildNum', function () {
const fn = createKibanaIndex(server, mappings);
const fn = createKibanaIndex(server);
return fn.then(function () {
const params = callWithInternalUser.args[0][1];
expect(params)
@ -63,7 +64,7 @@ describe('plugins/elasticsearch', function () {
});
it('should be created with 1 shard and default replica', function () {
const fn = createKibanaIndex(server, mappings);
const fn = createKibanaIndex(server);
return fn.then(function () {
const params = callWithInternalUser.args[0][1];
expect(params)
@ -78,7 +79,7 @@ describe('plugins/elasticsearch', function () {
});
it('should be created with index name set in the config', function () {
const fn = createKibanaIndex(server, mappings);
const fn = createKibanaIndex(server);
return fn.then(function () {
const params = callWithInternalUser.args[0][1];
expect(params)

View file

@ -1,247 +0,0 @@
import expect from 'expect.js';
import sinon from 'sinon';
import { cloneDeep } from 'lodash';
import Chance from 'chance';
import { ensureTypesExist } from '../ensure_types_exist';
const chance = new Chance();
function createRandomTypes(n = chance.integer({ min: 10, max: 20 })) {
return chance.n(
() => ({
name: chance.word(),
mapping: {
type: chance.pickone(['keyword', 'text', 'integer', 'boolean'])
}
}),
n
);
}
function typesToMapping(types) {
return types.reduce((acc, type) => ({
...acc,
[type.name]: type.mapping
}), {});
}
function createV5Index(name, types) {
return {
[name]: {
mappings: typesToMapping(types)
}
};
}
function createV6Index(name, types) {
return {
[name]: {
mappings: {
doc: {
properties: typesToMapping(types)
}
}
}
};
}
function createCallCluster(index) {
return sinon.spy(async (method, params) => {
switch (method) {
case 'indices.get':
expect(params).to.have.property('index', Object.keys(index)[0]);
return cloneDeep(index);
case 'indices.putMapping':
return { ok: true };
default:
throw new Error(`stub not expecting callCluster('${method}')`);
}
});
}
describe('es/healthCheck/ensureTypesExist()', () => {
describe('general', () => {
it('reads the _mappings feature of the indexName', async () => {
const indexName = chance.word();
const callCluster = createCallCluster(createV5Index(indexName, []));
await ensureTypesExist({
callCluster,
indexName,
types: [],
log: sinon.stub()
});
sinon.assert.calledOnce(callCluster);
sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({
feature: '_mappings'
}));
});
});
describe('v5 index', () => {
it('does nothing if mappings match elasticsearch', async () => {
const types = createRandomTypes();
const indexName = chance.word();
const callCluster = createCallCluster(createV5Index(indexName, types));
await ensureTypesExist({
indexName,
callCluster,
types,
log: sinon.stub()
});
sinon.assert.calledOnce(callCluster);
sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName }));
});
it('adds types that are not in index', async () => {
const indexTypes = createRandomTypes();
const missingTypes = indexTypes.splice(-5);
const indexName = chance.word();
const callCluster = createCallCluster(createV5Index(indexName, indexTypes));
await ensureTypesExist({
indexName,
callCluster,
types: [
...indexTypes,
...missingTypes,
],
log: sinon.stub()
});
sinon.assert.callCount(callCluster, 1 + missingTypes.length);
sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName }));
missingTypes.forEach(type => {
sinon.assert.calledWith(callCluster, 'indices.putMapping', sinon.match({
index: indexName,
type: type.name,
body: type.mapping
}));
});
});
it('ignores extra types in index', async () => {
const indexTypes = createRandomTypes();
const missingTypes = indexTypes.splice(-5);
const indexName = chance.word();
const callCluster = createCallCluster(createV5Index(indexName, indexTypes));
await ensureTypesExist({
indexName,
callCluster,
types: missingTypes,
log: sinon.stub()
});
sinon.assert.callCount(callCluster, 1 + missingTypes.length);
sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName }));
missingTypes.forEach(type => {
sinon.assert.calledWith(callCluster, 'indices.putMapping', sinon.match({
index: indexName,
type: type.name,
body: type.mapping
}));
});
});
});
describe('v6 index', () => {
it('does nothing if mappings match elasticsearch', async () => {
const types = createRandomTypes();
const indexName = chance.word();
const callCluster = createCallCluster(createV6Index(indexName, types));
await ensureTypesExist({
indexName,
callCluster,
types,
log: sinon.stub()
});
sinon.assert.calledOnce(callCluster);
sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName }));
});
it('adds types that are not in index', async () => {
const indexTypes = createRandomTypes();
const missingTypes = indexTypes.splice(-5);
const indexName = chance.word();
const callCluster = createCallCluster(createV6Index(indexName, indexTypes));
await ensureTypesExist({
indexName,
callCluster,
types: [
...indexTypes,
...missingTypes,
],
log: sinon.stub()
});
sinon.assert.callCount(callCluster, 1 + missingTypes.length);
sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName }));
missingTypes.forEach(type => {
sinon.assert.calledWith(callCluster, 'indices.putMapping', sinon.match({
index: indexName,
type: 'doc',
body: {
properties: {
[type.name]: type.mapping,
}
}
}));
});
});
it('ignores extra types in index', async () => {
const indexTypes = createRandomTypes();
const missingTypes = indexTypes.splice(-5);
const indexName = chance.word();
const callCluster = createCallCluster(createV6Index(indexName, indexTypes));
await ensureTypesExist({
indexName,
callCluster,
types: missingTypes,
log: sinon.stub()
});
sinon.assert.callCount(callCluster, 1 + missingTypes.length);
sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName }));
missingTypes.forEach(type => {
sinon.assert.calledWith(callCluster, 'indices.putMapping', sinon.match({
index: indexName,
type: 'doc',
body: {
properties: {
[type.name]: type.mapping,
}
}
}));
});
});
it('does not define the _default_ type', async () => {
const indexTypes = [];
const missingTypes = [
{
name: '_default_',
mapping: {}
}
];
const indexName = chance.word();
const callCluster = createCallCluster(createV6Index(indexName, indexTypes));
await ensureTypesExist({
indexName,
callCluster,
types: missingTypes,
log: sinon.stub()
});
sinon.assert.calledOnce(callCluster);
sinon.assert.calledWith(callCluster, 'indices.get', sinon.match({ index: indexName }));
});
});
});

View file

@ -9,7 +9,8 @@ import mappings from './fixtures/mappings';
import healthCheck from '../health_check';
import kibanaVersion from '../kibana_version';
import { esTestServerUrlParts } from '../../../../../test/es_test_server_url_parts';
import * as ensureTypesExistNS from '../ensure_types_exist';
import * as patchKibanaIndexNS from '../patch_kibana_index';
import * as migrateConfigNS from '../migrate_config';
const esPort = esTestServerUrlParts.port;
const esUrl = url.format(esTestServerUrlParts);
@ -21,13 +22,15 @@ describe('plugins/elasticsearch', () => {
let health;
let plugin;
let cluster;
const sandbox = sinon.sandbox.create();
beforeEach(() => {
const COMPATIBLE_VERSION_NUMBER = '5.0.0';
// Stub the Kibana version instead of drawing from package.json.
sinon.stub(kibanaVersion, 'get').returns(COMPATIBLE_VERSION_NUMBER);
sinon.stub(ensureTypesExistNS, 'ensureTypesExist');
sandbox.stub(kibanaVersion, 'get').returns(COMPATIBLE_VERSION_NUMBER);
sandbox.stub(patchKibanaIndexNS, 'patchKibanaIndex');
sandbox.stub(migrateConfigNS, 'migrateConfig');
// setup the plugin stub
plugin = {
@ -73,19 +76,15 @@ describe('plugins/elasticsearch', () => {
getCluster: sinon.stub().returns(cluster)
}
},
savedObjectsClientFactory: () => ({
find: sinon.stub().returns(Promise.resolve({ saved_objects: [] })),
create: sinon.stub().returns(Promise.resolve({ id: 'foo' })),
})
getKibanaIndexMappingsDsl() {
return mappings;
}
};
health = healthCheck(plugin, server, { mappings });
health = healthCheck(plugin, server);
});
afterEach(() => {
kibanaVersion.get.restore();
ensureTypesExistNS.ensureTypesExist.restore();
});
afterEach(() => sandbox.restore());
it('should set the cluster green if everything is ready', function () {
cluster.callWithInternalUser.withArgs('ping').returns(Promise.resolve());
@ -101,6 +100,7 @@ describe('plugins/elasticsearch', () => {
sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('ping'));
sinon.assert.calledTwice(cluster.callWithInternalUser.withArgs('nodes.info', sinon.match.any));
sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any));
sinon.assert.notCalled(plugin.status.red);
sinon.assert.calledOnce(plugin.status.green);
expect(plugin.status.green.args[0][0]).to.be('Kibana index ready');

View file

@ -0,0 +1,174 @@
import expect from 'expect.js';
import sinon from 'sinon';
import { times, cloneDeep, pick, partition } from 'lodash';
import Chance from 'chance';
import { patchKibanaIndex } from '../patch_kibana_index';
import { getRootProperties, getRootType } from '../../../../server/mappings';
const chance = new Chance();
function createRandomMappings(n = chance.integer({ min: 10, max: 20 })) {
return {
[chance.word()]: {
properties: times(n, () => chance.word())
.reduce((acc, prop) => ({
...acc,
[prop]: {
type: chance.pickone(['keyword', 'text', 'integer', 'boolean'])
}
}), {})
}
};
}
function splitMappings(mappings) {
const type = getRootType(mappings);
const allProps = getRootProperties(mappings);
const keyGroups = partition(Object.keys(allProps), (p, i) => i % 2);
return keyGroups.map(keys => ({
[type]: {
...mappings[type],
properties: pick(allProps, keys)
}
}));
}
function createIndex(name, mappings = {}) {
return {
[name]: {
mappings
}
};
}
function createCallCluster(index) {
return sinon.spy(async (method, params) => {
switch (method) {
case 'indices.get':
expect(params).to.have.property('index', Object.keys(index)[0]);
return cloneDeep(index);
case 'indices.putMapping':
return { ok: true };
default:
throw new Error(`stub not expecting callCluster('${method}')`);
}
});
}
describe('es/healthCheck/patchKibanaIndex()', () => {
describe('general', () => {
it('reads the _mappings feature of the indexName', async () => {
const indexName = chance.word();
const mappings = createRandomMappings();
const callCluster = createCallCluster(createIndex(indexName, mappings));
await patchKibanaIndex({
callCluster,
indexName,
kibanaIndexMappingsDsl: mappings,
log: sinon.stub()
});
sinon.assert.calledOnce(callCluster);
sinon.assert.calledWithExactly(callCluster, 'indices.get', sinon.match({
feature: '_mappings'
}));
});
});
describe('multi-type index', () => {
it('rejects', async () => {
try {
const mappings = createRandomMappings();
const indexName = chance.word();
const index = createIndex(indexName, {
...mappings,
...createRandomMappings(),
...createRandomMappings(),
...createRandomMappings(),
});
const callCluster = createCallCluster(index);
await patchKibanaIndex({
indexName,
callCluster,
kibanaIndexMappingsDsl: mappings,
log: sinon.stub()
});
throw new Error('expected patchKibanaIndex() to throw an error');
} catch (error) {
expect(error)
.to.have.property('message')
.contain('Your Kibana index is out of date');
}
});
});
describe('v6 index', () => {
it('does nothing if mappings match elasticsearch', async () => {
const mappings = createRandomMappings();
const indexName = chance.word();
const callCluster = createCallCluster(createIndex(indexName, mappings));
await patchKibanaIndex({
indexName,
callCluster,
kibanaIndexMappingsDsl: mappings,
log: sinon.stub()
});
sinon.assert.calledOnce(callCluster);
sinon.assert.calledWithExactly(callCluster, 'indices.get', sinon.match({ index: indexName }));
});
it('adds properties that are not in index', async () => {
const [indexMappings, missingMappings] = splitMappings(createRandomMappings());
const mappings = {
...indexMappings,
...missingMappings,
};
const indexName = chance.word();
const callCluster = createCallCluster(createIndex(indexName, indexMappings));
await patchKibanaIndex({
indexName,
callCluster,
kibanaIndexMappingsDsl: mappings,
log: sinon.stub()
});
sinon.assert.calledTwice(callCluster);
sinon.assert.calledWithExactly(callCluster, 'indices.get', sinon.match({ index: indexName }));
sinon.assert.calledWithExactly(callCluster, 'indices.putMapping', sinon.match({
index: indexName,
type: getRootType(mappings),
body: {
properties: getRootProperties(mappings)
}
}));
});
it('ignores extra properties in index', async () => {
const [indexMappings, mappings] = splitMappings(createRandomMappings());
const indexName = chance.word();
const callCluster = createCallCluster(createIndex(indexName, indexMappings));
await patchKibanaIndex({
indexName,
callCluster,
kibanaIndexMappingsDsl: mappings,
log: sinon.stub()
});
sinon.assert.calledTwice(callCluster);
sinon.assert.calledWithExactly(callCluster, 'indices.get', sinon.match({
index: indexName
}));
sinon.assert.calledWithExactly(callCluster, 'indices.putMapping', sinon.match({
index: indexName,
type: getRootType(mappings),
body: {
properties: getRootProperties(mappings)
}
}));
});
});
});

View file

@ -1,15 +1,13 @@
export default function (server, mappings) {
export default function (server) {
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
const index = server.config().get('kibana.index');
return callWithInternalUser('indices.create', {
index: index,
body: {
settings: {
number_of_shards: 1,
'index.mapper.dynamic': false,
'index.mapping.single_type': false
number_of_shards: 1
},
mappings
mappings: server.getKibanaIndexMappingsDsl()
}
})
.catch(() => {

View file

@ -1,68 +0,0 @@
/**
* Checks that a kibana index has all of the types specified. Any type
* that is not defined in the existing index will be added via the
* `indicies.putMapping` API.
*
* @param {Object} options
* @property {Function} options.log a method for writing log messages
* @property {string} options.indexName name of the index in elasticsearch
* @property {Function} options.callCluster a function for executing client requests
* @property {Array<Object>} options.types an array of objects with `name` and `mapping` properties
* describing the types that should be in the index
* @return {Promise<undefined>}
*/
export async function ensureTypesExist({ log, indexName, callCluster, types }) {
const index = await callCluster('indices.get', {
index: indexName,
feature: '_mappings'
});
// could be different if aliases were resolved by `indices.get`
const resolvedName = Object.keys(index)[0];
const mappings = index[resolvedName].mappings;
const literalTypes = Object.keys(mappings);
const v6Index = literalTypes.length === 1 && literalTypes[0] === 'doc';
// our types aren't really es types, at least not in v6
const typesDefined = Object.keys(
v6Index
? mappings.doc.properties
: mappings
);
for (const type of types) {
if (v6Index && type.name === '_default_') {
// v6 indices don't get _default_ types
continue;
}
const defined = typesDefined.includes(type.name);
if (defined) {
continue;
}
log(['info', 'elasticsearch'], {
tmpl: `Adding mappings to kibana index for SavedObject type "<%= typeName %>"`,
typeName: type.name,
typeMapping: type.mapping
});
if (v6Index) {
await callCluster('indices.putMapping', {
index: indexName,
type: 'doc',
body: {
properties: {
[type.name]: type.mapping
}
}
});
} else {
await callCluster('indices.putMapping', {
index: indexName,
type: type.name,
body: type.mapping
});
}
}
}

View file

@ -1,13 +1,13 @@
import _ from 'lodash';
import Promise from 'bluebird';
import elasticsearch from 'elasticsearch';
import migrateConfig from './migrate_config';
import { migrateConfig } from './migrate_config';
import createKibanaIndex from './create_kibana_index';
import kibanaVersion from './kibana_version';
import { ensureEsVersion } from './ensure_es_version';
import { ensureNotTribe } from './ensure_not_tribe';
import { ensureAllowExplicitIndex } from './ensure_allow_explicit_index';
import { ensureTypesExist } from './ensure_types_exist';
import { patchKibanaIndex } from './patch_kibana_index';
const NoConnections = elasticsearch.errors.NoConnections;
import util from 'util';
@ -17,7 +17,7 @@ const NO_INDEX = 'no_index';
const INITIALIZING = 'initializing';
const READY = 'ready';
export default function (plugin, server, { mappings }) {
export default function (plugin, server) {
const config = server.config();
const callAdminAsKibanaUser = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser;
const callDataAsKibanaUser = server.plugins.elasticsearch.getCluster('data').callWithInternalUser;
@ -71,7 +71,7 @@ export default function (plugin, server, { mappings }) {
.then(function (health) {
if (health === NO_INDEX) {
plugin.status.yellow('No existing Kibana index found');
return createKibanaIndex(server, mappings);
return createKibanaIndex(server);
}
if (health === INITIALIZING) {
@ -99,11 +99,11 @@ export default function (plugin, server, { mappings }) {
.then(() => ensureNotTribe(callAdminAsKibanaUser))
.then(() => ensureAllowExplicitIndex(callAdminAsKibanaUser, config))
.then(waitForShards)
.then(() => ensureTypesExist({
.then(() => patchKibanaIndex({
callCluster: callAdminAsKibanaUser,
log: (...args) => server.log(...args),
indexName: config.get('kibana.index'),
types: Object.keys(mappings).map(name => ({ name, mapping: mappings[name] }))
kibanaIndexMappingsDsl: server.getKibanaIndexMappingsDsl()
}))
.then(_.partial(migrateConfig, server))
.then(() => {

View file

@ -1,6 +1,6 @@
import upgrade from './upgrade_config';
export default async function (server) {
export async function migrateConfig(server) {
const savedObjectsClient = server.savedObjectsClientFactory({
callCluster: server.plugins.elasticsearch.getCluster('admin').callWithInternalUser
});

View file

@ -0,0 +1,101 @@
import {
getTypes,
getRootType,
getRootProperties
} from '../../../server/mappings';
/**
* Checks that the root type in the kibana index has all of the
* root properties specified by the kibanaIndexMappings.
*
* @param {Object} options
* @property {Function} options.log
* @property {string} options.indexName
* @property {Function} options.callCluster
* @property {EsMappingsDsl} options.kibanaIndexMappingsDsl
* @return {Promise<undefined>}
*/
export async function patchKibanaIndex(options) {
const {
log,
indexName,
callCluster,
kibanaIndexMappingsDsl
} = options;
const rootEsType = getRootType(kibanaIndexMappingsDsl);
const currentMappingsDsl = await getCurrentMappings(callCluster, indexName, rootEsType);
const missingProperties = await getMissingRootProperties(currentMappingsDsl, kibanaIndexMappingsDsl);
const missingPropertyNames = Object.keys(missingProperties);
if (!missingPropertyNames.length) {
// all expected properties are in current mapping
return;
}
// log about new properties
log(['info', 'elasticsearch'], {
tmpl: `Adding mappings to kibana index for SavedObject types "<%= names.join('", "') %>"`,
names: missingPropertyNames
});
// add the new properties to the index mapping
await callCluster('indices.putMapping', {
index: indexName,
type: rootEsType,
body: {
properties: missingProperties
}
});
}
/**
* Get the mappings dsl for the current Kibana index
* @param {Function} callCluster
* @param {string} indexName
* @param {string} rootEsType
* @return {EsMappingsDsl}
*/
async function getCurrentMappings(callCluster, indexName, rootEsType) {
const index = await callCluster('indices.get', {
index: indexName,
feature: '_mappings'
});
// could be different if aliases were resolved by `indices.get`
const resolvedName = Object.keys(index)[0];
const currentMappingsDsl = index[resolvedName].mappings;
const currentTypes = getTypes(currentMappingsDsl);
const isV5Index = currentTypes.length > 1 || currentTypes[0] !== rootEsType;
if (isV5Index) {
throw new Error(
'Your Kibana index is out of date, reset it or use the X-Pack upgrade assistant.'
);
}
return currentMappingsDsl;
}
/**
* Get the properties that are in the expectedMappingsDsl but not the
* currentMappingsDsl. Properties will be an object of properties normally
* found at `[index]mappings[typeName].properties` is es mapping responses
*
* @param {EsMappingsDsl} currentMappingsDsl
* @param {EsMappingsDsl} expectedMappingsDsl
* @return {PropertyMappings}
*/
async function getMissingRootProperties(currentMappingsDsl, expectedMappingsDsl) {
const expectedProps = getRootProperties(expectedMappingsDsl);
const existingProps = getRootProperties(currentMappingsDsl);
return Object.keys(expectedProps)
.reduce((acc, prop) => {
if (existingProps[prop]) {
return acc;
} else {
return { ...acc, [prop]: expectedProps[prop] };
}
}, {});
}

View file

@ -46,7 +46,7 @@ uiModules.get('apps/management')
$scope.kbnUrl = Private(KbnUrlProvider);
$scope.indexPattern = $route.current.locals.indexPattern;
docTitle.change($scope.indexPattern.id);
docTitle.change($scope.indexPattern.title);
const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => {
return pattern.id !== $scope.indexPattern.id;

View file

@ -9,6 +9,19 @@ uiRoutes
.when('/management/kibana/indices/:indexPatternId/create-field/', { mode: 'create' })
.defaults(/management\/kibana\/indices\/[^\/]+\/(field|create-field)(\/|$)/, {
template,
mapBreadcrumbs($route, breadcrumbs) {
const { indexPattern } = $route.current.locals;
return breadcrumbs.map(crumb => {
if (crumb.id !== indexPattern.id) {
return crumb;
}
return {
...crumb,
display: indexPattern.title
};
});
},
resolve: {
indexPattern: function ($route, courier) {
return courier.indexPatterns.get($route.current.params.indexPatternId)
@ -46,7 +59,7 @@ uiRoutes
throw new Error('unknown fieldSettings mode ' + this.mode);
}
docTitle.change([this.field.name || 'New Scripted Field', this.indexPattern.id]);
docTitle.change([this.field.name || 'New Scripted Field', this.indexPattern.title]);
this.goBack = function () {
kbnUrl.changeToRoute(this.indexPattern, 'edit');
};

View file

@ -112,17 +112,21 @@ export function decorateMochaUi(lifecycle, context) {
switch (property) {
case 'describe':
case 'describe.only':
case 'describe.skip':
case 'xdescribe':
case 'context':
case 'context.only':
case 'context.skip':
case 'xcontext':
return wrapSuiteFunction(property, value);
case 'it':
case 'it.only':
case 'it.skip':
case 'xit':
case 'specify':
case 'specify.only':
case 'specify.skip':
case 'xspecify':
return wrapTestFunction(property, value);

View file

@ -21,6 +21,7 @@ import pluginsInitializeMixin from './plugins/initialize';
import { indexPatternsMixin } from './index_patterns';
import { savedObjectsMixin } from './saved_objects';
import { statsMixin } from './stats';
import { kibanaIndexMappingsMixin } from './mappings';
import { serverExtensionsMixin } from './server_extensions';
const rootDir = fromRoot('.');
@ -62,6 +63,9 @@ export default class KbnServer {
// tell the config we are done loading plugins
configCompleteMixin,
// setup kbnServer.mappings and server.getKibanaIndexMappingsDsl()
kibanaIndexMappingsMixin,
// setup this.uiExports and this.bundles
uiMixin,
indexPatternsMixin,

View file

@ -0,0 +1,128 @@
import expect from 'expect.js';
import Chance from 'chance';
import { IndexMappings } from '../index_mappings';
import { getRootType } from '../lib';
const chance = new Chance();
describe('server/mapping/index_mapping', function () {
describe('constructor', () => {
it('initializes with a default mapping when no args', () => {
const mapping = new IndexMappings();
const dsl = mapping.getDsl();
expect(dsl).to.be.an('object');
expect(getRootType(dsl)).to.be.a('string');
expect(dsl[getRootType(dsl)]).to.be.an('object');
});
it('accepts a default mapping dsl as the only argument', () => {
const mapping = new IndexMappings({
foobar: {
dynamic: false,
properties: {}
}
});
expect(mapping.getDsl()).to.eql({
foobar: {
dynamic: false,
properties: {}
}
});
});
it('throws if root type is of type=anything-but-object', () => {
expect(() => {
new IndexMappings({
root: {
type: chance.pickone(['string', 'keyword', 'geo_point'])
}
});
}).to.throwException(/non-object/);
});
it('throws if root type has no type and no properties', () => {
expect(() => {
new IndexMappings({
root: {}
});
}).to.throwException(/non-object/);
});
it('initialized root type with properties object if not set', () => {
const mapping = new IndexMappings({
root: {
type: 'object'
}
});
expect(mapping.getDsl()).to.eql({
root: {
type: 'object',
properties: {}
}
});
});
});
describe('#getDsl()', () => {
// tests are light because this method is used all over these tests
it('returns mapping as es dsl', function () {
const mapping = new IndexMappings();
expect(mapping.getDsl()).to.be.an('object');
});
});
describe('#addRootProperties()', () => {
it('extends the properties of the root type', () => {
const mapping = new IndexMappings({
x: { properties: {} }
});
mapping.addRootProperties({
y: {
properties: {
z: {
type: 'text'
}
}
}
});
expect(mapping.getDsl()).to.eql({
x: {
properties: {
y: {
properties: {
z: {
type: 'text'
}
}
}
}
}
});
});
it('throws if any property is conflicting', () => {
const props = { foo: 'bar' };
const mapping = new IndexMappings({
root: { properties: props }
});
expect(() => {
mapping.addRootProperties(props);
}).to.throwException(/foo/);
});
it('includes the plugin option in the error message when specified', () => {
const props = { foo: 'bar' };
const mapping = new IndexMappings({ root: { properties: props } });
expect(() => {
mapping.addRootProperties(props, { plugin: 'abc123' });
}).to.throwException(/plugin abc123/);
});
});
});

View file

@ -0,0 +1,10 @@
export {
kibanaIndexMappingsMixin
} from './kibana_index_mappings_mixin';
export {
getTypes,
getRootType,
getProperty,
getRootProperties,
} from './lib';

View file

@ -0,0 +1,61 @@
import { cloneDeep, isPlainObject } from 'lodash';
import { formatListAsProse } from '../../utils';
import { getRootProperties, getRootType } from './lib';
const DEFAULT_INITIAL_DSL = {
rootType: {
type: 'object',
properties: {},
},
};
export class IndexMappings {
constructor(initialDsl = DEFAULT_INITIAL_DSL) {
this._dsl = cloneDeep(initialDsl);
if (!isPlainObject(this._dsl)) {
throw new TypeError('initial mapping must be an object');
}
// ensure that we have a properties object in the dsl
// and that the dsl can be parsed with getRootProperties() and kin
this._setProperties(getRootProperties(this._dsl) || {});
}
getDsl() {
return cloneDeep(this._dsl);
}
addRootProperties(newProperties, options = {}) {
const { plugin } = options;
const rootProperties = getRootProperties(this._dsl);
const conflicts = Object.keys(newProperties)
.filter(key => rootProperties.hasOwnProperty(key));
if (conflicts.length) {
const props = formatListAsProse(conflicts);
const owner = plugin ? `registered by plugin ${plugin} ` : '';
throw new Error(
`Mappings for ${props} ${owner}have already been defined`
);
}
this._setProperties({
...rootProperties,
...newProperties
});
}
_setProperties(newProperties) {
const rootType = getRootType(this._dsl);
this._dsl = {
...this._dsl,
[rootType]: {
...this._dsl[rootType],
properties: newProperties
}
};
}
}

View file

@ -0,0 +1,60 @@
import { IndexMappings } from './index_mappings';
/**
* The default mappings used for the kibana index. This is
* extended via uiExports type "mappings". See the kibana
* and timelion plugins for examples.
* @type {EsMappingDsl}
*/
const BASE_KIBANA_INDEX_MAPPINGS_DSL = {
doc: {
'dynamic': 'strict',
properties: {
type: {
type: 'keyword'
},
config: {
dynamic: true,
properties: {
buildNum: {
type: 'keyword'
}
}
},
}
}
};
export function kibanaIndexMappingsMixin(kbnServer, server) {
/**
* Stores the current mappings that we expect to find in the Kibana
* index. Using `kbnServer.mappings.addRootProperties()` the UiExports
* class extends these mappings based on `mappings` ui export specs.
*
* Application code should not access this object, and instead should
* use `server.getKibanaIndexMappingsDsl()` from below, mixed with the
* helpers exposed by this module, to interact with the mappings via
* their DSL.
*
* @type {IndexMappings}
*/
kbnServer.mappings = new IndexMappings(BASE_KIBANA_INDEX_MAPPINGS_DSL);
/**
* Get the mappings dsl that we expect to see in the
* Kibana index. Used by the elasticsearch plugin to create
* and update the kibana index. Also used by the SavedObjectsClient
* to determine the properties defined in the mapping as well as
* things like the "rootType".
*
* See `src/server/mappings/lib/index.js` for helpers useful for reading
* the EsMappingDsl object.
*
* @method server.getKibanaIndexMappingsDsl
* @returns {EsMappingDsl}
*/
server.decorate('server', 'getKibanaIndexMappingsDsl', () => {
return kbnServer.mappings.getDsl();
});
}

View file

@ -0,0 +1,68 @@
import expect from 'expect.js';
import { getProperty } from '../get_property';
const MAPPINGS = {
rootType: {
properties: {
foo: {
properties: {
name: {
type: 'text'
},
description: {
type: 'text'
}
}
},
bar: {
properties: {
baz: {
type: 'text',
fields: {
box: {
type: 'keyword'
}
}
}
}
}
}
}
};
function test(key, mapping) {
expect(typeof key === 'string' || Array.isArray(key)).to.be.ok();
expect(mapping).to.be.an('object');
expect(getProperty(MAPPINGS, key)).to.be(mapping);
}
describe('getProperty(mappings, path)', () => {
describe('string key', () => {
it('finds root properties', () => {
test('foo', MAPPINGS.rootType.properties.foo);
});
it('finds nested properties', () => {
test('foo.name', MAPPINGS.rootType.properties.foo.properties.name);
test('foo.description', MAPPINGS.rootType.properties.foo.properties.description);
test('bar.baz', MAPPINGS.rootType.properties.bar.properties.baz);
});
it('finds nested multi-fields', () => {
test('bar.baz.box', MAPPINGS.rootType.properties.bar.properties.baz.fields.box);
});
});
describe('string key', () => {
it('finds root properties', () => {
test(['foo'], MAPPINGS.rootType.properties.foo);
});
it('finds nested properties', () => {
test(['foo', 'name'], MAPPINGS.rootType.properties.foo.properties.name);
test(['foo', 'description'], MAPPINGS.rootType.properties.foo.properties.description);
test(['bar', 'baz'], MAPPINGS.rootType.properties.bar.properties.baz);
});
it('finds nested multi-fields', () => {
test(['bar', 'baz', 'box'], MAPPINGS.rootType.properties.bar.properties.baz.fields.box);
});
});
});

View file

@ -0,0 +1,40 @@
import toPath from 'lodash/internal/toPath';
import { getRootType } from './get_root_type';
/**
* Recursively read properties from the mapping object of type "object"
* until the `path` is resolved.
* @param {EsObjectMapping} mapping
* @param {Array<string>} path
* @return {Objects|undefined}
*/
function getPropertyMappingFromObjectMapping(mapping, path) {
const props = mapping && (mapping.properties || mapping.fields);
if (!props) {
return undefined;
}
if (path.length > 1) {
return getPropertyMappingFromObjectMapping(
props[path[0]],
path.slice(1)
);
} else {
return props[path[0]];
}
}
/**
* Get the mapping for a specific property within the root type of the EsMappingsDsl.
* @param {EsMappingsDsl} mappings
* @param {string|Array<string>} path
* @return {Object|undefined}
*/
export function getProperty(mappings, path) {
return getPropertyMappingFromObjectMapping(
mappings[getRootType(mappings)],
toPath(path)
);
}

View file

@ -0,0 +1,29 @@
import { getRootType } from './get_root_type';
/**
* Get the property mappings for the root type in the EsMappingsDsl
*
* If the mappings don't have a root type, or the root type is not
* an object type (it's a keyword or something) this function will
* throw an error.
*
* EsPropertyMappings objects have the root property names as their
* first level keys which map to the mappings object for each property.
* If the property is of type object it too could have a `properties`
* key whose value follows the same format.
*
* This data can be found at `{indexName}.mappings.{typeName}.properties`
* in the es indices.get() response.
*
* @param {EsMappingsDsl} mappings
* @return {EsPropertyMappings}
*/
export function getRootProperties(mappings) {
const mapping = mappings[getRootType(mappings)];
if (mapping.type !== 'object' && !mapping.properties) {
throw new TypeError('Unable to get property names non-object root mapping');
}
return mapping.properties || {};
}

View file

@ -0,0 +1,19 @@
import { getTypes } from './get_types';
/**
* Get the singular root type in the EsMappingsDsl
* object. If there are no types, or there are more
* that one type, this function will throw an error.
*
* @param {EsMappingsDsl} mappings
* @return {string}
*/
export function getRootType(mappings) {
const allTypes = getTypes(mappings);
if (allTypes.length !== 1) {
throw new TypeError(`Unable to get root type of mappings object with ${allTypes.length} root types.`);
}
return allTypes[0];
}

View file

@ -0,0 +1,9 @@
/**
* Get the names of the types defined in the EsMappingsDsl
*
* @param {EsMappingsDsl} mappings
* @return {Array<string>}
*/
export function getTypes(mappings) {
return Object.keys(mappings);
}

View file

@ -0,0 +1,4 @@
export { getProperty } from './get_property';
export { getTypes } from './get_types';
export { getRootType } from './get_root_type';
export { getRootProperties } from './get_root_properties';

View file

@ -1,71 +1,94 @@
import expect from 'expect.js';
import sinon from 'sinon';
import { SavedObjectsClient } from '../saved_objects_client';
import { createIdQuery } from '../lib/create_id_query';
import * as getSearchDslNS from '../lib/search_dsl/search_dsl';
import { getSearchDsl } from '../lib';
describe('SavedObjectsClient', () => {
const sandbox = sinon.sandbox.create();
let callAdminCluster;
let savedObjectsClient;
const docs = {
const searchResults = {
hits: {
total: 3,
hits: [{
_index: '.kibana',
_type: 'index-pattern',
_id: 'logstash-*',
_type: 'doc',
_id: 'index-pattern:logstash-*',
_score: 1,
_source: {
title: 'logstash-*',
timeFieldName: '@timestamp',
notExpandable: true
type: 'index-pattern',
'index-pattern': {
title: 'logstash-*',
timeFieldName: '@timestamp',
notExpandable: true
}
}
}, {
_index: '.kibana',
_type: 'config',
_id: '6.0.0-alpha1',
_type: 'doc',
_id: 'config:6.0.0-alpha1',
_score: 1,
_source: {
buildNum: 8467,
defaultIndex: 'logstash-*'
type: 'config',
config: {
buildNum: 8467,
defaultIndex: 'logstash-*'
}
}
}, {
_index: '.kibana',
_type: 'index-pattern',
_id: 'stocks-*',
_type: 'doc',
_id: 'index-pattern:stocks-*',
_score: 1,
_source: {
title: 'stocks-*',
timeFieldName: '@timestamp',
notExpandable: true
type: 'index-pattern',
'index-pattern': {
title: 'stocks-*',
timeFieldName: '@timestamp',
notExpandable: true
}
}
}]
}
};
const mappings = {
'index-pattern': {
doc: {
properties: {
someField: {
type: 'keyword'
'index-pattern': {
properties: {
someField: {
type: 'keyword'
}
}
}
}
}
};
beforeEach(() => {
callAdminCluster = sinon.mock();
callAdminCluster = sandbox.stub();
savedObjectsClient = new SavedObjectsClient('.kibana-test', mappings, callAdminCluster);
sandbox.stub(getSearchDslNS, 'getSearchDsl').returns({});
});
afterEach(() => {
callAdminCluster.reset();
sandbox.restore();
});
describe('#create', () => {
it('formats Elasticsearch response', async () => {
callAdminCluster.returns({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 });
beforeEach(() => {
callAdminCluster.returns(Promise.resolve({
_type: 'doc',
_id: 'index-pattern:logstash-*',
_version: 2
}));
});
it('formats Elasticsearch response', async () => {
const response = await savedObjectsClient.create('index-pattern', {
title: 'Logstash'
});
@ -81,8 +104,6 @@ describe('SavedObjectsClient', () => {
});
it('should use ES index action', async () => {
callAdminCluster.returns({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 });
await savedObjectsClient.create('index-pattern', {
id: 'logstash-*',
title: 'Logstash'
@ -95,8 +116,6 @@ describe('SavedObjectsClient', () => {
});
it('should use create action if ID defined and overwrite=false', async () => {
callAdminCluster.returns({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 });
await savedObjectsClient.create('index-pattern', {
title: 'Logstash'
}, {
@ -110,22 +129,32 @@ describe('SavedObjectsClient', () => {
});
it('allows for id to be provided', async () => {
callAdminCluster.returns({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 });
await savedObjectsClient.create('index-pattern', {
id: 'logstash-*',
title: 'Logstash'
}, { id: 'myId' });
}, { id: 'logstash-*' });
expect(callAdminCluster.calledOnce).to.be(true);
const args = callAdminCluster.getCall(0).args;
expect(args[1].id).to.be('myId');
expect(args[1].id).to.be('index-pattern:logstash-*');
});
it('self-generates an ID', async () => {
await savedObjectsClient.create('index-pattern', {
title: 'Logstash'
});
expect(callAdminCluster.calledOnce).to.be(true);
const args = callAdminCluster.getCall(0).args;
expect(args[1].id).to.match(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/);
});
});
describe('#bulkCreate', () => {
it('formats Elasticsearch request', async () => {
callAdminCluster.returns({ items: [] });
await savedObjectsClient.bulkCreate([
{ type: 'config', id: 'one', attributes: { title: 'Test One' } },
{ type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } }
@ -137,30 +166,39 @@ describe('SavedObjectsClient', () => {
expect(args[0]).to.be('bulk');
expect(args[1].body).to.eql([
{ create: { _type: 'config', _id: 'one' } },
{ title: 'Test One' },
{ create: { _type: 'index-pattern', _id: 'two' } },
{ title: 'Test Two' }
{ create: { _type: 'doc', _id: 'config:one' } },
{ type: 'config', config: { title: 'Test One' } },
{ create: { _type: 'doc', _id: 'index-pattern:two' } },
{ type: 'index-pattern', 'index-pattern': { title: 'Test Two' } }
]);
});
it('should overwrite objects if overwrite is truthy', async () => {
await savedObjectsClient.bulkCreate([
{ type: 'config', id: 'one', attributes: { title: 'Test One' } },
{ type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } }
], { overwrite: true });
callAdminCluster.returns({ items: [] });
expect(callAdminCluster.calledOnce).to.be(true);
await savedObjectsClient.bulkCreate([{ type: 'foo', id: 'bar', attributes: {} }], { overwrite: false });
sinon.assert.calledOnce(callAdminCluster);
sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({
body: [
// uses create because overwriting is not allowed
{ create: { _type: 'doc', _id: 'foo:bar' } },
{ type: 'foo', 'foo': {} },
]
}));
callAdminCluster.reset();
await savedObjectsClient.bulkCreate([{ type: 'foo', id: 'bar', attributes: {} }], { overwrite: true });
sinon.assert.calledOnce(callAdminCluster);
sinon.assert.calledWithExactly(callAdminCluster, 'bulk', sinon.match({
body: [
// uses index because overwriting is allowed
{ index: { _type: 'doc', _id: 'foo:bar' } },
{ type: 'foo', 'foo': {} },
]
}));
const args = callAdminCluster.getCall(0).args;
expect(args[0]).to.be('bulk');
expect(args[1].body).to.eql([
{ index: { _type: 'config', _id: 'one' } },
{ title: 'Test One' },
{ index: { _type: 'index-pattern', _id: 'two' } },
{ title: 'Test Two' }
]);
});
it('returns document errors', async () => {
@ -168,16 +206,16 @@ describe('SavedObjectsClient', () => {
errors: false,
items: [{
create: {
_type: 'config',
_id: 'one',
_type: 'doc',
_id: 'config:one',
error: {
reason: 'type[config] missing'
}
}
}, {
create: {
_type: 'index-pattern',
_id: 'two',
_type: 'doc',
_id: 'index-pattern:two',
_version: 2
}
}]
@ -192,15 +230,12 @@ describe('SavedObjectsClient', () => {
{
id: 'one',
type: 'config',
version: undefined,
attributes: { title: 'Test One' },
error: { message: 'type[config] missing' }
}, {
id: 'two',
type: 'index-pattern',
version: 2,
attributes: { title: 'Test Two' },
error: undefined
}
]);
});
@ -210,14 +245,14 @@ describe('SavedObjectsClient', () => {
errors: false,
items: [{
create: {
_type: 'config',
_id: 'one',
_type: 'doc',
_id: 'config:one',
_version: 2
}
}, {
create: {
_type: 'index-pattern',
_id: 'two',
_type: 'doc',
_id: 'index-pattern:two',
_version: 2
}
}]
@ -234,41 +269,40 @@ describe('SavedObjectsClient', () => {
type: 'config',
version: 2,
attributes: { title: 'Test One' },
error: undefined
}, {
id: 'two',
type: 'index-pattern',
version: 2,
attributes: { title: 'Test Two' },
error: undefined
}
]);
});
});
describe('#delete', () => {
it('throws notFound when ES is unable to find the document', (done) => {
callAdminCluster.returns(Promise.resolve({
deleted: 0
}));
it('throws notFound when ES is unable to find the document', async () => {
callAdminCluster.returns(Promise.resolve({ found: false }));
try {
await savedObjectsClient.delete('index-pattern', 'logstash-*');
expect().fail('should throw error');
} catch(e) {
expect(e.output.statusCode).to.eql(404);
}
savedObjectsClient.delete('index-pattern', 'logstash-*').then(() => {
done('failed');
}).catch(e => {
expect(e.output.statusCode).to.be(404);
done();
});
});
it('passes the parameters to callAdminCluster', async () => {
callAdminCluster.returns({});
await savedObjectsClient.delete('index-pattern', 'logstash-*');
expect(callAdminCluster.calledOnce).to.be(true);
const args = callAdminCluster.getCall(0).args;
expect(args[0]).to.be('deleteByQuery');
expect(args[1]).to.eql({
body: createIdQuery({ type: 'index-pattern', id: 'logstash-*' }),
const [method, args] = callAdminCluster.getCall(0).args;
expect(method).to.be('delete');
expect(args).to.eql({
type: 'doc',
id: 'index-pattern:logstash-*',
refresh: 'wait_for',
index: '.kibana-test'
});
@ -276,20 +310,68 @@ describe('SavedObjectsClient', () => {
});
describe('#find', () => {
it('formats Elasticsearch response', async () => {
const count = docs.hits.hits.length;
beforeEach(() => {
callAdminCluster.returns(searchResults);
});
it('requires searchFields be an array if defined', async () => {
try {
await savedObjectsClient.find({ searchFields: 'string' });
throw new Error('expected find() to reject');
} catch (error) {
expect(error).to.have.property('message').contain('must be an array');
}
});
it('requires fields be an array if defined', async () => {
try {
await savedObjectsClient.find({ fields: 'string' });
throw new Error('expected find() to reject');
} catch (error) {
expect(error).to.have.property('message').contain('must be an array');
}
});
it('passes mappings, search, searchFields, type, sortField, and sortOrder to getSearchDsl', async () => {
const relevantOpts = {
search: 'foo*',
searchFields: ['foo'],
type: 'bar',
sortField: 'name',
sortOrder: 'desc'
};
await savedObjectsClient.find(relevantOpts);
sinon.assert.calledOnce(getSearchDsl);
sinon.assert.calledWithExactly(getSearchDsl, mappings, relevantOpts);
});
it('merges output of getSearchDsl into es request body', async () => {
getSearchDsl.returns({ query: 1, aggregations: 2 });
await savedObjectsClient.find();
sinon.assert.calledOnce(callAdminCluster);
sinon.assert.calledWithExactly(callAdminCluster, 'search', sinon.match({
body: sinon.match({
query: 1,
aggregations: 2,
})
}));
});
it('formats Elasticsearch response', async () => {
const count = searchResults.hits.hits.length;
callAdminCluster.returns(Promise.resolve(docs));
const response = await savedObjectsClient.find();
expect(response.total).to.be(count);
expect(response.saved_objects).to.have.length(count);
docs.hits.hits.forEach((doc, i) => {
searchResults.hits.hits.forEach((doc, i) => {
expect(response.saved_objects[i]).to.eql({
id: doc._id,
type: doc._type,
id: doc._id.replace(/(index-pattern|config)\:/, ''),
type: doc._source.type,
version: doc._version,
attributes: doc._source
attributes: doc._source[doc._source.type]
});
});
});
@ -304,80 +386,8 @@ describe('SavedObjectsClient', () => {
expect(options.from).to.be(50);
});
it('accepts type', async () => {
await savedObjectsClient.find({ type: 'index-pattern' });
expect(callAdminCluster.calledOnce).to.be(true);
const options = callAdminCluster.getCall(0).args[1];
const expectedQuery = {
bool: {
must: [{ match_all: {} }],
filter: [
{
bool: {
should: [
{
term: {
_type: 'index-pattern'
}
}, {
term: {
type: 'index-pattern'
}
}
]
}
}
]
}
};
expect(options.body).to.eql({
query: expectedQuery, version: true
});
});
it('throws error when providing sortField but no type', (done) => {
savedObjectsClient.find({
sortField: 'someField'
}).then(() => {
done('failed');
}).catch(e => {
expect(e).to.be.an(Error);
done();
});
});
it('accepts sort with type', async () => {
await savedObjectsClient.find({
type: 'index-pattern',
sortField: 'someField',
sortOrder: 'desc',
});
expect(callAdminCluster.calledOnce).to.be(true);
const options = callAdminCluster.getCall(0).args[1];
const expectedQuerySort = [
{
someField: {
order: 'desc',
unmapped_type: 'keyword'
},
}, {
'index-pattern.someField': {
order: 'desc',
unmapped_type: 'keyword'
},
},
];
expect(options.body.sort).to.eql(expectedQuerySort);
});
it('can filter by fields', async () => {
await savedObjectsClient.find({ fields: 'title' });
await savedObjectsClient.find({ fields: ['title'] });
expect(callAdminCluster.calledOnce).to.be(true);
@ -389,22 +399,21 @@ describe('SavedObjectsClient', () => {
});
describe('#get', () => {
it('formats Elasticsearch response', async () => {
beforeEach(() => {
callAdminCluster.returns(Promise.resolve({
hits: {
hits: [
{
_id: 'logstash-*',
_type: 'index-pattern',
_version: 2,
_source: {
title: 'Testing'
}
}
]
_id: 'index-pattern:logstash-*',
_type: 'doc',
_version: 2,
_source: {
type: 'index-pattern',
'index-pattern': {
title: 'Testing'
}
}
}));
});
it('formats Elasticsearch response', async () => {
const response = await savedObjectsClient.get('index-pattern', 'logstash-*');
expect(response).to.eql({
id: 'logstash-*',
@ -415,10 +424,20 @@ describe('SavedObjectsClient', () => {
}
});
});
it('prepends type to the id', async () => {
await savedObjectsClient.get('index-pattern', 'logstash-*');
const [, args] = callAdminCluster.getCall(0).args;
expect(args.id).to.eql('index-pattern:logstash-*');
expect(args.type).to.eql('doc');
});
});
describe('#bulkGet', () => {
it('accepts an array of mixed type and ids', async () => {
it('accepts a array of mixed type and ids', async () => {
callAdminCluster.returns({ docs: [] });
await savedObjectsClient.bulkGet([
{ id: 'one', type: 'config' },
{ id: 'two', type: 'index-pattern' }
@ -427,15 +446,15 @@ describe('SavedObjectsClient', () => {
expect(callAdminCluster.calledOnce).to.be(true);
const options = callAdminCluster.getCall(0).args[1];
expect(options.body).to.eql([
{},
createIdQuery({ type: 'config', id: 'one' }),
{},
createIdQuery({ type: 'index-pattern', id: 'two' })
expect(options.body.docs).to.eql([
{ _type: 'doc', _id: 'config:one' },
{ _type: 'doc', _id: 'index-pattern:two' }
]);
});
it('returns early for empty objects argument', async () => {
callAdminCluster.returns({ docs: [] });
const response = await savedObjectsClient.bulkGet([]);
expect(response.saved_objects).to.have.length(0);
@ -444,74 +463,73 @@ describe('SavedObjectsClient', () => {
it('reports error on missed objects', async () => {
callAdminCluster.returns(Promise.resolve({
responses: [
{
hits: {
hits: [
{
_id: 'good',
_type: 'doc',
_version: 2,
_source: {
type: 'config',
config: {
title: 'Test'
}
}
}
]
}
}
]
docs:[{
_type: 'doc',
_id: 'config:good',
found: true,
_version: 2,
_source: { config: { title: 'Test' } }
}, {
_type: 'doc',
_id: 'config:bad',
found: false
}]
}));
const { saved_objects: savedObjects } = await savedObjectsClient.bulkGet(
[{ id: 'good', type: 'config' }, { id: 'bad', type: 'config' }]
);
expect(savedObjects).to.have.length(1);
expect(savedObjects).to.have.length(2);
expect(savedObjects[0]).to.eql({
id: 'good',
type: 'config',
version: 2,
attributes: { title: 'Test' }
});
expect(savedObjects[1]).to.eql({
id: 'bad',
type: 'config',
error: { statusCode: 404, message: 'Not found' }
});
});
});
describe('#update', () => {
it('returns current ES document version', async () => {
const id = 'logstash-*';
const type = 'index-pattern';
const version = 2;
const attributes = { title: 'Testing' };
const id = 'logstash-*';
const type = 'index-pattern';
const newVersion = 2;
const attributes = { title: 'Testing' };
beforeEach(() => {
callAdminCluster.returns(Promise.resolve({
_id: id,
_type: type,
_version: version,
_id: `${type}:${id}`,
_type: 'doc',
_version: newVersion,
result: 'updated'
}));
});
it('returns current ES document version', async () => {
const response = await savedObjectsClient.update('index-pattern', 'logstash-*', attributes);
expect(response).to.eql({
id,
type,
version,
version: newVersion,
attributes
});
});
it('accepts version', async () => {
await savedObjectsClient.update(
'index-pattern',
'logstash-*',
type,
id,
{ title: 'Testing' },
{ version: 1 }
{ version: newVersion - 1 }
);
const esParams = callAdminCluster.getCall(0).args[1];
expect(esParams.version).to.be(1);
expect(esParams.version).to.be(newVersion - 1);
});
it('passes the parameters to callAdminCluster', async () => {
@ -523,10 +541,10 @@ describe('SavedObjectsClient', () => {
expect(args[0]).to.be('update');
expect(args[1]).to.eql({
type: 'index-pattern',
id: 'logstash-*',
type: 'doc',
id: 'index-pattern:logstash-*',
version: undefined,
body: { doc: { title: 'Testing' } },
body: { doc: { 'index-pattern': { title: 'Testing' } } },
refresh: 'wait_for',
index: '.kibana-test'
});

View file

@ -1,195 +0,0 @@
import elasticsearch from 'elasticsearch';
import expect from 'expect.js';
import sinon from 'sinon';
import { SavedObjectsClient } from '../saved_objects_client';
const { BadRequest } = elasticsearch.errors;
describe('SavedObjectsClient', () => {
let callAdminCluster;
let savedObjectsClient;
const illegalArgumentException = { type: 'type_missing_exception' };
describe('mapping', () => {
beforeEach(() => {
callAdminCluster = sinon.stub();
savedObjectsClient = new SavedObjectsClient('.kibana-test', {}, callAdminCluster);
});
afterEach(() => {
callAdminCluster.reset();
});
describe('#create', () => {
it('falls back to single-type mapping', async () => {
const error = new BadRequest('[illegal_argument_exception] Rejecting mapping update to [.kibana-v6]', {
body: {
error: illegalArgumentException
}
});
callAdminCluster
.onFirstCall().throws(error)
.onSecondCall().returns(Promise.resolve({ _type: 'index-pattern', _id: 'logstash-*', _version: 2 }));
const response = await savedObjectsClient.create('index-pattern', {
title: 'Logstash'
});
expect(response).to.eql({
type: 'index-pattern',
id: 'logstash-*',
version: 2,
attributes: {
title: 'Logstash',
}
});
});
it('prepends id for single-type', async () => {
const id = 'foo';
const error = new BadRequest('[illegal_argument_exception] Rejecting mapping update to [.kibana-v6]', {
body: {
error: illegalArgumentException
}
});
callAdminCluster
.onFirstCall().throws(error)
.onSecondCall().returns(Promise.resolve());
await savedObjectsClient.create('index-pattern', {}, { id });
const [, args] = callAdminCluster.getCall(1).args;
expect(args.id).to.eql('index-pattern:foo');
});
});
describe('#bulkCreate', () => {
const firstResponse = {
errors: true,
items: [{
create: {
_type: 'config',
_id: 'one',
_version: 2,
status: 400,
error: illegalArgumentException
}
}, {
create: {
_type: 'index-pattern',
_id: 'two',
_version: 2,
status: 400,
error: illegalArgumentException
}
}]
};
const secondResponse = {
errors: false,
items: [{
create: {
_type: 'config',
_id: 'one',
_version: 2
}
}, {
create: {
_type: 'index-pattern',
_id: 'two',
_version: 2
}
}]
};
it('falls back to single-type mappings', async () => {
callAdminCluster
.onFirstCall().returns(Promise.resolve(firstResponse))
.onSecondCall().returns(Promise.resolve(secondResponse));
const response = await savedObjectsClient.bulkCreate([
{ type: 'config', id: 'one', attributes: { title: 'Test One' } },
{ type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } }
]);
expect(response).to.eql([
{
id: 'one',
type: 'config',
version: 2,
attributes: { title: 'Test One' },
error: undefined
}, {
id: 'two',
type: 'index-pattern',
version: 2,
attributes: { title: 'Test Two' },
error: undefined
}
]);
});
it('prepends id for single-type', async () => {
callAdminCluster
.onFirstCall().returns(Promise.resolve(firstResponse))
.onSecondCall().returns(Promise.resolve(secondResponse));
await savedObjectsClient.bulkCreate([
{ type: 'config', id: 'one', attributes: { title: 'Test One' } },
{ type: 'index-pattern', id: 'two', attributes: { title: 'Test Two' } }
]);
const [, { body }] = callAdminCluster.getCall(1).args;
expect(body[0].create._id).to.eql('config:one');
expect(body[2].create._id).to.eql('index-pattern:two');
// expect(args.id).to.eql('index-pattern:foo');
});
});
describe('update', () => {
const id = 'logstash-*';
const type = 'index-pattern';
const version = 2;
const attributes = { title: 'Testing' };
const error = new BadRequest('[document_missing_exception] [config][logstash-*]: document missing', {
body: {
error: {
type: 'document_missing_exception'
}
}
});
beforeEach(() => {
callAdminCluster
.onFirstCall().throws(error)
.onSecondCall().returns(Promise.resolve({
_id: id,
_type: type,
_version: version,
result: 'updated'
}));
});
it('falls back to single-type mappings', async () => {
const response = await savedObjectsClient.update('index-pattern', 'logstash-*', attributes);
expect(response).to.eql({
id,
type,
version,
attributes
});
});
it('prepends id for single-type', async () => {
await savedObjectsClient.update('index-pattern', 'logstash-*', attributes);
const [, args] = callAdminCluster.getCall(1).args;
expect(args.id).to.eql('index-pattern:logstash-*');
});
});
});
});

View file

@ -1,53 +0,0 @@
import expect from 'expect.js';
import { v5BulkCreate, v6BulkCreate } from '../compatibility';
describe('compatibility', () => {
const testObjects = [
{ type: 'index-pattern', id: 'one', attributes: { title: 'Test Index Pattern' } },
{ type: 'config', id: 'two', attributes: { title: 'Test Config Value' } }
];
describe('v5BulkCreate', () => {
it('handles default options', () => {
const objects = v5BulkCreate(testObjects);
expect(objects).to.eql([
{ create: { _type: 'index-pattern', _id: 'one' } },
{ title: 'Test Index Pattern' },
{ create: { _type: 'config', _id: 'two' } },
{ title: 'Test Config Value' }
]);
});
it('uses index action for options.overwrite=true', () => {
const objects = v5BulkCreate(testObjects, { overwrite: true });
expect(objects).to.eql([
{ index: { _type: 'index-pattern', _id: 'one' } },
{ title: 'Test Index Pattern' },
{ index: { _type: 'config', _id: 'two' } },
{ title: 'Test Config Value' }
]);
});
});
describe('v6BulkCreate', () => {
it('handles default options', () => {
const objects = v6BulkCreate(testObjects);
expect(objects).to.eql([
{ create: { _type: 'doc', _id: 'index-pattern:one' } },
{ type: 'index-pattern', 'index-pattern': { title: 'Test Index Pattern' } },
{ create: { _type: 'doc', _id: 'config:two' } },
{ type: 'config', config: { title: 'Test Config Value' } }
]);
});
it('uses index action for options.overwrite=true', () => {
const objects = v6BulkCreate(testObjects, { overwrite: true });
expect(objects).to.eql([
{ index: { _type: 'doc', _id: 'index-pattern:one' } },
{ type: 'index-pattern', 'index-pattern': { title: 'Test Index Pattern' } },
{ index: { _type: 'doc', _id: 'config:two' } },
{ type: 'config', config: { title: 'Test Config Value' } }
]);
});
});
});

View file

@ -1,96 +0,0 @@
import expect from 'expect.js';
import { createFindQuery } from '../create_find_query';
const mappings = {};
describe('createFindQuery', () => {
it('matches all when there is no type or filter', () => {
const query = createFindQuery(mappings);
expect(query).to.eql({ query: { match_all: {} }, version: true });
});
it('adds bool filter for type', () => {
const query = createFindQuery(mappings, { type: 'index-pattern' });
expect(query).to.eql({
query: {
bool: {
filter: [{
bool: {
should: [
{
term: {
_type: 'index-pattern'
}
},
{
term: {
type: 'index-pattern'
}
}
]
}
}],
must: [{
match_all: {}
}]
}
},
version: true
});
});
it('can search across all fields', () => {
const query = createFindQuery(mappings, { search: 'foo' });
expect(query).to.eql({
query: {
bool: {
filter: [],
must: [{
simple_query_string: {
query: 'foo',
all_fields: true
}
}]
}
},
version: true
});
});
it('can search a single field', () => {
const query = createFindQuery(mappings, { search: 'foo', searchFields: 'title' });
expect(query).to.eql({
query: {
bool: {
filter: [],
must: [{
simple_query_string: {
query: 'foo',
fields: ['title']
}
}]
}
},
version: true
});
});
it('can search across multiple fields', () => {
const query = createFindQuery(mappings, { search: 'foo', searchFields: ['title', 'description'] });
expect(query).to.eql({
query: {
bool: {
filter: [],
must: [{
simple_query_string: {
query: 'foo',
fields: ['title', 'description']
}
}]
}
},
version: true
});
});
});

View file

@ -1,48 +0,0 @@
import expect from 'expect.js';
import { createIdQuery } from '../create_id_query';
describe('createIdQuery', () => {
it('takes an id and type', () => {
const query = createIdQuery({ id: 'foo', type: 'bar' });
const expectedQuery = {
version: true,
size: 1,
query: {
bool: {
should: [
// v5 document
{
bool: {
must: [
{ term: { _id: 'foo' } },
{ term: { _type: 'bar' } }
]
}
},
// migrated v5 document
{
bool: {
must: [
{ term: { _id: 'bar:foo' } },
{ term: { type: 'bar' } }
]
}
},
// v6 document
{
bool: {
must: [
{ term: { _id: 'foo' } },
{ term: { type: 'bar' } }
]
}
},
]
}
}
};
expect(query).to.eql(expectedQuery);
});
});

View file

@ -1,105 +0,0 @@
import expect from 'expect.js';
import { normalizeEsDoc } from '../normalize_es_doc';
describe('normalizeEsDoc', () => {
it('handle legacy doc types', () => {
const doc = {
_id: 'foo',
_type: 'test',
_version: 2,
_source: { title: 'test' }
};
expect(normalizeEsDoc(doc)).to.eql({
id: 'foo',
type: 'test',
version: 2,
attributes: { title: 'test' }
});
});
it('handle migrated single doc type', () => {
const doc = {
_id: 'test:foo',
_type: 'doc',
_version: 2,
_source: { type: 'test', test: { title: 'test' } }
};
expect(normalizeEsDoc(doc)).to.eql({
id: 'foo',
type: 'test',
version: 2,
attributes: { title: 'test' }
});
});
it('handles an overwritten type', () => {
const doc = {
_type: 'doc',
_id: 'test:foo',
_version: 2,
_source: { type: 'test', test: { title: 'test' } }
};
const overrides = { type: 'test' };
expect(normalizeEsDoc(doc, overrides)).to.eql({
id: 'foo',
type: 'test',
version: 2,
attributes: { title: 'test' }
});
});
it('can add additional keys', () => {
const doc = {
_type: 'doc',
_id: 'test:foo',
_version: 2,
_source: { type: 'test', test: { title: 'test' } }
};
const overrides = { error: 'An error!' };
expect(normalizeEsDoc(doc, overrides)).to.eql({
id: 'foo',
type: 'test',
version: 2,
attributes: { title: 'test' },
error: 'An error!'
});
});
it('handles already prefixed ids with the type', () => {
const doc = {
_type: 'doc',
_id: 'test:test:foo',
_version: 2,
_source: { type: 'test', test: { title: 'test' } }
};
const overrides = { error: 'An error!' };
expect(normalizeEsDoc(doc, overrides)).to.eql({
id: 'test:foo',
type: 'test',
version: 2,
attributes: { title: 'test' },
error: 'An error!'
});
});
it('handles legacy doc having an attribute the same as type', () => {
const doc = {
_id: 'foo',
_type: 'test',
_version: 2,
_source: { test: 'test' }
};
expect(normalizeEsDoc(doc)).to.eql({
id: 'foo',
type: 'test',
version: 2,
attributes: { test: 'test' }
});
});
});

View file

@ -1,43 +0,0 @@
import uuid from 'uuid';
import { V6_TYPE } from '../saved_objects_client';
/**
* @param {array} objects - [{ type, id, attributes }]
* @param {object} [options={}]
* @property {boolean} [options.overwrite=false] - overrides existing documents
* @returns {array}
*/
export function v5BulkCreate(objects, options = {}) {
return objects.reduce((acc, object) => {
const method = object.id && !options.overwrite ? 'create' : 'index';
acc.push({ [method]: { _type: object.type, _id: object.id } });
acc.push(object.attributes);
return acc;
}, []);
}
/**
* @param {array} objects - [{ type, id, attributes }]
* @param {object} [options={}]
* @property {boolean} [options.overwrite=false] - overrides existing documents
* @returns {array}
*/
export function v6BulkCreate(objects, options = {}) {
return objects.reduce((acc, object) => {
const method = object.id && !options.overwrite ? 'create' : 'index';
acc.push({ [method]: {
_type: V6_TYPE,
_id: `${object.type}:${object.id || uuid.v1()}`,
} });
acc.push(Object.assign({},
{ type: object.type },
{ [object.type]: object.attributes }
));
return acc;
}, []);
}

View file

@ -1,70 +0,0 @@
import { get } from 'lodash';
export function createFindQuery(mappings, options = {}) {
const { type, search, searchFields, sortField, sortOrder } = options;
if (!type && sortField) {
throw new Error('Cannot sort without knowing the type');
}
if (!type && !search) {
return { version: true, query: { match_all: {} } };
}
const bool = { must: [], filter: [] };
if (type) {
bool.filter.push({
bool: {
should: [
{
term: {
_type: type
}
},
{
term: {
type
}
}
]
}
});
}
if (search) {
const simpleQueryString = {
query: search
};
if (!searchFields) {
simpleQueryString.all_fields = true;
} else if (Array.isArray(searchFields)) {
simpleQueryString.fields = searchFields;
} else {
simpleQueryString.fields = [searchFields];
}
bool.must.push({ simple_query_string: simpleQueryString });
} else {
bool.must.push({
match_all: {}
});
}
const query = { version: true, query: { bool } };
if (sortField) {
const value = {
order: sortOrder,
unmapped_type: get(mappings, [type, 'properties', sortField, 'type'])
};
query.sort = [{
[sortField]: value
}, {
[`${type}.${sortField}`]: value
}];
}
return query;
}

View file

@ -1,45 +0,0 @@
/**
* Finds a document by either its v5 or v6 format
*
* @param type The documents type
* @param id The documents id or legacy id
**/
export function createIdQuery({ type, id }) {
return {
version: true,
size: 1,
query: {
bool: {
should: [
// v5 document
{
bool: {
must: [
{ term: { _id: id } },
{ term: { _type: type } }
]
}
},
// migrated v5 document
{
bool: {
must: [
{ term: { _id: `${type}:${id}` } },
{ term: { type: type } }
]
}
},
// v6 document
{
bool: {
must: [
{ term: { _id: id } },
{ term: { type: type } }
]
}
},
]
}
}
};
}

View file

@ -1,6 +1,4 @@
export { createFindQuery } from './create_find_query';
export { createIdQuery } from './create_id_query';
export { getSearchDsl } from './search_dsl';
export { handleEsError } from './handle_es_error';
export { v5BulkCreate, v6BulkCreate } from './compatibility';
export { normalizeEsDoc } from './normalize_es_doc';
export { trimIdPrefix } from './trim_id_prefix';
export { includedFields } from './included_fields';

View file

@ -1,29 +0,0 @@
import { get } from 'lodash';
import { V6_TYPE } from '../saved_objects_client';
export function normalizeEsDoc(doc, overrides = {}) {
if (!doc) return {};
let type;
let id = doc._id;
let attributes;
if (doc._type === V6_TYPE) {
type = overrides.type || get(doc, '_source.type');
attributes = get(doc, `_source.${type}`);
// migrated v5 indices and objects created with a specified ID
// have the type prefixed to the id.
id = doc._id.replace(`${type}:`, '');
} else {
type = overrides.type || doc._type;
attributes = doc._source;
}
return Object.assign({}, {
id,
type,
version: doc._version,
attributes
}, overrides);
}

View file

@ -0,0 +1,247 @@
import expect from 'expect.js';
import { getQueryParams } from '../query_params';
const MAPPINGS = {
rootType: {
properties: {
type: {
type: 'keyword'
},
pending: {
properties: {
title: {
type: 'text',
}
}
},
saved: {
properties: {
title: {
type: 'text',
fields: {
raw: {
type: 'keyword'
}
}
},
obj: {
properties: {
key1: {
type: 'text'
}
}
}
}
}
}
}
};
describe('searchDsl/queryParams', () => {
describe('{}', () => {
it('searches for everything', () => {
expect(getQueryParams(MAPPINGS))
.to.eql({});
});
});
describe('{type}', () => {
it('includes just a terms filter', () => {
expect(getQueryParams(MAPPINGS, 'saved'))
.to.eql({
query: {
bool: {
filter: [
{
term: { type: 'saved' }
}
]
}
}
});
});
});
describe('{search}', () => {
it('includes just a sqs query', () => {
expect(getQueryParams(MAPPINGS, null, 'us*'))
.to.eql({
query: {
bool: {
must: [
{
simple_query_string: {
query: 'us*',
all_fields: true
}
}
]
}
}
});
});
});
describe('{type,search}', () => {
it('includes bool with sqs query and term filter for type', () => {
expect(getQueryParams(MAPPINGS, 'saved', 'y*'))
.to.eql({
query: {
bool: {
filter: [
{ term: { type: 'saved' } }
],
must: [
{
simple_query_string: {
query: 'y*',
all_fields: true
}
}
]
}
}
});
});
});
describe('{search,searchFields}', () => {
it('includes all types for field', () => {
expect(getQueryParams(MAPPINGS, null, 'y*', ['title']))
.to.eql({
query: {
bool: {
must: [
{
simple_query_string: {
query: 'y*',
fields: [
'type.title',
'pending.title',
'saved.title'
]
}
}
]
}
}
});
});
it('supports field boosting', () => {
expect(getQueryParams(MAPPINGS, null, 'y*', ['title^3']))
.to.eql({
query: {
bool: {
must: [
{
simple_query_string: {
query: 'y*',
fields: [
'type.title^3',
'pending.title^3',
'saved.title^3'
]
}
}
]
}
}
});
});
it('supports field and multi-field', () => {
expect(getQueryParams(MAPPINGS, null, 'y*', ['title', 'title.raw']))
.to.eql({
query: {
bool: {
must: [
{
simple_query_string: {
query: 'y*',
fields: [
'type.title',
'pending.title',
'saved.title',
'type.title.raw',
'pending.title.raw',
'saved.title.raw',
]
}
}
]
}
}
});
});
});
describe('{type,search,searchFields}', () => {
it('includes bool, and sqs with field list', () => {
expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title']))
.to.eql({
query: {
bool: {
filter: [
{ term: { type: 'saved' } }
],
must: [
{
simple_query_string: {
query: 'y*',
fields: [
'saved.title'
]
}
}
]
}
}
});
});
it('supports fields pointing to multi-fields', () => {
expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title.raw']))
.to.eql({
query: {
bool: {
filter: [
{ term: { type: 'saved' } }
],
must: [
{
simple_query_string: {
query: 'y*',
fields: [
'saved.title.raw'
]
}
}
]
}
}
});
});
it('supports multiple search fields', () => {
expect(getQueryParams(MAPPINGS, 'saved', 'y*', ['title', 'title.raw']))
.to.eql({
query: {
bool: {
filter: [
{ term: { type: 'saved' } }
],
must: [
{
simple_query_string: {
query: 'y*',
fields: [
'saved.title',
'saved.title.raw'
]
}
}
]
}
}
});
});
});
});

View file

@ -0,0 +1,77 @@
import sinon from 'sinon';
import expect from 'expect.js';
import { getSearchDsl } from '../search_dsl';
import * as queryParamsNS from '../query_params';
import * as sortParamsNS from '../sorting_params';
describe('getSearchDsl', () => {
const sandbox = sinon.sandbox.create();
afterEach(() => sandbox.restore());
describe('validation', () => {
it('throws when sortField is passed without type', () => {
expect(() => {
getSearchDsl({}, {
type: undefined,
sortField: 'title'
});
}).to.throwException(/sort without .+ type/);
});
it('throws when sortOrder without sortField', () => {
expect(() => {
getSearchDsl({}, {
type: 'foo',
sortOrder: 'desc'
});
}).to.throwException(/sortOrder requires a sortField/);
});
});
describe('passes control', () => {
it('passes (mappings, type, search, searchFields) to getQueryParams', () => {
const spy = sandbox.spy(queryParamsNS, 'getQueryParams');
const mappings = { type: { properties: {} } };
const opts = {
type: 'foo',
search: 'bar',
searchFields: ['baz']
};
getSearchDsl(mappings, opts);
sinon.assert.calledOnce(spy);
sinon.assert.calledWithExactly(
spy,
mappings,
opts.type,
opts.search,
opts.searchFields,
);
});
it('passes (mappings, type, sortField, sortOrder) to getSortingParams', () => {
const spy = sandbox.stub(sortParamsNS, 'getSortingParams').returns({});
const mappings = { type: { properties: {} } };
const opts = {
type: 'foo',
sortField: 'bar',
sortOrder: 'baz'
};
getSearchDsl(mappings, opts);
sinon.assert.calledOnce(spy);
sinon.assert.calledWithExactly(
spy,
mappings,
opts.type,
opts.sortField,
opts.sortOrder,
);
});
it('returns combination of getQueryParams and getSortingParams', () => {
sandbox.stub(queryParamsNS, 'getQueryParams').returns({ a: 'a' });
sandbox.stub(sortParamsNS, 'getSortingParams').returns({ b: 'b' });
expect(getSearchDsl({})).to.eql({ a: 'a', b: 'b' });
});
});
});

View file

@ -0,0 +1,125 @@
import expect from 'expect.js';
import { getSortingParams } from '../sorting_params';
const MAPPINGS = {
rootType: {
properties: {
pending: {
properties: {
title: {
type: 'text',
}
}
},
saved: {
properties: {
title: {
type: 'text',
fields: {
raw: {
type: 'keyword'
}
}
},
obj: {
properties: {
key1: {
type: 'text'
}
}
}
}
}
}
}
};
describe('searchDsl/getSortParams', () => {
describe('no sortField, type, or order', () => {
it('returns no params', () => {
expect(getSortingParams(MAPPINGS))
.to.eql({});
});
});
describe('type, no sortField', () => {
it('returns no params', () => {
expect(getSortingParams(MAPPINGS, 'pending'))
.to.eql({});
});
});
describe('type, order, no sortField', () => {
it('returns no params', () => {
expect(getSortingParams(MAPPINGS, 'saved', null, 'desc'))
.to.eql({});
});
});
describe('search field no direction', () => {
describe('search field is simple property', () => {
it('returns correct params', () => {
expect(getSortingParams(MAPPINGS, 'saved', 'title'))
.to.eql({
sort: [
{
'saved.title': {
order: undefined,
unmapped_type: 'text'
}
}
]
});
});
});
describe('search field is multi-field', () => {
it('returns correct params', () => {
expect(getSortingParams(MAPPINGS, 'saved', 'title.raw'))
.to.eql({
sort: [
{
'saved.title.raw': {
order: undefined,
unmapped_type: 'keyword'
}
}
]
});
});
});
});
describe('search with direction', () => {
describe('search field is simple property', () => {
it('returns correct params', () => {
expect(getSortingParams(MAPPINGS, 'saved', 'title', 'desc'))
.to.eql({
sort: [
{
'saved.title': {
order: 'desc',
unmapped_type: 'text'
}
}
]
});
});
});
describe('search field is multi-field', () => {
it('returns correct params', () => {
expect(getSortingParams(MAPPINGS, 'saved', 'title.raw', 'asc'))
.to.eql({
sort: [
{
'saved.title.raw': {
order: 'asc',
unmapped_type: 'keyword'
}
}
]
});
});
});
});
});

View file

@ -0,0 +1 @@
export { getSearchDsl } from './search_dsl';

View file

@ -0,0 +1,60 @@
import { getRootProperties } from '../../../../mappings';
/**
* Get the field params based on the types and searchFields
* @param {Array<string>} searchFields
* @param {Array<string>} types
* @return {Object}
*/
function getFieldsForTypes(searchFields, types) {
if (!searchFields || !searchFields.length) {
return {
all_fields: true
};
}
return {
fields: searchFields.reduce((acc, field) => [
...acc,
...types.map(prefix => `${prefix}.${field}`)
], []),
};
}
/**
* Get the "query" related keys for the search body
* @param {EsMapping} mapping mappings from Ui
* @param {Object} type
* @param {String} search
* @param {Array<string>} searchFields
* @return {Object}
*/
export function getQueryParams(mappings, type, search, searchFields) {
if (!type && !search) {
return {};
}
const bool = {};
if (type) {
bool.filter = [
{ term: { type } }
];
}
if (search) {
bool.must = [
{
simple_query_string: {
query: search,
...getFieldsForTypes(
searchFields,
type ? [type] : Object.keys(getRootProperties(mappings))
)
}
}
];
}
return { query: { bool } };
}

View file

@ -0,0 +1,27 @@
import Boom from 'boom';
import { getQueryParams } from './query_params';
import { getSortingParams } from './sorting_params';
export function getSearchDsl(mappings, options = {}) {
const {
type,
search,
searchFields,
sortField,
sortOrder
} = options;
if (!type && sortField) {
throw Boom.notAcceptable('Cannot sort without filtering by type');
}
if (sortOrder && !sortField) {
throw Boom.notAcceptable('sortOrder requires a sortField');
}
return {
...getQueryParams(mappings, type, search, searchFields),
...getSortingParams(mappings, type, sortField, sortOrder),
};
}

View file

@ -0,0 +1,25 @@
import Boom from 'boom';
import { getProperty } from '../../../../mappings';
export function getSortingParams(mappings, type, sortField, sortOrder) {
if (!sortField) {
return {};
}
const field = getProperty(mappings, `${type}.${sortField}`);
if (!field) {
throw Boom.badRequest(`Unknown sort field ${sortField}`);
}
return {
sort: [
{
[`${type}.${sortField}`]: {
order: sortOrder,
unmapped_type: field.type
}
}
]
};
}

View file

@ -0,0 +1,25 @@
function assertNonEmptyString(value, name) {
if (!value || typeof value !== 'string') {
throw new TypeError(`Expected "${value}" to be a ${name}`);
}
}
/**
* Trim the prefix from the id of a saved object doc
*
* @param {string} id
* @param {string} type
* @return {string}
*/
export function trimIdPrefix(id, type) {
assertNonEmptyString(id, 'document id');
assertNonEmptyString(type, 'saved object type');
const prefix = `${type}:`;
if (!id.startsWith(prefix)) {
return id;
}
return id.slice(prefix.length);
}

View file

@ -1,23 +1,20 @@
import Boom from 'boom';
import uuid from 'uuid';
import { get } from 'lodash';
import { getRootType } from '../../mappings';
import {
createFindQuery,
createIdQuery,
getSearchDsl,
handleEsError,
v5BulkCreate,
v6BulkCreate,
normalizeEsDoc,
trimIdPrefix,
includedFields
} from './lib';
export const V6_TYPE = 'doc';
export class SavedObjectsClient {
constructor(kibanaIndex, mappings, callAdminCluster) {
this._kibanaIndex = kibanaIndex;
this._mappings = mappings;
this._type = getRootType(this._mappings);
this._callAdminCluster = callAdminCluster;
}
@ -32,22 +29,28 @@ export class SavedObjectsClient {
* @returns {promise} - { id, type, version, attributes }
*/
async create(type, attributes = {}, options = {}) {
const method = options.id && !options.overwrite ? 'create' : 'index';
const response = await this._withKibanaIndexAndMappingFallback(method, {
type,
id: options.id,
body: attributes,
refresh: 'wait_for'
}, {
type: V6_TYPE,
id: `${type}:${options.id || uuid.v1()}`,
const {
id,
overwrite = false
} = options;
const method = id && !overwrite ? 'create' : 'index';
const response = await this._withKibanaIndex(method, {
id: this._generateEsId(type, id),
type: this._type,
refresh: 'wait_for',
body: {
type,
[type]: attributes
}
},
});
return normalizeEsDoc(response, { type, attributes });
return {
id: trimIdPrefix(response._id, type),
type,
version: response._version,
attributes
};
}
/**
@ -55,41 +58,68 @@ export class SavedObjectsClient {
*
* @param {array} objects - [{ type, id, attributes }]
* @param {object} [options={}]
* @property {boolean} [options.force=false] - overrides existing documents
* @property {string} [options.format=v5]
* @property {boolean} [options.overwrite=false] - overwrites existing documents
* @returns {promise} - [{ id, type, version, attributes, error: { message } }]
*/
async bulkCreate(objects, options = {}) {
const { format = 'v5' } = options;
const {
overwrite = false
} = options;
const bulkCreate = format === 'v5' ? v5BulkCreate : v6BulkCreate;
const response = await this._withKibanaIndex('bulk', {
body: bulkCreate(objects, options),
refresh: 'wait_for'
const objectToBulkRequest = (object) => {
const method = object.id && !overwrite ? 'create' : 'index';
return [
{
[method]: {
_id: this._generateEsId(object.type, object.id),
_type: this._type,
}
},
{
type: object.type,
[object.type]: object.attributes
}
];
};
const { items } = await this._withKibanaIndex('bulk', {
refresh: 'wait_for',
body: objects.reduce((acc, object) => ([
...acc,
...objectToBulkRequest(object)
]), []),
});
const items = get(response, 'items', []);
const missingTypesCount = items.filter(item => {
const method = Object.keys(item)[0];
return get(item, `${method}.error.type`) === 'type_missing_exception';
}).length;
return items.map((response, i) => {
const {
error,
_id: responseId,
_version: version,
} = Object.values(response)[0];
const formatFallback = format === 'v5' && items.length > 0 && items.length === missingTypesCount;
if (formatFallback) {
return this.bulkCreate(objects, Object.assign({}, options, { format: 'v6' }));
}
return get(response, 'items', []).map((resp, i) => {
const method = Object.keys(resp)[0];
const { type, attributes } = objects[i];
return normalizeEsDoc(resp[method], {
id: resp[method]._id,
const {
id = responseId,
type,
attributes,
error: resp[method].error ? { message: get(resp[method], 'error.reason') } : undefined
});
} = objects[i];
if (error) {
return {
id,
type,
error: {
message: error.reason || JSON.stringify(error)
}
};
}
return {
id,
type,
version,
attributes
};
});
}
@ -101,27 +131,28 @@ export class SavedObjectsClient {
* @returns {promise}
*/
async delete(type, id) {
const response = await this._withKibanaIndex('deleteByQuery', {
body: createIdQuery({ type, id }),
refresh: 'wait_for'
const response = await this._withKibanaIndex('delete', {
id: this._generateEsId(type, id),
type: this._type,
refresh: 'wait_for',
});
if (get(response, 'deleted') === 0) {
if (response.found === false) {
throw Boom.notFound();
}
}
/**
* @param {object} [options={}]
* @property {string} options.type
* @property {string} options.search
* @property {string} options.searchFields - see Elasticsearch Simple Query String
* @property {string} [options.type]
* @property {string} [options.search]
* @property {Array<string>} [options.searchFields] - see Elasticsearch Simple Query String
* Query field argument for more information
* @property {integer} [options.page=1]
* @property {integer} [options.perPage=20]
* @property {string} options.sortField
* @property {string} options.sortOrder
* @property {array|string} options.fields
* @property {string} [options.sortField]
* @property {string} [options.sortOrder]
* @property {Array<string>} [options.fields]
* @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page }
*/
async find(options = {}) {
@ -136,23 +167,46 @@ export class SavedObjectsClient {
fields,
} = options;
if (searchFields && !Array.isArray(searchFields)) {
throw new TypeError('options.searchFields must be an array');
}
if (fields && !Array.isArray(fields)) {
throw new TypeError('options.searchFields must be an array');
}
const esOptions = {
_source: includedFields(type, fields),
size: perPage,
from: perPage * (page - 1),
body: createFindQuery(this._mappings, { search, searchFields, type, sortField, sortOrder })
_source: includedFields(type, fields),
body: {
version: true,
...getSearchDsl(this._mappings, {
search,
searchFields,
type,
sortField,
sortOrder
})
}
};
const response = await this._withKibanaIndex('search', esOptions);
return {
saved_objects: get(response, 'hits.hits', []).map(hit => {
return normalizeEsDoc(hit);
}),
total: get(response, 'hits.total', 0),
page,
per_page: perPage,
page
total: response.hits.total,
saved_objects: response.hits.hits.map(hit => {
const type = hit._source.type;
return {
id: trimIdPrefix(hit._id, type),
type,
version: hit._version,
attributes: hit._source[type],
};
}),
};
}
@ -173,24 +227,33 @@ export class SavedObjectsClient {
return { saved_objects: [] };
}
const docs = objects.reduce((acc, { type, id }) => {
return [...acc, {}, createIdQuery({ type, id })];
}, []);
const response = await this._withKibanaIndex('msearch', { body: docs });
const responses = get(response, 'responses', []);
const response = await this._withKibanaIndex('mget', {
body: {
docs: objects.map(object => ({
_id: this._generateEsId(object.type, object.id),
_type: this._type,
}))
}
});
return {
saved_objects: responses.map((r, i) => {
const [hit] = get(r, 'hits.hits', []);
saved_objects: response.docs.map((doc, i) => {
const { id, type } = objects[i];
if (!hit) {
return Object.assign({}, objects[i], {
if (doc.found === false) {
return {
id,
type,
error: { statusCode: 404, message: 'Not found' }
});
};
}
return normalizeEsDoc(hit, objects[i]);
return {
id,
type,
version: doc._version,
attributes: doc._source[type]
};
})
};
}
@ -203,14 +266,17 @@ export class SavedObjectsClient {
* @returns {promise} - { id, type, version, attributes }
*/
async get(type, id) {
const response = await this._withKibanaIndex('search', { body: createIdQuery({ type, id }) });
const [hit] = get(response, 'hits.hits', []);
const response = await this._withKibanaIndex('get', {
id: this._generateEsId(type, id),
type: this._type,
});
if (!hit) {
throw Boom.notFound();
}
return normalizeEsDoc(hit);
return {
id,
type,
version: response._version,
attributes: response._source[type]
};
}
/**
@ -223,41 +289,24 @@ export class SavedObjectsClient {
* @returns {promise}
*/
async update(type, id, attributes, options = {}) {
const response = await this._withKibanaIndexAndMappingFallback('update', {
id,
type,
const response = await this._withKibanaIndex('update', {
id: this._generateEsId(type, id),
type: this._type,
version: options.version,
refresh: 'wait_for',
body: {
doc: attributes
}
}, {
type: V6_TYPE,
id: `${type}:${id}`,
body: {
doc: {
[type]: attributes
}
}
},
});
return normalizeEsDoc(response, { id, type, attributes });
}
_withKibanaIndexAndMappingFallback(method, params, fallbackParams) {
const fallbacks = {
'create': ['type_missing_exception'],
'index': ['type_missing_exception'],
'update': ['document_missing_exception']
return {
id,
type,
version: response._version,
attributes
};
return this._withKibanaIndex(method, params).catch(err => {
if (get(fallbacks, method, []).includes(get(err, 'data.type'))) {
return this._withKibanaIndex(method, Object.assign({}, params, fallbackParams));
}
throw err;
});
}
async _withKibanaIndex(method, params) {
@ -270,4 +319,8 @@ export class SavedObjectsClient {
throw handleEsError(err);
}
}
_generateEsId(type, id) {
return `${type}:${id || uuid.v1()}`;
}
}

View file

@ -99,7 +99,7 @@ describe('GET /api/saved_objects/{type?}', () => {
expect(savedObjectsClient.find.calledOnce).to.be(true);
const options = savedObjectsClient.find.getCall(0).args[0];
expect(options).to.eql({ perPage: 20, page: 1, searchFields: 'title' });
expect(options).to.eql({ perPage: 20, page: 1, searchFields: ['title'] });
});
it('accepts the query parameter fields as a string', async () => {
@ -113,7 +113,7 @@ describe('GET /api/saved_objects/{type?}', () => {
expect(savedObjectsClient.find.calledOnce).to.be(true);
const options = savedObjectsClient.find.getCall(0).args[0];
expect(options).to.eql({ perPage: 20, page: 1, fields: 'title' });
expect(options).to.eql({ perPage: 20, page: 1, fields: ['title'] });
});
it('accepts the query parameter fields as an array', async () => {

View file

@ -9,7 +9,7 @@ export const createCreateRoute = (prereqs) => {
validate: {
query: Joi.object().keys({
overwrite: Joi.boolean().default(false)
}),
}).default(),
params: Joi.object().keys({
type: Joi.string().required(),
id: Joi.string()

View file

@ -9,15 +9,15 @@ export const createFindRoute = (prereqs) => ({
validate: {
params: Joi.object().keys({
type: Joi.string()
}),
}).default(),
query: Joi.object().keys({
per_page: Joi.number().min(0).default(20),
page: Joi.number().min(0).default(1),
type: Joi.string(),
search: Joi.string().allow('').optional(),
search_fields: [Joi.string(), Joi.array().items(Joi.string())],
fields: [Joi.string(), Joi.array().items(Joi.string())]
})
search_fields: Joi.array().items(Joi.string()).single(),
fields: Joi.array().items(Joi.string()).single()
}).default()
},
handler(request, reply) {
const options = keysToCamelCaseShallow(request.query);

View file

@ -29,7 +29,7 @@ export function savedObjectsMixin(kbnServer, server) {
server.decorate('server', 'savedObjectsClientFactory', ({ callCluster }) => {
return new SavedObjectsClient(
server.config().get('kibana.index'),
kbnServer.uiExports.mappings.getCombined(),
server.getKibanaIndexMappingsDsl(),
callCluster
);
});

View file

@ -1,40 +0,0 @@
import expect from 'expect.js';
import { has } from 'lodash';
import { MappingsCollection } from '../ui_mappings';
describe('UiExports', function () {
describe('MappingsCollection', function () {
let mappingsCollection;
beforeEach(() => {
mappingsCollection = new MappingsCollection();
});
it('provides default mappings', function () {
expect(mappingsCollection.getCombined()).to.be.an('object');
});
it('registers new mappings', () => {
mappingsCollection.register({
foo: {
'properties': {
'bar': {
'type': 'text'
}
}
}
});
const mappings = mappingsCollection.getCombined();
expect(has(mappings, 'foo.properties.bar')).to.be(true);
});
it('throws and includes the plugin id in the mapping conflict message', () => {
const mappings = { foo: 'bar' };
const plugin = { plugin: 'abc123' };
mappingsCollection.register(mappings, plugin);
expect(mappingsCollection.register).withArgs(mappings, plugin).to.throwException(/abc123/);
});
});
});

View file

@ -14,7 +14,8 @@ import { fieldFormatsMixin } from './field_formats_mixin';
export default async (kbnServer, server, config) => {
const uiExports = kbnServer.uiExports = new UiExports({
urlBasePath: config.get('server.basePath')
urlBasePath: config.get('server.basePath'),
kibanaIndexMappings: kbnServer.mappings,
});
await kbnServer.mixin(uiSettingsMixin);

View file

@ -27,7 +27,7 @@
<div class="hintbox" ng-if="!indexedFields.length">
<p>
<i class="fa fa-danger text-danger"></i>
<strong>No Compatible Fields:</strong> The "{{ vis.indexPattern.id }}" index pattern does not contain any of the following field types: {{ agg.type.params.byName.field.filterFieldTypes | commaList:false }}
<strong>No Compatible Fields:</strong> The "{{ vis.indexPattern.title }}" index pattern does not contain any of the following field types: {{ agg.type.params.byName.field.filterFieldTypes | commaList:false }}
</p>
</div>

View file

@ -11,7 +11,6 @@ const URL_LIMIT_WARN_WITHIN = 1000;
export function initAngularApi(chrome, internals) {
chrome.getFirstPathSegment = _.noop;
chrome.getBreadcrumbs = _.noop;
chrome.setupAngular = function () {
const kibana = uiModules.get('kibana');
@ -49,19 +48,6 @@ export function initAngularApi(chrome, internals) {
return $location.path().split('/')[1];
};
chrome.getBreadcrumbs = () => {
const path = $location.path();
let length = path.length - 1;
// trim trailing slash
if (path.charAt(length) === '/') {
length--;
}
return path.substr(1, length)
.split('/');
};
const notify = new Notifier();
const urlOverflow = Private(UrlOverflowServiceProvider);
const check = () => {

View file

@ -89,7 +89,7 @@ export class SavedObjectLoader {
* @param size
* @returns {Promise}
*/
find(search, size = 100) {
find(search = '', size = 100) {
return this.savedObjectsClient.find(
{
type: this.lowercaseType,

View file

@ -458,7 +458,7 @@ describe('index pattern', function () {
expect(notif.content).to.match(MARKDOWN_LINK_RE);
const [,text,url] = notif.content.match(MARKDOWN_LINK_RE);
expect(text).to.contain(indexPattern.id);
expect(text).to.contain(indexPattern.title);
expect(url).to.contain(indexPattern.id);
expect(url).to.contain('management/kibana/indices');
});

View file

@ -26,7 +26,7 @@ export function IndexPatternsFieldProvider(Private, shortDotsFilter, $rootScope,
notify.error(
'Unknown field type "' + spec.type + '"' +
' for field "' + spec.name + '"' +
' in indexPattern "' + indexPattern.id + '"'
' in indexPattern "' + indexPattern.title + '"'
);
}

View file

@ -93,11 +93,15 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
// give index pattern all of the values in _source
_.assign(indexPattern, response._source);
if (!indexPattern.title) {
indexPattern.title = indexPattern.id;
}
if (indexPattern.isUnsupportedTimePattern()) {
if (!isUserAwareOfUnsupportedTimePattern(indexPattern)) {
const warning = (
'Support for time-intervals has been removed. ' +
`View the ["${indexPattern.id}" index pattern in management](` +
`View the ["${indexPattern.title}" index pattern in management](` +
kbnUrl.getRouteHref(indexPattern, 'edit') +
') for more information.'
);
@ -403,7 +407,7 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
});
}
async save() {
save() {
return savedObjectsClient.update(type, this.id, this.prepBody())
.then(({ id }) => setId(this, id));
}

View file

@ -1,61 +0,0 @@
import expect from 'expect.js';
import { getBreadCrumbUrls } from '../bread_crumbs/bread_crumb_urls';
describe('getBreadCrumbUrls', function () {
it('returns urls for the breadcrumbs', function () {
const breadCrumbUrls = getBreadCrumbUrls(
['path1', 'path2', 'a', 'longlonglonglong'],
'http://test.com/path1/path2/a/longlonglonglong');
expect(breadCrumbUrls.length).to.equal(4);
expect(breadCrumbUrls[0].url).to.equal('http://test.com/path1');
expect(breadCrumbUrls[0].title).to.equal('Path 1');
expect(breadCrumbUrls[1].url).to.equal('http://test.com/path1/path2');
expect(breadCrumbUrls[1].title).to.equal('Path 2');
expect(breadCrumbUrls[2].url).to.equal('http://test.com/path1/path2/a');
expect(breadCrumbUrls[2].title).to.equal('A');
expect(breadCrumbUrls[3].url).to.equal('http://test.com/path1/path2/a/longlonglonglong');
expect(breadCrumbUrls[3].title).to.equal('Longlonglonglong');
});
it('is case insensitive', function () {
const breadCrumbUrls = getBreadCrumbUrls(['paTh1', 'path2'], 'http://TEST.com/paTh1/path2');
expect(breadCrumbUrls.length).to.equal(2);
expect(breadCrumbUrls[0].url).to.equal('http://TEST.com/paTh1');
expect(breadCrumbUrls[0].path).to.equal('paTh1');
expect(breadCrumbUrls[0].title).to.equal('Pa Th 1');
expect(breadCrumbUrls[1].url).to.equal('http://TEST.com/paTh1/path2');
expect(breadCrumbUrls[1].title).to.equal('Path 2');
});
it('handles no breadcrumbs case', function () {
const breadCrumbUrls = getBreadCrumbUrls([], 'http://test.com');
expect(breadCrumbUrls.length).to.equal(0);
});
it('handles spaces in breadcrumbs', function () {
const breadCrumbUrls = getBreadCrumbUrls(
['something', 'somethingElse', 'snake_case', 'longLongLongLong'],
'http://test.com/something/somethingElse/snake_case/longLongLongLong');
expect(breadCrumbUrls.length).to.equal(4);
expect(breadCrumbUrls[0].url).to.equal('http://test.com/something');
expect(breadCrumbUrls[0].title).to.equal('Something');
expect(breadCrumbUrls[1].url).to.equal('http://test.com/something/somethingElse');
expect(breadCrumbUrls[1].path).to.equal('somethingElse');
expect(breadCrumbUrls[1].title).to.equal('Something Else');
expect(breadCrumbUrls[2].url).to.equal('http://test.com/something/somethingElse/snake_case');
expect(breadCrumbUrls[2].path).to.equal('snake_case');
expect(breadCrumbUrls[2].title).to.equal('Snake Case');
expect(breadCrumbUrls[3].url).to.equal('http://test.com/something/somethingElse/snake_case/longLongLongLong');
expect(breadCrumbUrls[3].title).to.equal('Long Long Long Long');
});
});

View file

@ -1,27 +0,0 @@
import _ from 'lodash';
/**
* @typedef BreadCrumbUrl {Object}
* @property title {String} the display title for the breadcrumb
* @property path {String} the subdirectory for this particular breadcrumb
* @property url {String} a url for the breadcrumb
*/
/**
*
* @param {Array.<String>} breadcrumbs An array of breadcrumbs for the given url.
* @param {String} url The current url that the breadcrumbs have been generated for
* @returns {Array.<BreadCrumbUrl> An array comprised of objects that
* will contain both the url for the given breadcrumb, as well as the breadcrumb the url
* was generated for.
*/
export function getBreadCrumbUrls(breadcrumbs, url) {
// the url should not have a slash on the end or else the route will not be properly built
const urlBase = url.replace(/\/+$/, '').replace(breadcrumbs.join('/'), '');
return breadcrumbs.map((path, index) => {
return {
path: path,
title: _.startCase(path),
url: urlBase + breadcrumbs.slice(0, index + 1).join('/')
};
});
}

View file

@ -1,23 +1,23 @@
<div class="kuiLocalBreadcrumbs" data-test-subj="breadcrumbs">
<div
class="kuiLocalBreadcrumb"
ng-if="useLinks && (!omitPages || !omitPages.includes(breadcrumb.path))"
ng-if="useLinks"
ng-repeat="breadcrumb in breadcrumbs"
>
<a
class="kuiLocalBreadcrumb__link"
href="{{breadcrumb.url}}"
href="{{ breadcrumb.href }}"
>
{{breadcrumb.title}}
{{ breadcrumb.display }}
</a>
</div>
<div
class="kuiLocalBreadcrumb"
ng-if="!useLinks && (!omitPages || !omitPages.includes(breadcrumb.path))"
ng-if="!useLinks"
ng-repeat="breadcrumb in breadcrumbs"
>
{{ breadcrumb.title }}
{{ breadcrumb.display }}
</div>
<div

View file

@ -1,11 +1,10 @@
import _ from 'lodash';
import chrome from 'ui/chrome/chrome';
import breadCrumbsTemplate from './bread_crumbs.html';
import { getBreadCrumbUrls } from './bread_crumb_urls';
import { uiModules } from 'ui/modules';
import uiRouter from 'ui/routes';
const module = uiModules.get('kibana');
module.directive('breadCrumbs', function ($location) {
module.directive('breadCrumbs', function () {
return {
restrict: 'E',
replace: true,
@ -30,20 +29,29 @@ module.directive('breadCrumbs', function ($location) {
},
template: breadCrumbsTemplate,
controller: function ($scope) {
const breadcrumbs = chrome.getBreadcrumbs();
if ($scope.useLinks) {
const url = '#' + $location.path();
$scope.breadcrumbs = getBreadCrumbUrls(breadcrumbs, url);
} else {
$scope.breadcrumbs = breadcrumbs.map(path => ({
path: path,
title: _.startCase(path)
}));
function omitPagesFilter(crumb) {
return (
!$scope.omitPages ||
!$scope.omitPages.includes(crumb.id)
);
}
if ($scope.omitCurrentPage === true) {
$scope.breadcrumbs.pop();
function omitCurrentPageFilter(crumb) {
return !($scope.omitCurrentPage && crumb.current);
}
$scope.$watchMulti([
'[]omitPages',
'omitCurrentPage'
], function getBreadcrumbs() {
$scope.breadcrumbs = (
uiRouter
.getBreadcrumbs()
.filter(omitPagesFilter)
.filter(omitCurrentPageFilter)
);
});
}
};
});

View file

@ -0,0 +1,22 @@
import { trim, startCase } from 'lodash';
/**
* Take a path (from $location.path() usually) and parse
* it's segments into a list of breadcrumbs
*
* @param {string} path
* @return {Array<Breadcrumb>}
*/
export function parsePathToBreadcrumbs(path) {
return trim(path, '/')
.split('/')
.reduce((acc, id, i, parts) => [
...acc,
{
id,
display: startCase(id),
href: i === 0 ? `#/${id}` : `${acc[i - 1].href}/${id}`,
current: i === (parts.length - 1)
}
], []);
}

View file

@ -1,7 +1,8 @@
import _ from 'lodash';
import { defaultsDeep, wrap } from 'lodash';
import { wrapRouteWithPrep } from './wrap_route_with_prep';
import { RouteSetupManager } from './route_setup_manager';
import { parsePathToBreadcrumbs } from './breadcrumbs';
// eslint-disable-next-line kibana-custom/no-default-export
export default function RouteManager() {
@ -16,18 +17,17 @@ export default function RouteManager() {
const path = args[0];
const route = args[1] || {};
// merge in any defaults
defaults.forEach(function (args) {
if (args[0].test(path)) {
_.merge(route, args[1]);
defaults.forEach(def => {
if (def.regex.test(path)) {
defaultsDeep(route, def.value);
}
});
if (route.reloadOnSearch === void 0) {
if (route.reloadOnSearch == null) {
route.reloadOnSearch = false;
}
if (route.requireDefaultIndex === void 0) {
if (route.requireDefaultIndex == null) {
route.requireDefaultIndex = false;
}
@ -41,14 +41,22 @@ export default function RouteManager() {
}
};
self.run = function ($location, $route, $injector) {
self.getBreadcrumbs = () => {
const breadcrumbs = parsePathToBreadcrumbs($location.path());
const map = $route.current.mapBreadcrumbs;
return map ? $injector.invoke(map, null, { breadcrumbs }) : breadcrumbs;
};
};
const wrapSetupAndChain = (fn, ...args) => {
fn.apply(setup, args);
return this;
};
this.addSetupWork = _.wrap(setup.addSetupWork, wrapSetupAndChain);
this.afterSetupWork = _.wrap(setup.afterSetupWork, wrapSetupAndChain);
this.afterWork = _.wrap(setup.afterWork, wrapSetupAndChain);
this.addSetupWork = wrap(setup.addSetupWork, wrapSetupAndChain);
this.afterSetupWork = wrap(setup.afterSetupWork, wrapSetupAndChain);
this.afterWork = wrap(setup.afterWork, wrapSetupAndChain);
self.when = function (path, route) {
when.push([path, route]);
@ -57,8 +65,8 @@ export default function RouteManager() {
// before attaching the routes to the routeProvider, test the RE
// against the .when() path and add/override the resolves if there is a match
self.defaults = function (RE, def) {
defaults.push([RE, def]);
self.defaults = function (regex, value) {
defaults.push({ regex, value });
return self;
};
@ -67,5 +75,10 @@ export default function RouteManager() {
return self;
};
self.getBreadcrumbs = function () {
// overwritten in self.run();
return [];
};
self.RouteManager = RouteManager;
}

View file

@ -5,12 +5,17 @@ import { WAIT_FOR_URL_CHANGE_TOKEN } from './route_setup_manager';
const defaultRouteManager = new RouteManager();
// eslint-disable-next-line kibana-custom/no-default-export
export default {
...defaultRouteManager,
WAIT_FOR_URL_CHANGE_TOKEN,
enable() {
uiModules
.get('kibana', ['ngRoute'])
.config(defaultRouteManager.config);
export default Object.create(defaultRouteManager, {
WAIT_FOR_URL_CHANGE_TOKEN: {
value: WAIT_FOR_URL_CHANGE_TOKEN
},
enable: {
value() {
uiModules
.get('kibana', ['ngRoute'])
.config(defaultRouteManager.config)
.run(defaultRouteManager.run);
}
}
};
});

View file

@ -298,7 +298,7 @@ describe('SavedObjectsClient', () => {
expect($http.calledOnce).to.be(true);
const options = $http.getCall(0).args[0];
expect(options.url).to.eql(`${basePath}/api/saved_objects/index-pattern?type=index-pattern&invalid=true`);
expect(options.url).to.eql(`${basePath}/api/saved_objects/?type=index-pattern&invalid=true`);
});
it('accepts fields', () => {

View file

@ -17,7 +17,7 @@ export function findObjectByTitle(savedObjectsClient, type, title) {
type,
perPage: 10,
search: `"${title}"`,
searchFields: 'title',
searchFields: ['title'],
fields: ['title']
}).then(response => {
const match = find(response.savedObjects, (obj) => {

View file

@ -74,7 +74,7 @@ export class SavedObjectsClient {
* @returns {promise} - { savedObjects: [ SavedObject({ id, type, version, attributes }) ]}
*/
find(options = {}) {
const url = this._getUrl([options.type], keysToSnakeCaseShallow(options));
const url = this._getUrl([], keysToSnakeCaseShallow(options));
return this._request('GET', url).then(resp => {
resp.saved_objects = resp.saved_objects.map(d => this.createSavedObject(d));

View file

@ -3,10 +3,9 @@ import minimatch from 'minimatch';
import UiAppCollection from './ui_app_collection';
import UiNavLinkCollection from './ui_nav_link_collection';
import { MappingsCollection } from './ui_mappings';
export default class UiExports {
constructor({ urlBasePath }) {
constructor({ urlBasePath, kibanaIndexMappings }) {
this.navLinks = new UiNavLinkCollection(this);
this.apps = new UiAppCollection(this);
this.aliases = {
@ -29,7 +28,7 @@ export default class UiExports {
this.bundleProviders = [];
this.defaultInjectedVars = {};
this.injectedVarsReplacers = [];
this.mappings = new MappingsCollection();
this.kibanaIndexMappings = kibanaIndexMappings;
}
consumePlugin(plugin) {
@ -146,7 +145,7 @@ export default class UiExports {
case 'mappings':
return (plugin, mappings) => {
this.mappings.register(mappings, { plugin: plugin.id });
this.kibanaIndexMappings.addRootProperties(mappings, { plugin: plugin.id });
};
case 'replaceInjectedVars':

View file

@ -1,37 +0,0 @@
import _ from 'lodash';
class MappingsCollection {
constructor() {
this._defaultMappings = {
'_default_': {
'dynamic': 'strict'
},
config: {
dynamic: true,
properties: {
buildNum: {
type: 'keyword'
}
}
},
};
this._currentMappings = _.cloneDeep(this._defaultMappings);
}
getCombined = () => {
return this._currentMappings;
}
register = (newMappings, options = {}) => {
Object.keys(this._currentMappings).forEach(key => {
if (newMappings.hasOwnProperty(key)) {
const pluginPartial = options.plugin ? `registered by plugin ${options.plugin} ` : '';
throw new Error(`Mappings for ${key} ${pluginPartial}have already been defined`);
return;
}
});
Object.assign(this._currentMappings, newMappings);
}
}
export { MappingsCollection };

View file

@ -74,7 +74,6 @@ module.exports = function (grunt) {
...stdDevArgs,
'--dev',
'--no-base-path',
'--no-ssl',
'--optimize.enabled=false',
'--elasticsearch.url=' + format(esTestServerUrlParts),
'--server.port=' + kibanaTestServerUrlParts.port,

View file

@ -13,6 +13,8 @@ export default async function ({ readConfigFile }) {
services: {
es: commonConfig.get('services.es'),
esArchiver: commonConfig.get('services.esArchiver'),
kibanaIndex: commonConfig.get('services.kibanaIndex'),
retry: commonConfig.get('services.retry'),
supertest: SupertestProvider,
chance: ChanceProvider,
},

View file

@ -4,242 +4,241 @@
"index": ".kibana",
"settings": {
"index": {
"mapping": {
"single_type": "false"
},
"number_of_shards": "1",
"mapper": {
"dynamic": "false"
},
"mapper.dynamic": false,
"number_of_replicas": "1"
}
},
"mappings": {
"url": {
"dynamic": "strict",
"doc": {
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
"type": {
"type": "keyword"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"description": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"_default_": {
"dynamic": "strict"
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"pause": {
"type": "boolean"
"timelion_other_interval": {
"type": "keyword"
},
"section": {
"timelion_rows": {
"type": "integer"
},
"value": {
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"timeRestore": {
"type": "boolean"
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"timeTo": {
"type": "keyword"
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"title": {
"type": "text"
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"uiStateJSON": {
"type": "text"
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"version": {
"type": "integer"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
}
}
}

View file

@ -1,7 +1,9 @@
import {
KibanaServerProvider,
KibanaIndexProvider,
EsProvider,
EsArchiverProvider,
RetryProvider,
} from './services';
import { esTestServerUrlParts } from '../es_test_server_url_parts';
@ -15,6 +17,8 @@ export default function () {
},
services: {
kibanaServer: KibanaServerProvider,
kibanaIndex: KibanaIndexProvider,
retry: RetryProvider,
es: EsProvider,
esArchiver: EsArchiverProvider,
}

View file

@ -1,3 +1,5 @@
export { KibanaServerProvider } from './kibana_server';
export { KibanaIndexProvider } from './kibana_index';
export { EsProvider } from './es';
export { EsArchiverProvider } from './es_archiver';
export { RetryProvider } from './retry';

View file

@ -0,0 +1,21 @@
export async function KibanaIndexProvider({ getService }) {
const retry = getService('retry');
const es = getService('es');
const KIBANA_INDEX_NAME = '.kibana';
const esIndex = await retry.try(async () => {
return await es.indices.get({
index: KIBANA_INDEX_NAME
});
});
return new class KibanaIndex {
getName() {
return KIBANA_INDEX_NAME;
}
getMappingsDsl() {
return Object.values(esIndex)[0].mappings;
}
};
}

View file

@ -6,18 +6,19 @@ import { KibanaServerStatus } from './status';
import { KibanaServerUiSettings } from './ui_settings';
import { KibanaServerVersion } from './version';
export function KibanaServerProvider({ getService }) {
export async function KibanaServerProvider({ getService }) {
const log = getService('log');
const config = getService('config');
const lifecycle = getService('lifecycle');
const es = getService('es');
const kibanaIndex = await getService('kibanaIndex').init();
class KibanaServer {
return new class KibanaServer {
constructor() {
const url = formatUrl(config.get('servers.kibana'));
this.status = new KibanaServerStatus(url);
this.version = new KibanaServerVersion(this.status);
this.uiSettings = new KibanaServerUiSettings(log, es, this.version);
this.uiSettings = new KibanaServerUiSettings(log, es, kibanaIndex, this.version);
lifecycle.on('beforeEachTest', async () => {
await this.waitForStabilization();
@ -63,7 +64,5 @@ export function KibanaServerProvider({ getService }) {
const docState = exists ? 'exists' : `doesn't exist`;
throw new Error(`Kibana never stabilized: config doc ${docState} and status is ${state}`);
}
}
return new KibanaServer();
};
}

View file

@ -1,46 +1,49 @@
import { get } from 'lodash';
import toPath from 'lodash/internal/toPath';
import { SavedObjectsClient } from '../../../../src/server/saved_objects';
function createCallCluster(es) {
return function callCluster(method, params) {
const path = toPath(method);
const contextPath = path.slice(0, -1);
const action = get(es, path);
const context = contextPath.length ? get(es, contextPath) : es;
return action.call(context, params);
};
}
export class KibanaServerUiSettings {
constructor(log, es, kibanaVersion) {
this.es = es;
this.log = log;
this.kibanaVersion = kibanaVersion;
constructor(log, es, kibanaIndex, kibanaVersion) {
this._log = log;
this._kibanaVersion = kibanaVersion;
this._savedObjectsClient = new SavedObjectsClient(
kibanaIndex.getName(),
kibanaIndex.getMappingsDsl(),
createCallCluster(es)
);
}
async _docParams() {
const { kibanaVersion } = this;
return {
index: '.kibana',
type: 'config',
id: await kibanaVersion.get()
};
async _id() {
return await this._kibanaVersion.get();
}
async existInEs() {
const { es } = this;
return await es.exists(await this._docParams());
}
async _read() {
const { log, es } = this;
try {
const doc = await es.get(await this._docParams());
log.verbose('Fetched kibana config doc', doc);
return doc;
} catch (err) {
log.debug('Failed to fetch kibana config doc', err.message);
return;
}
return !!(await this._read());
}
/*
** Gets defaultIndex from the config doc.
*/
async getDefaultIndex() {
const { log } = this;
const doc = await this._read();
const defaultIndex = get(doc, ['_source', 'defaultIndex']);
log.verbose('uiSettings.defaultIndex: %j', defaultIndex);
if (!doc) {
throw new TypeError('Failed to fetch kibana config doc');
}
const defaultIndex = doc.attributes.defaultIndex;
this._log.verbose('uiSettings.defaultIndex: %j', defaultIndex);
return defaultIndex;
}
@ -62,12 +65,10 @@ export class KibanaServerUiSettings {
}
async replace(doc) {
const { log, es } = this;
log.debug('updating kibana config doc: %j', doc);
await es.index({
...(await this._docParams()),
refresh: 'wait_for',
body: doc,
this._log.debug('replacing kibana config doc: %j', doc);
await this._savedObjectsClient.create('config', { doc }, {
id: await this._id(),
overwrite: true,
});
}
@ -75,13 +76,19 @@ export class KibanaServerUiSettings {
* Add fields to the config doc (like setting timezone and defaultIndex)
* @return {Promise} A promise that is resolved when elasticsearch has a response
*/
async update(doc) {
const { log, es } = this;
log.debug('updating kibana config doc: %j', doc);
await es.update({
...(await this._docParams()),
refresh: 'wait_for',
body: { doc, upsert: doc },
});
async update(updates) {
this._log.debug('applying update to kibana config: %j', updates);
await this._savedObjectsClient.update('config', await this._id(), updates);
}
async _read() {
try {
const doc = await this._savedObjectsClient.get('config', await this._id());
this._log.verbose('Fetched kibana config doc', doc);
return doc;
} catch (err) {
this._log.debug('Failed to fetch kibana config doc', err.message);
return;
}
}
}

View file

@ -16,7 +16,6 @@ import {
RemoteProvider,
FilterBarProvider,
FindProvider,
RetryProvider,
TestSubjectsProvider,
DocTableProvider,
ScreenshotsProvider,
@ -53,10 +52,11 @@ export default async function ({ readConfigFile }) {
es: commonConfig.get('services.es'),
esArchiver: commonConfig.get('services.esArchiver'),
kibanaServer: commonConfig.get('services.kibanaServer'),
kibanaIndex: commonConfig.get('services.kibanaIndex'),
retry: commonConfig.get('services.retry'),
remote: RemoteProvider,
filterBar: FilterBarProvider,
find: FindProvider,
retry: RetryProvider,
testSubjects: TestSubjectsProvider,
docTable: DocTableProvider,
screenshots: ScreenshotsProvider,

View file

@ -4,244 +4,243 @@
"index": ".kibana",
"settings": {
"index": {
"mapping": {
"single_type": "false"
},
"number_of_shards": "1",
"mapper": {
"dynamic": "false"
},
"mapper.dynamic": false,
"number_of_replicas": "1"
}
},
"mappings": {
"index-pattern": {
"dynamic": "strict",
"doc": {
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"index-pattern": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"fieldFormatMap": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
},
"fields": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
},
"intervalName": {
"type": "keyword"
},
"pause": {
"notExpandable": {
"type": "boolean"
},
"section": {
"type": "integer"
"sourceFilters": {
"type": "text"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"title": {
"type": "text"
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"uiStateJSON": {
"type": "text"
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"version": {
"type": "integer"
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"visState": {
"type": "text"
}
}
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
}
}
},
"_default_": {
"dynamic": "strict"
}
}
}

View file

@ -4,242 +4,241 @@
"index": ".kibana",
"settings": {
"index": {
"mapping": {
"single_type": "false"
},
"number_of_shards": "1",
"mapper": {
"dynamic": "false"
},
"mapper.dynamic": false,
"number_of_replicas": "1"
}
},
"mappings": {
"url": {
"dynamic": "strict",
"doc": {
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
"type": {
"type": "keyword"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
}
}
},
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"index-pattern": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"fieldFormatMap": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"pause": {
"notExpandable": {
"type": "boolean"
},
"section": {
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"value": {
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"config": {
"dynamic": "true",
"properties": {
"searchSourceJSON": {
"buildNum": {
"type": "keyword"
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"_default_": {
"dynamic": "strict"
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"search": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
}

View file

@ -4,249 +4,248 @@
"index": ".kibana",
"settings": {
"index": {
"mapping": {
"single_type": "false"
},
"number_of_shards": "1",
"mapper": {
"dynamic": "false"
},
"mapper.dynamic": false,
"number_of_replicas": "1"
}
},
"mappings": {
"timelion-sheet": {
"dynamic": "strict",
"doc": {
"properties": {
"description": {
"type": "text"
"type": {
"type": "keyword"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"description": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"_default_": {
"dynamic": "strict"
},
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"pause": {
"type": "boolean"
"timelion_other_interval": {
"type": "keyword"
},
"section": {
"timelion_rows": {
"type": "integer"
},
"value": {
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"timeRestore": {
"type": "boolean"
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timeTo": {
"type": "keyword"
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"title": {
"type": "text"
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"uiStateJSON": {
"type": "text"
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"version": {
"type": "integer"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"dateFormat:tz": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
},
"dateFormat:tz": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}

View file

@ -4,242 +4,241 @@
"index": ".kibana",
"settings": {
"index": {
"mapping": {
"single_type": "false"
},
"number_of_shards": "1",
"mapper": {
"dynamic": "false"
},
"mapper.dynamic": false,
"number_of_replicas": "1"
}
},
"mappings": {
"server": {
"dynamic": "strict",
"doc": {
"properties": {
"uuid": {
"type": {
"type": "keyword"
}
}
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"type": "text"
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"search": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"_default_": {
"dynamic": "strict"
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"columns": {
"type": "keyword"
},
"pause": {
"type": "boolean"
"description": {
"type": "text"
},
"section": {
"hits": {
"type": "integer"
},
"value": {
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"visualization": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"timelion_columns": {
"type": "integer"
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"timelion_interval": {
"type": "keyword"
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
}
}
}

View file

@ -4,240 +4,239 @@
"index": ".kibana",
"settings": {
"index": {
"mapping": {
"single_type": "false"
},
"number_of_shards": "1",
"mapper": {
"dynamic": "false"
},
"mapper.dynamic": false,
"number_of_replicas": "1"
}
},
"mappings": {
"visualization": {
"dynamic": "strict",
"doc": {
"properties": {
"description": {
"type": "text"
"type": {
"type": "keyword"
},
"kibanaSavedObjectMeta": {
"visualization": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"description": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"pause": {
"type": "boolean"
"title": {
"type": "text"
},
"section": {
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"_default_": {
"dynamic": "strict"
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"visState": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"title": {
"type": "text"
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"version": {
"type": "integer"
}
}
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"accessDate": {
"type": "date"
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"createDate": {
"type": "date"
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
}

View file

@ -4,260 +4,259 @@
"index": ".kibana",
"settings": {
"index": {
"mapping": {
"single_type": "false"
},
"number_of_shards": "1",
"mapper": {
"dynamic": "false"
},
"mapper.dynamic": false,
"number_of_replicas": "1"
}
},
"mappings": {
"index-pattern": {
"dynamic": "strict",
"doc": {
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"index-pattern": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"fieldFormatMap": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"pause": {
"notExpandable": {
"type": "boolean"
},
"section": {
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"value": {
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"search": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"_default_": {
"dynamic": "strict"
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
},
"dateFormat:tz": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"defaultIndex": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
},
"dateFormat:tz": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"defaultIndex": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"searchSourceJSON": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
}

View file

@ -1,4 +1,3 @@
export { RetryProvider } from './retry';
export { FilterBarProvider } from './filter_bar';
export { FindProvider } from './find';
export { TestSubjectsProvider } from './test_subjects';