Skip to content
This repository has been archived by the owner on Mar 31, 2024. It is now read-only.

Commit

Permalink
[Saved Objects] Create mappings lib within core/server/saved_objects (e…
Browse files Browse the repository at this point in the history
…lastic#21093)

This commit moves the large majority of the mappings library previously in src/server/mappings into src/core/server/saved_objects/mappings, since mappings should be owned by saved_objects. The kibanaIndexMappingsMixin remains in src/server, but is moved into src/server/saved_objects/mappings, keeping the same folder structure for consistency.
  • Loading branch information
archanid authored Aug 14, 2018
1 parent fee34f7 commit 02067a0
Show file tree
Hide file tree
Showing 27 changed files with 396 additions and 293 deletions.
6 changes: 6 additions & 0 deletions src/core/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@ Platform Server Modules
Http Server
-----------
TODO: explain

Saved Objects Service
-----------

Elasticsearch Service
-----------
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`saved objects index mapping constructor includes the pluginId from the extension in the _ error message if defined 1`] = `"Property names _foo registered by plugin abc123 are not allowed to start with an underscore (_)"`;

exports[`saved objects index mapping constructor throws if any of the new properties start with _ 1`] = `"Property names _foo are not allowed to start with an underscore (_)"`;
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
* under the License.
*/

export {
kibanaIndexMappingsMixin
} from './kibana_index_mappings_mixin';
export { IndexMappings } from './index_mappings';

export {
getTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { getRootType } from './lib';

const chance = new Chance();

describe('server/mapping/index_mapping', function () {
describe('saved objects index mapping', () => {
describe('constructor', () => {
it('initializes with a default mapping when no args', () => {
const mapping = new IndexMappings();
Expand All @@ -38,67 +38,69 @@ describe('server/mapping/index_mapping', function () {
const mapping = new IndexMappings({
foobar: {
dynamic: false,
properties: {}
}
properties: {},
},
});

expect(mapping.getDsl()).toEqual({
foobar: {
dynamic: false,
properties: {}
}
properties: {},
},
});
});

it('throws if root type is of type=anything-but-object', () => {
expect(() => {
// tslint:disable-next-line:no-unused-expression
new IndexMappings({
root: {
type: chance.pickone(['string', 'keyword', 'geo_point'])
}
type: chance.pickone(['string', 'keyword', 'geo_point']),
},
});
}).toThrowError(/non-object/);
}).toThrowError(/not an object/);
});

it('throws if root type has no type and no properties', () => {
expect(() => {
// tslint:disable-next-line:no-unused-expression
new IndexMappings({
root: {}
root: {},
});
}).toThrowError(/non-object/);
}).toThrowError(/not an object/);
});

it('initialized root type with properties object if not set', () => {
const mapping = new IndexMappings({
root: {
type: 'object'
}
type: 'object',
},
});

expect(mapping.getDsl()).toEqual({
root: {
type: 'object',
properties: {}
}
properties: {},
},
});
});

it('accepts an array of new extensions that will be added to the mapping', () => {
const initialMapping = {
x: { properties: {} }
x: { properties: {} },
};
const extensions = [
{
properties: {
y: {
properties: {
z: {
type: 'text'
}
}
}
}
}
type: 'text',
},
},
},
},
},
];

const mapping = new IndexMappings(initialMapping, extensions);
Expand All @@ -108,89 +110,89 @@ describe('server/mapping/index_mapping', function () {
y: {
properties: {
z: {
type: 'text'
}
}
}
}
}
type: 'text',
},
},
},
},
},
});
});

it('throws if any of the new properties conflict', () => {
const initialMapping = {
root: { properties: { foo: 'bar' } }
root: { properties: { foo: { type: 'string' } } },
};
const extensions = [
{
properties: {
foo: 'bar'
}
}
foo: { type: 'string' },
},
},
];

expect(() => {
new IndexMappings(initialMapping, extensions);
new IndexMappings(initialMapping, extensions); // tslint:disable-line:no-unused-expression
}).toThrowError(/foo/);
});

it('includes the pluginId from the extension in the error message if defined', () => {
const initialMapping = {
root: { properties: { foo: 'bar' } }
root: { properties: { foo: { type: 'string' } } },
};
const extensions = [
{
pluginId: 'abc123',
properties: {
foo: 'bar'
}
}
foo: { type: 'string' },
},
},
];

expect(() => {
new IndexMappings(initialMapping, extensions);
new IndexMappings(initialMapping, extensions); // tslint:disable-line:no-unused-expression
}).toThrowError(/plugin abc123/);
});

it('throws if any of the new properties start with _', () => {
const initialMapping = {
root: { properties: { foo: 'bar' } }
root: { properties: { foo: { type: 'string' } } },
};
const extensions = [
{
properties: {
_foo: 'bar'
}
}
_foo: { type: 'string' },
},
},
];

expect(() => {
new IndexMappings(initialMapping, extensions);
new IndexMappings(initialMapping, extensions); // tslint:disable-line:no-unused-expression
}).toThrowErrorMatchingSnapshot();
});

it('includes the pluginId from the extension in the _ error message if defined', () => {
const initialMapping = {
root: { properties: { foo: 'bar' } }
root: { properties: { foo: { type: 'string' } } },
};
const extensions = [
{
pluginId: 'abc123',
properties: {
_foo: 'bar'
}
}
_foo: { type: 'string' },
},
},
];

expect(() => {
new IndexMappings(initialMapping, extensions);
new IndexMappings(initialMapping, extensions); // tslint:disable-line:no-unused-expression
}).toThrowErrorMatchingSnapshot();
});
});

describe('#getDsl()', () => {
// tests are light because this method is used all over these tests
it('returns mapping as es dsl', function () {
it('returns mapping as es dsl', () => {
const mapping = new IndexMappings();
expect(typeof mapping.getDsl()).toBe('object');
});
Expand Down
92 changes: 92 additions & 0 deletions src/core/server/saved_objects/mappings/index_mappings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { cloneDeep } from 'lodash';

import { EsMapping, EsMappings, getRootProperties, getRootType } from './lib';
import { formatListAsProse } from './utils';

interface MappingExtension {
pluginId?: string;
properties: { [key: string]: EsMapping };
}

const DEFAULT_INITIAL_DSL: EsMappings = {
rootType: {
type: 'object',
properties: {},
},
};

export class IndexMappings {
private dsl: EsMappings = this.initialDsl;

constructor(
private initialDsl: EsMappings = DEFAULT_INITIAL_DSL,
mappingExtensions: MappingExtension[] = []
) {
// ensure that the dsl can be parsed with getRootProperties() and kin
this.setProperties(getRootProperties(this.dsl) || {});

// extend this.dsl with each extension (which currently come from uiExports.savedObjectMappings)
mappingExtensions.forEach(({ properties, pluginId }) => {
const rootProperties = getRootProperties(this.dsl);

const conflicts = Object.keys(properties).filter(key => rootProperties.hasOwnProperty(key));

const illegal = Object.keys(properties).filter(key => key.startsWith('_'));

if (conflicts.length) {
const props = formatListAsProse(conflicts);
const owner = pluginId ? `registered by plugin ${pluginId} ` : '';
throw new Error(`Mappings for ${props} ${owner}have already been defined`);
}

if (illegal.length) {
const props = formatListAsProse(illegal);
const owner = pluginId ? `registered by plugin ${pluginId} ` : '';
throw new Error(
`Property name${
props.length > 1 ? 's' : ''
} ${props} ${owner}are not allowed to start with an underscore (_)`
);
}

this.setProperties({
...rootProperties,
...properties,
});
});
}

public getDsl() {
return cloneDeep(this.dsl);
}

private setProperties(newProperties: MappingExtension['properties']) {
const rootType = getRootType(this.dsl);
this.dsl = {
...this.dsl,
[rootType]: {
...this.dsl[rootType],
properties: newProperties,
},
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,34 +25,31 @@ const MAPPINGS = {
foo: {
properties: {
name: {
type: 'text'
type: 'text',
},
description: {
type: 'text'
}
}
type: 'text',
},
},
},
bar: {
properties: {
baz: {
type: 'text',
fields: {
box: {
type: 'keyword'
}
}
}
}
}
}
}
type: 'keyword',
},
},
},
},
},
},
},
};

function runTest(key, mapping) {
expect(typeof key === 'string' || Array.isArray(key)).toBeTruthy();
expect(typeof mapping).toBe('object');

expect(getProperty(MAPPINGS, key)).toBe(mapping);
function runTest(path: string | string[], mapping: { [key: string]: any }) {
expect(getProperty(MAPPINGS, path)).toBe(mapping);
}

describe('getProperty(mappings, path)', () => {
Expand Down
Loading

0 comments on commit 02067a0

Please sign in to comment.