[6.x] Remove dependency on doc versions (#29906) (#30036)

Backports the following commits to 6.x:
 - Remove dependency on doc versions  (#29906)
This commit is contained in:
Spencer 2019-02-13 21:07:26 -08:00 committed by GitHub
parent 41eebe6747
commit f13b696018
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 790 additions and 263 deletions

View file

@ -61,7 +61,6 @@ describe('plugins/elasticsearch', () => {
cluster = { callWithInternalUser: sinon.stub() };
cluster.callWithInternalUser.withArgs('index', sinon.match.any).returns(Promise.resolve());
cluster.callWithInternalUser.withArgs('create', sinon.match.any).returns(Promise.resolve({ _id: '1', _version: 1 }));
cluster.callWithInternalUser.withArgs('mget', sinon.match.any).returns(Promise.resolve({ ok: true }));
cluster.callWithInternalUser.withArgs('get', sinon.match.any).returns(Promise.resolve({ found: false }));
cluster.callWithInternalUser.withArgs('search', sinon.match.any).returns(Promise.resolve({ hits: { hits: [] } }));

View file

@ -7,5 +7,5 @@
},
"id": "apm-*",
"type": "index-pattern",
"version": 1
"version": "1"
}

View file

@ -11,7 +11,7 @@
},
"id": "1ffc5e20-7827-11e7-8c47-65b845b5cfb3",
"type": "visualization",
"version": 1
"version": "1"
},
{
"attributes": {
@ -26,7 +26,7 @@
},
"id": "1bdca740-7828-11e7-8c47-65b845b5cfb3",
"type": "visualization",
"version": 2
"version": "2"
},
{
"attributes": {
@ -41,7 +41,7 @@
},
"id": "804ffc40-7828-11e7-8c47-65b845b5cfb3",
"type": "visualization",
"version": 1
"version": "1"
},
{
"attributes": {
@ -59,7 +59,7 @@
},
"id": "8d3ed660-7828-11e7-8c47-65b845b5cfb3",
"type": "dashboard",
"version": 1
"version": "1"
},
{
"attributes": {
@ -74,7 +74,7 @@
},
"id": "c618e4e0-7c69-11e7-aa55-3b0d52c71c60",
"type": "visualization",
"version": 1
"version": "1"
},
{
"attributes": {
@ -100,7 +100,7 @@
},
"id": "ceefd050-7c6a-11e7-aa55-3b0d52c71c60",
"type": "search",
"version": 1
"version": "1"
},
{
"attributes": {
@ -118,7 +118,7 @@
},
"id": "5f08a870-7c6a-11e7-aa55-3b0d52c71c60",
"type": "dashboard",
"version": 1
"version": "1"
},
{
"attributes": {
@ -133,7 +133,7 @@
},
"id": "22518e70-7c69-11e7-aa55-3b0d52c71c60",
"type": "visualization",
"version": 1
"version": "1"
},
{
"attributes": {
@ -151,7 +151,7 @@
},
"id": "37f6fac0-7c6a-11e7-aa55-3b0d52c71c60",
"type": "dashboard",
"version": 1
"version": "1"
},
{
"attributes": {
@ -166,7 +166,7 @@
},
"id": "a2e199b0-7820-11e7-8c47-65b845b5cfb3",
"type": "visualization",
"version": 1
"version": "1"
},
{
"attributes": {
@ -181,7 +181,7 @@
},
"id": "09bcf890-7822-11e7-8c47-65b845b5cfb3",
"type": "visualization",
"version": 2
"version": "2"
},
{
"attributes": {
@ -196,7 +196,7 @@
},
"id": "55606a60-7823-11e7-8c47-65b845b5cfb3",
"type": "visualization",
"version": 1
"version": "1"
},
{
"attributes": {
@ -214,7 +214,7 @@
},
"id": "41b5d920-7821-11e7-8c47-65b845b5cfb3",
"type": "dashboard",
"version": 1
"version": "1"
},
{
"attributes": {
@ -238,7 +238,7 @@
},
"id": "d7735b90-7ddf-11e7-b115-df9c90da2df1",
"type": "search",
"version": 1
"version": "1"
},
{
"attributes": {
@ -256,5 +256,5 @@
},
"id": "3e3de700-7de0-11e7-b115-df9c90da2df1",
"type": "dashboard",
"version": 2
"version": "2"
}]

View file

@ -35,7 +35,7 @@ export const createBulkCreateRoute = prereqs => ({
type: Joi.string().required(),
id: Joi.string(),
attributes: Joi.object().required(),
version: Joi.number(),
version: Joi.string(),
migrationVersion: Joi.object().optional(),
}).required()
),

View file

@ -59,7 +59,7 @@ describe('POST /api/saved_objects/_bulk_get', () => {
id: 'abc123',
type: 'index-pattern',
title: 'logstash-*',
version: 2
version: 'foo',
}]
};

View file

@ -32,7 +32,7 @@ export const createUpdateRoute = (prereqs) => {
}).required(),
payload: Joi.object({
attributes: Joi.object().required(),
version: Joi.number().min(1)
version: Joi.string(),
}).required()
},
handler(request) {

View file

@ -66,7 +66,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => {
it('calls upon savedObjectClient.update', async () => {
const attributes = { title: 'Testing' };
const options = { version: 2 };
const options = { version: 'foo' };
const request = {
method: 'PUT',
url: '/api/saved_objects/index-pattern/logstash-*',

View file

@ -24,6 +24,7 @@
import uuid from 'uuid';
import { SavedObjectsSchema } from '../schema';
import { decodeVersion, encodeVersion } from '../version';
/**
* The root document type. In 7.0, this needs to change to '_doc'.
@ -37,7 +38,8 @@ export interface RawDoc {
_id: string;
_source: any;
_type?: string;
_version?: number;
_seq_no?: number;
_primary_term?: number;
}
/**
@ -60,7 +62,7 @@ export interface SavedObjectDoc {
type: string;
namespace?: string;
migrationVersion?: MigrationVersion;
version?: number;
version?: string;
updated_at?: Date;
[rootProp: string]: any;
@ -99,8 +101,14 @@ export class SavedObjectsSerializer {
*
* @param {RawDoc} rawDoc - The raw ES document to be converted to saved object format.
*/
public rawToSavedObject({ _id, _source, _version }: RawDoc): SavedObjectDoc {
public rawToSavedObject({ _id, _source, _seq_no, _primary_term }: RawDoc): SavedObjectDoc {
const { type, namespace } = _source;
const version =
_seq_no != null || _primary_term != null
? encodeVersion(_seq_no!, _primary_term!)
: undefined;
return {
type,
id: this.trimIdPrefix(namespace, type, _id),
@ -108,7 +116,7 @@ export class SavedObjectsSerializer {
attributes: _source[type],
...(_source.migrationVersion && { migrationVersion: _source.migrationVersion }),
...(_source.updated_at && { updated_at: _source.updated_at }),
...(_version != null && { version: _version }),
...(version && { version }),
};
}
@ -131,7 +139,7 @@ export class SavedObjectsSerializer {
_id: this.generateRawId(namespace, type, id),
_source: source,
_type: ROOT_TYPE,
...(version != null && { _version: version }),
...(version != null && decodeVersion(version)),
};
}

View file

@ -20,6 +20,7 @@
import _ from 'lodash';
import { ROOT_TYPE, SavedObjectsSerializer } from '.';
import { SavedObjectsSchema } from '../schema';
import { encodeVersion } from '../version';
describe('saved object conversion', () => {
describe('#rawToSavedObject', () => {
@ -69,7 +70,8 @@ describe('saved object conversion', () => {
const actual = serializer.rawToSavedObject({
_id: 'hello:world',
_type: ROOT_TYPE,
_version: 3,
_seq_no: 3,
_primary_term: 1,
_source: {
type: 'hello',
hello: {
@ -86,7 +88,7 @@ describe('saved object conversion', () => {
const expected = {
id: 'world',
type: 'hello',
version: 3,
version: encodeVersion(3, 1),
attributes: {
a: 'b',
c: 'd',
@ -112,17 +114,46 @@ describe('saved object conversion', () => {
expect(actual).not.toHaveProperty('version');
});
test(`if specified it copies _version to version`, () => {
test(`if specified it encodes _seq_no and _primary_term to version`, () => {
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
const actual = serializer.rawToSavedObject({
_id: 'foo:bar',
_version: 4,
_seq_no: 4,
_primary_term: 1,
_source: {
type: 'foo',
hello: {},
},
});
expect(actual).toHaveProperty('version', 4);
expect(actual).toHaveProperty('version', encodeVersion(4, 1));
});
test(`if only _seq_no is specified it throws`, () => {
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
expect(() =>
serializer.rawToSavedObject({
_id: 'foo:bar',
_seq_no: 4,
_source: {
type: 'foo',
hello: {},
},
})
).toThrowErrorMatchingInlineSnapshot(`"_primary_term from elasticsearch must be an integer"`);
});
test(`if only _primary_term is throws`, () => {
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
expect(() =>
serializer.rawToSavedObject({
_id: 'foo:bar',
_primary_term: 1,
_source: {
type: 'foo',
hello: {},
},
})
).toThrowErrorMatchingInlineSnapshot(`"_seq_no from elasticsearch must be an integer"`);
});
test('if specified it copies the _source.updated_at property to updated_at', () => {
@ -206,7 +237,8 @@ describe('saved object conversion', () => {
const raw = {
_id: 'foo-namespace:foo:bar',
_type: ROOT_TYPE,
_version: 24,
_primary_term: 24,
_seq_no: 42,
_source: {
type: 'foo',
foo: {
@ -440,25 +472,38 @@ describe('saved object conversion', () => {
expect(actual._source).not.toHaveProperty('migrationVersion');
});
test('it copies the version property to _version', () => {
test('it decodes the version property to _seq_no and _primary_term', () => {
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
const actual = serializer.savedObjectToRaw({
type: '',
attributes: {},
version: 4,
version: encodeVersion(1, 2),
} as any);
expect(actual).toHaveProperty('_version', 4);
expect(actual).toHaveProperty('_seq_no', 1);
expect(actual).toHaveProperty('_primary_term', 2);
});
test(`if unspecified it doesn't add _version property`, () => {
test(`if unspecified it doesn't add _seq_no or _primary_term properties`, () => {
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
const actual = serializer.savedObjectToRaw({
type: '',
attributes: {},
} as any);
expect(actual).not.toHaveProperty('_version');
expect(actual).not.toHaveProperty('_seq_no');
expect(actual).not.toHaveProperty('_primary_term');
});
test(`if version invalid it throws`, () => {
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
expect(() =>
serializer.savedObjectToRaw({
type: '',
attributes: {},
version: 'foo',
} as any)
).toThrowErrorMatchingInlineSnapshot(`"Invalid version [foo]"`);
});
test('it copies attributes to _source[type]', () => {

View file

@ -25,3 +25,6 @@ export function isNotFoundError(maybeError: any): boolean;
export function isConflictError(maybeError: any): boolean;
export function isEsUnavailableError(maybeError: any): boolean;
export function isEsAutoCreateIndexError(maybeError: any): boolean;
export function createInvalidVersionError(version: any): Error;
export function isInvalidVersionError(maybeError: Error): boolean;

View file

@ -50,6 +50,15 @@ export function isBadRequestError(error) {
return error && error[code] === CODE_BAD_REQUEST;
}
// 400 - invalid version
const CODE_INVALID_VERSION = 'SavedObjectsClient/invalidVersion';
export function createInvalidVersionError(versionInput) {
return decorate(Boom.badRequest(`Invalid version [${versionInput}]`), CODE_INVALID_VERSION, 400);
}
export function isInvalidVersionError(error) {
return error && error[code] === CODE_INVALID_VERSION;
}
// 401 - Not Authorized
const CODE_NOT_AUTHORIZED = 'SavedObjectsClient/notAuthorized';

View file

@ -23,6 +23,7 @@ import { getSearchDsl } from './search_dsl';
import { includedFields } from './included_fields';
import { decorateEsError } from './decorate_es_error';
import * as errors from './errors';
import { decodeRequestVersion, encodeVersion, encodeHitVersion } from '../../version';
// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
@ -169,7 +170,8 @@ export class SavedObjectsRepository {
const {
error,
_id: responseId,
_version: version,
_seq_no: seqNo,
_primary_term: primaryTerm,
} = Object.values(response)[0];
const {
@ -199,8 +201,8 @@ export class SavedObjectsRepository {
id,
type,
updated_at: time,
version,
attributes
version: encodeVersion(seqNo, primaryTerm),
attributes,
};
})
};
@ -252,7 +254,6 @@ export class SavedObjectsRepository {
* @returns {promise} - { took, timed_out, total, deleted, batches, version_conflicts, noops, retries, failures }
*/
async deleteByNamespace(namespace) {
if (!namespace || typeof namespace !== 'string') {
throw new TypeError(`namespace is required, and must be a string`);
}
@ -324,7 +325,7 @@ export class SavedObjectsRepository {
ignore: [404],
rest_total_hits_as_int: true,
body: {
version: true,
seq_no_primary_term: true,
...getSearchDsl(this._mappings, this._schema, {
search,
searchFields,
@ -407,7 +408,7 @@ export class SavedObjectsRepository {
id,
type,
...time && { updated_at: time },
version: doc._version,
version: encodeHitVersion(doc),
attributes: doc._source[type],
migrationVersion: doc._source.migrationVersion,
};
@ -449,7 +450,7 @@ export class SavedObjectsRepository {
id,
type,
...updatedAt && { updated_at: updatedAt },
version: response._version,
version: encodeHitVersion(response),
attributes: response._source[type],
migrationVersion: response._source.migrationVersion,
};
@ -461,7 +462,7 @@ export class SavedObjectsRepository {
* @param {string} type
* @param {string} id
* @param {object} [options={}]
* @property {integer} options.version - ensures version matches that of persisted object
* @property {string} options.version - ensures version matches that of persisted object
* @property {string} [options.namespace]
* @returns {promise}
*/
@ -476,7 +477,7 @@ export class SavedObjectsRepository {
id: this._serializer.generateRawId(namespace, type, id),
type: this._type,
index: this._index,
version,
...(version && decodeRequestVersion(version)),
refresh: 'wait_for',
ignore: [404],
body: {
@ -496,7 +497,7 @@ export class SavedObjectsRepository {
id,
type,
updated_at: time,
version: response._version,
version: encodeHitVersion(response),
attributes
};
}
@ -570,7 +571,7 @@ export class SavedObjectsRepository {
id,
type,
updated_at: time,
version: response._version,
version: encodeHitVersion(response),
attributes: response.get._source[type],
};

View file

@ -26,6 +26,7 @@ import * as errors from './errors';
import elasticsearch from 'elasticsearch';
import { SavedObjectsSchema } from '../../schema';
import { SavedObjectsSerializer } from '../../serialization';
import { encodeHitVersion } from '../../version';
// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
@ -39,6 +40,8 @@ describe('SavedObjectsRepository', () => {
let migrator;
const mockTimestamp = '2017-08-14T15:49:14.886Z';
const mockTimestampFields = { updated_at: mockTimestamp };
const mockVersionProps = { _seq_no: 1, _primary_term: 1 };
const mockVersion = encodeHitVersion(mockVersionProps);
const noNamespaceSearchResults = {
hits: {
total: 4,
@ -47,6 +50,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'index-pattern:logstash-*',
_score: 1,
...mockVersionProps,
_source: {
type: 'index-pattern',
...mockTimestampFields,
@ -61,6 +65,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'config:6.0.0-alpha1',
_score: 1,
...mockVersionProps,
_source: {
type: 'config',
...mockTimestampFields,
@ -74,6 +79,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'index-pattern:stocks-*',
_score: 1,
...mockVersionProps,
_source: {
type: 'index-pattern',
...mockTimestampFields,
@ -88,6 +94,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'globaltype:something',
_score: 1,
...mockVersionProps,
_source: {
type: 'globaltype',
...mockTimestampFields,
@ -107,6 +114,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'foo-namespace:index-pattern:logstash-*',
_score: 1,
...mockVersionProps,
_source: {
namespace: 'foo-namespace',
type: 'index-pattern',
@ -122,6 +130,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'foo-namespace:config:6.0.0-alpha1',
_score: 1,
...mockVersionProps,
_source: {
namespace: 'foo-namespace',
type: 'config',
@ -136,6 +145,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'foo-namespace:index-pattern:stocks-*',
_score: 1,
...mockVersionProps,
_source: {
namespace: 'foo-namespace',
type: 'index-pattern',
@ -151,6 +161,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'globaltype:something',
_score: 1,
...mockVersionProps,
_source: {
type: 'globaltype',
...mockTimestampFields,
@ -239,7 +250,7 @@ describe('SavedObjectsRepository', () => {
callAdminCluster.callsFake((method, params) => ({
_type: 'doc',
_id: params.id,
_version: 2
...mockVersionProps,
}));
});
@ -267,7 +278,7 @@ describe('SavedObjectsRepository', () => {
type: 'index-pattern',
id: 'logstash-*',
...mockTimestampFields,
version: 2,
version: mockVersion,
attributes: {
title: 'Logstash',
}
@ -518,7 +529,7 @@ describe('SavedObjectsRepository', () => {
create: {
_type: 'doc',
_id: 'index-pattern:two',
_version: 2
...mockVersionProps,
}
}]
}));
@ -537,7 +548,7 @@ describe('SavedObjectsRepository', () => {
}, {
id: 'two',
type: 'index-pattern',
version: 2,
version: mockVersion,
...mockTimestampFields,
attributes: { title: 'Test Two' },
}
@ -552,13 +563,13 @@ describe('SavedObjectsRepository', () => {
create: {
_type: 'doc',
_id: 'config:one',
_version: 2
...mockVersionProps
}
}, {
create: {
_type: 'doc',
_id: 'index-pattern:two',
_version: 2
...mockVersionProps
}
}]
}));
@ -575,13 +586,13 @@ describe('SavedObjectsRepository', () => {
{
id: 'one',
type: 'config',
version: 2,
version: mockVersion,
...mockTimestampFields,
attributes: { title: 'Test One' },
}, {
id: 'two',
type: 'index-pattern',
version: 2,
version: mockVersion,
...mockTimestampFields,
attributes: { title: 'Test Two' },
}
@ -858,8 +869,8 @@ describe('SavedObjectsRepository', () => {
id: doc._id.replace(/(index-pattern|config|globaltype)\:/, ''),
type: doc._source.type,
...mockTimestampFields,
version: doc._version,
attributes: doc._source[doc._source.type]
version: mockVersion,
attributes: doc._source[doc._source.type],
});
});
});
@ -881,8 +892,8 @@ describe('SavedObjectsRepository', () => {
id: doc._id.replace(/(foo-namespace\:)?(index-pattern|config|globaltype)\:/, ''),
type: doc._source.type,
...mockTimestampFields,
version: doc._version,
attributes: doc._source[doc._source.type]
version: mockVersion,
attributes: doc._source[doc._source.type],
});
});
});
@ -926,7 +937,7 @@ describe('SavedObjectsRepository', () => {
const noNamespaceResult = {
_id: 'index-pattern:logstash-*',
_type: 'doc',
_version: 2,
...mockVersionProps,
_source: {
type: 'index-pattern',
specialProperty: 'specialValue',
@ -939,7 +950,7 @@ describe('SavedObjectsRepository', () => {
const namespacedResult = {
_id: 'foo-namespace:index-pattern:logstash-*',
_type: 'doc',
_version: 2,
...mockVersionProps,
_source: {
namespace: 'foo-namespace',
type: 'index-pattern',
@ -968,7 +979,7 @@ describe('SavedObjectsRepository', () => {
id: 'logstash-*',
type: 'index-pattern',
updated_at: mockTimestamp,
version: 2,
version: mockVersion,
attributes: {
title: 'Testing'
}
@ -983,7 +994,7 @@ describe('SavedObjectsRepository', () => {
id: 'logstash-*',
type: 'index-pattern',
updated_at: mockTimestamp,
version: 2,
version: mockVersion,
attributes: {
title: 'Testing'
}
@ -1111,7 +1122,7 @@ describe('SavedObjectsRepository', () => {
_type: 'doc',
_id: 'config:good',
found: true,
_version: 2,
...mockVersionProps,
_source: { ...mockTimestampFields, config: { title: 'Test' } }
}, {
_type: 'doc',
@ -1132,8 +1143,8 @@ describe('SavedObjectsRepository', () => {
id: 'good',
type: 'config',
...mockTimestampFields,
version: 2,
attributes: { title: 'Test' }
version: mockVersion,
attributes: { title: 'Test' },
});
expect(savedObjects[1]).toEqual({
id: 'bad',
@ -1146,14 +1157,13 @@ describe('SavedObjectsRepository', () => {
describe('#update', () => {
const id = 'logstash-*';
const type = 'index-pattern';
const newVersion = 2;
const attributes = { title: 'Testing' };
beforeEach(() => {
callAdminCluster.returns(Promise.resolve({
_id: `${type}:${id}`,
_type: 'doc',
_version: newVersion,
...mockVersionProps,
result: 'updated'
}));
});
@ -1168,14 +1178,16 @@ describe('SavedObjectsRepository', () => {
sinon.assert.calledOnce(migrator.awaitMigration);
});
it('returns current ES document version', async () => {
const response = await savedObjectsRepository.update('index-pattern', 'logstash-*', attributes, { namespace: 'foo-namespace' });
it('returns current ES document _seq_no and _primary_term encoded as version', async () => {
const response = await savedObjectsRepository.update('index-pattern', 'logstash-*', attributes, {
namespace: 'foo-namespace',
});
expect(response).toEqual({
id,
type,
...mockTimestampFields,
version: newVersion,
attributes
version: mockVersion,
attributes,
});
});
@ -1184,12 +1196,18 @@ describe('SavedObjectsRepository', () => {
type,
id,
{ title: 'Testing' },
{ version: newVersion - 1 }
{
version: encodeHitVersion({
_seq_no: 100,
_primary_term: 200
})
}
);
sinon.assert.calledOnce(callAdminCluster);
sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({
version: newVersion - 1
if_seq_no: 100,
if_primary_term: 200,
}));
});
@ -1204,7 +1222,6 @@ describe('SavedObjectsRepository', () => {
sinon.assert.calledWithExactly(callAdminCluster, 'update', {
type: 'doc',
id: 'foo-namespace:index-pattern:logstash-*',
version: undefined,
body: {
doc: { updated_at: mockTimestamp, 'index-pattern': { title: 'Testing' } }
},
@ -1223,7 +1240,6 @@ describe('SavedObjectsRepository', () => {
sinon.assert.calledWithExactly(callAdminCluster, 'update', {
type: 'doc',
id: 'index-pattern:logstash-*',
version: undefined,
body: {
doc: { updated_at: mockTimestamp, 'index-pattern': { title: 'Testing' } }
},
@ -1246,7 +1262,6 @@ describe('SavedObjectsRepository', () => {
sinon.assert.calledWithExactly(callAdminCluster, 'update', {
type: 'doc',
id: 'globaltype:foo',
version: undefined,
body: {
doc: { updated_at: mockTimestamp, 'globaltype': { name: 'bar' } }
},
@ -1264,7 +1279,7 @@ describe('SavedObjectsRepository', () => {
callAdminCluster.callsFake((method, params) => ({
_type: 'doc',
_id: params.id,
_version: 2,
...mockVersionProps,
_index: '.kibana',
get: {
found: true,
@ -1284,7 +1299,7 @@ describe('SavedObjectsRepository', () => {
callAdminCluster.callsFake((method, params) => ({
_type: 'doc',
_id: params.id,
_version: 2,
...mockVersionProps,
_index: '.kibana',
get: {
found: true,
@ -1313,7 +1328,7 @@ describe('SavedObjectsRepository', () => {
type: 'config',
id: '6.0.0-alpha1',
...mockTimestampFields,
version: 2,
version: mockVersion,
attributes: {
buildNum: 8468,
defaultIndex: 'logstash-*'
@ -1384,7 +1399,7 @@ describe('SavedObjectsRepository', () => {
callAdminCluster.callsFake((method, params) => ({
_type: 'doc',
_id: params.id,
_version: 2,
...mockVersionProps,
_index: '.kibana',
get: {
found: true,

View file

@ -58,7 +58,7 @@ export interface FindResponse<T extends SavedObjectAttributes = any> {
}
export interface UpdateOptions extends BaseOptions {
version?: number;
version?: string;
}
export interface BulkGetObject {
@ -78,7 +78,7 @@ export interface SavedObjectAttributes {
export interface SavedObject<T extends SavedObjectAttributes = any> {
id: string;
type: string;
version?: number;
version?: string;
updated_at?: string;
error?: {
message: string;

View file

@ -0,0 +1,21 @@
/*
* 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.
*/
export const decodeBase64 = (base64: string) => Buffer.from(base64, 'base64').toString('utf8');
export const encodeBase64 = (utf8: string) => Buffer.from(utf8, 'utf8').toString('base64');

View file

@ -0,0 +1,35 @@
/*
* 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.
*/
jest.mock('./decode_version', () => ({
decodeVersion: jest.fn().mockReturnValue({ _seq_no: 1, _primary_term: 2 }),
}));
import { decodeRequestVersion } from './decode_request_version';
import { decodeVersion } from './decode_version';
it('renames decodeVersion() return value to use if_seq_no and if_primary_term', () => {
expect(decodeRequestVersion('foobar')).toMatchInlineSnapshot(`
Object {
"if_primary_term": 2,
"if_seq_no": 1,
}
`);
expect(decodeVersion).toHaveBeenCalledWith('foobar');
});

View file

@ -0,0 +1,32 @@
/*
* 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 { decodeVersion } from './decode_version';
/**
* Helper for decoding version to request params that are driven
* by the version info
*/
export function decodeRequestVersion(version?: string) {
const decoded = decodeVersion(version);
return {
if_seq_no: decoded._seq_no,
if_primary_term: decoded._primary_term,
};
}

View file

@ -0,0 +1,102 @@
/*
* 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 Boom from 'boom';
import { decodeVersion } from './decode_version';
describe('decodeVersion', () => {
it('parses version back into {_seq_no,_primary_term} object', () => {
expect(decodeVersion('WzQsMV0=')).toMatchInlineSnapshot(`
Object {
"_primary_term": 1,
"_seq_no": 4,
}
`);
});
it('throws Boom error if not in base64', () => {
let error;
try {
decodeVersion('[1,4]');
} catch (err) {
error = err;
}
expect(error.message).toMatchInlineSnapshot(`"Invalid version [[1,4]]"`);
expect(Boom.isBoom(error)).toBe(true);
expect(error.output).toMatchInlineSnapshot(`
Object {
"headers": Object {},
"payload": Object {
"error": "Bad Request",
"message": "Invalid version [[1,4]]",
"statusCode": 400,
},
"statusCode": 400,
}
`);
});
it('throws if not JSON encoded', () => {
let error;
try {
decodeVersion('MSwy');
} catch (err) {
error = err;
}
expect(error.message).toMatchInlineSnapshot(`"Invalid version [MSwy]"`);
expect(Boom.isBoom(error)).toBe(true);
expect(error.output).toMatchInlineSnapshot(`
Object {
"headers": Object {},
"payload": Object {
"error": "Bad Request",
"message": "Invalid version [MSwy]",
"statusCode": 400,
},
"statusCode": 400,
}
`);
});
it('throws if either value is not an integer', () => {
let error;
try {
decodeVersion('WzEsMy41XQ==');
} catch (err) {
error = err;
}
expect(error.message).toMatchInlineSnapshot(`"Invalid version [WzEsMy41XQ==]"`);
expect(Boom.isBoom(error)).toBe(true);
expect(error.output).toMatchInlineSnapshot(`
Object {
"headers": Object {},
"payload": Object {
"error": "Bad Request",
"message": "Invalid version [WzEsMy41XQ==]",
"statusCode": 400,
},
"statusCode": 400,
}
`);
});
});

View file

@ -0,0 +1,51 @@
/*
* 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 { createInvalidVersionError } from '../service/lib/errors';
import { decodeBase64 } from './base64';
/**
* Decode the "opaque" version string to the sequence params we
* can use to activate optimistic concurrency in Elasticsearch
*/
export function decodeVersion(version?: string) {
try {
if (typeof version !== 'string') {
throw new TypeError();
}
const seqParams = JSON.parse(decodeBase64(version)) as [number, number];
if (
!Array.isArray(seqParams) ||
seqParams.length !== 2 ||
!Number.isInteger(seqParams[0]) ||
!Number.isInteger(seqParams[1])
) {
throw new TypeError();
}
return {
_seq_no: seqParams[0],
_primary_term: seqParams[1],
};
} catch (_) {
throw createInvalidVersionError(version);
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.
*/
jest.mock('./encode_version', () => ({
encodeVersion: jest.fn().mockReturnValue('foo'),
}));
import { encodeHitVersion } from './encode_hit_version';
import { encodeVersion } from './encode_version';
it('renames decodeVersion() return value to use if_seq_no and if_primary_term', () => {
expect(encodeHitVersion({ _seq_no: 1, _primary_term: 2 })).toMatchInlineSnapshot(`"foo"`);
expect(encodeVersion).toHaveBeenCalledWith(1, 2);
});

View file

@ -0,0 +1,28 @@
/*
* 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 { encodeVersion } from './encode_version';
/**
* Helper for encoding a version from a "hit" (hits.hits[#] from _search) or
* "doc" (body from GET, update, etc) object
*/
export function encodeHitVersion(response: { _seq_no: number; _primary_term: number }) {
return encodeVersion(response._seq_no, response._primary_term);
}

View file

@ -0,0 +1,62 @@
/*
* 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 { encodeVersion } from './encode_version';
describe('encodeVersion', () => {
it('throws if primaryTerm is not an integer', () => {
expect(() => encodeVersion(1, undefined as any)).toThrowErrorMatchingInlineSnapshot(
`"_primary_term from elasticsearch must be an integer"`
);
expect(() => encodeVersion(1, null as any)).toThrowErrorMatchingInlineSnapshot(
`"_primary_term from elasticsearch must be an integer"`
);
expect(() => encodeVersion(1, {} as any)).toThrowErrorMatchingInlineSnapshot(
`"_primary_term from elasticsearch must be an integer"`
);
expect(() => encodeVersion(1, [] as any)).toThrowErrorMatchingInlineSnapshot(
`"_primary_term from elasticsearch must be an integer"`
);
expect(() => encodeVersion(1, 2.5 as any)).toThrowErrorMatchingInlineSnapshot(
`"_primary_term from elasticsearch must be an integer"`
);
});
it('throws if seqNo is not an integer', () => {
expect(() => encodeVersion(undefined as any, 1)).toThrowErrorMatchingInlineSnapshot(
`"_seq_no from elasticsearch must be an integer"`
);
expect(() => encodeVersion(null as any, 1)).toThrowErrorMatchingInlineSnapshot(
`"_seq_no from elasticsearch must be an integer"`
);
expect(() => encodeVersion({} as any, 1)).toThrowErrorMatchingInlineSnapshot(
`"_seq_no from elasticsearch must be an integer"`
);
expect(() => encodeVersion([] as any, 1)).toThrowErrorMatchingInlineSnapshot(
`"_seq_no from elasticsearch must be an integer"`
);
expect(() => encodeVersion(2.5 as any, 1)).toThrowErrorMatchingInlineSnapshot(
`"_seq_no from elasticsearch must be an integer"`
);
});
it('returns a base64 encoded, JSON string of seqNo and primaryTerm', () => {
expect(encodeVersion(123, 456)).toMatchInlineSnapshot(`"WzEyMyw0NTZd"`);
});
});

View file

@ -0,0 +1,37 @@
/*
* 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 { encodeBase64 } from './base64';
/**
* Encode the sequence params into an "opaque" version string
* that can be used in the saved object API in place of numeric
* version numbers
*/
export function encodeVersion(seqNo: number, primaryTerm: number) {
if (!Number.isInteger(primaryTerm)) {
throw new TypeError('_primary_term from elasticsearch must be an integer');
}
if (!Number.isInteger(seqNo)) {
throw new TypeError('_seq_no from elasticsearch must be an integer');
}
return encodeBase64(JSON.stringify([seqNo, primaryTerm]));
}

View file

@ -0,0 +1,23 @@
/*
* 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.
*/
export * from './encode_version';
export * from './encode_hit_version';
export * from './decode_version';
export * from './decode_request_version';

View file

@ -42,13 +42,13 @@ describe('Saved Object', function () {
* that can be used to stub es calls.
* @param indexPatternId
* @param additionalOptions - object that will be assigned to the mocked doc response.
* @returns {{attributes: {}, type: string, id: *, _version: integer}}
* @returns {{attributes: {}, type: string, id: *, _version: string}}
*/
function getMockedDocResponse(indexPatternId, additionalOptions = {}) {
return {
type: 'dashboard',
id: indexPatternId,
_version: 2,
_version: 'foo',
attributes: {},
...additionalOptions
};
@ -242,7 +242,11 @@ describe('Saved Object', function () {
return createInitializedSavedObject({ type: 'dashboard' }).then(savedObject => {
const mockDocResponse = getMockedDocResponse('myId');
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
return BluebirdPromise.resolve({ type: 'dashboard', id: 'myId', _version: 2 });
return BluebirdPromise.resolve({
type: 'dashboard',
id: 'myId',
_version: 'foo'
});
});
stubESResponse(mockDocResponse);
@ -261,7 +265,9 @@ describe('Saved Object', function () {
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
expect(savedObject.isSaving).to.be(true);
return BluebirdPromise.resolve({
type: 'dashboard', id, version: 2
type: 'dashboard',
id,
version: 'foo'
});
});
expect(savedObject.isSaving).to.be(false);
@ -451,7 +457,7 @@ describe('Saved Object', function () {
attributes: {
title: 'testIndexPattern'
},
_version: 2
_version: 'foo'
});
const savedObject = new SavedObject(config);

View file

@ -208,7 +208,6 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm
return savedObjectsClient.get(esType, this.id)
.then(resp => {
// temporary compatability for savedObjectsClient
return {
_id: resp.id,
_type: resp.type,

View file

@ -86,7 +86,7 @@ jest.mock('../unsupported_time_patterns', () => ({
jest.mock('../../saved_objects', () => {
const object = {
_version: 1,
_version: 'foo',
_id: 'foo',
attributes: {
title: 'something'
@ -106,10 +106,11 @@ jest.mock('../../saved_objects', () => {
}
object.attributes.title = body.title;
object._version += 'a';
return {
id: object._id,
_version: ++object._version,
_version: object._version,
};
}
},
@ -137,13 +138,13 @@ describe('IndexPattern', () => {
const pattern = new IndexPattern('foo');
await pattern.init();
expect(pattern.version).toBe(2);
expect(pattern.version).toBe('fooa');
// Create the same one - we're going to handle concurrency
const samePattern = new IndexPattern('foo');
await samePattern.init();
expect(samePattern.version).toBe(3);
expect(samePattern.version).toBe('fooaa');
// This will conflict because samePattern did a save (from refreshFields)
// but the resave should work fine

View file

@ -47,7 +47,6 @@ describe('SavedObject', () => {
const client = sinon.stub();
const savedObject = new SavedObject(client, { version });
expect(savedObject._version).to.be(version);
});
});

View file

@ -29,7 +29,7 @@ describe('SavedObjectsClient', () => {
id: 'AVwSwFxtcMV38qjDZoQg',
type: 'config',
attributes: { title: 'Example title' },
version: 2
version: 'foo'
};
let kfetchStub;
@ -228,8 +228,8 @@ describe('SavedObjectsClient', () => {
test('makes HTTP call', () => {
const attributes = { foo: 'Foo', bar: 'Bar' };
const body = { attributes, version: 2 };
const options = { version: 2 };
const body = { attributes, version: 'foo' };
const options = { version: 'foo' };
savedObjectsClient.update('index-pattern', 'logstash-*', attributes, options);
sinon.assert.calledOnce(kfetchStub);

View file

@ -41,7 +41,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
create: sinon.stub().callsFake(async (type, attributes, options = {}) => ({
type,
id: options.id,
version: 1,
version: 'foo',
}))
};

View file

@ -66,7 +66,7 @@ export default function ({ getService }) {
type: 'dashboard',
id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e',
updated_at: resp.body.saved_objects[1].updated_at,
version: 1,
version: 'WzgsMV0=',
attributes: {
title: 'A great new dashboard'
}
@ -98,7 +98,7 @@ export default function ({ getService }) {
type: 'visualization',
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
updated_at: resp.body.saved_objects[0].updated_at,
version: 1,
version: 'WzAsMV0=',
attributes: {
title: 'An existing visualization'
}
@ -107,7 +107,7 @@ export default function ({ getService }) {
type: 'dashboard',
id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e',
updated_at: resp.body.saved_objects[1].updated_at,
version: 1,
version: 'WzEsMV0=',
attributes: {
title: 'A great new dashboard'
}

View file

@ -48,7 +48,7 @@ export default function ({ getService }) {
id: resp.body.id,
type: 'visualization',
updated_at: resp.body.updated_at,
version: 1,
version: 'WzgsMV0=',
attributes: {
title: 'My favorite vis'
}
@ -86,7 +86,7 @@ export default function ({ getService }) {
id: resp.body.id,
type: 'visualization',
updated_at: resp.body.updated_at,
version: 1,
version: 'WzAsMV0=',
attributes: {
title: 'My favorite vis'
}

View file

@ -42,7 +42,7 @@ export default function ({ getService }) {
{
type: 'visualization',
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
version: 1,
version: 'WzIsMV0=',
attributes: {
'title': 'Count of requests'
}

View file

@ -48,7 +48,7 @@ export default function ({ getService }) {
id: resp.body.id,
type: 'visualization',
updated_at: resp.body.updated_at,
version: 2,
version: 'WzgsMV0=',
attributes: {
title: 'My second favorite vis'
}

View file

@ -101,7 +101,8 @@ export interface DatabaseSearchResponse<T> {
_id: string;
_score: number;
_source: T;
_version?: number;
_seq_no?: number;
_primary_term?: number;
_explanation?: DatabaseExplanation;
fields?: any;
highlight?: any;
@ -129,7 +130,8 @@ export interface DatabaseGetDocumentResponse<Source> {
_index: string;
_type: string;
_id: string;
_version: number;
_seq_no: number;
_primary_term: number;
found: boolean;
_source: Source;
}
@ -183,8 +185,8 @@ export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams {
refresh?: DatabaseRefresh;
routing?: string;
timeout?: string;
version?: number;
versionType?: DatabaseVersionType;
ifSeqNo?: number;
ifPrimaryTerm?: number;
index: string;
type: string;
id: string;
@ -195,7 +197,8 @@ export interface DatabaseIndexDocumentResponse {
_index: string;
_type: string;
_id: string;
_version: number;
_seq_no: number;
_primary_term: number;
result: string;
}
@ -204,7 +207,8 @@ export interface DatabaseUpdateDocumentResponse {
_index: string;
_type: string;
_id: string;
_version: number;
_seq_no: number;
_primary_term: number;
result: string;
}
@ -213,7 +217,8 @@ export interface DatabaseDeleteDocumentResponse {
_index: string;
_type: string;
_id: string;
_version: number;
_seq_no: number;
_primary_term: number;
result: string;
}
@ -226,8 +231,8 @@ export interface DatabaseIndexDocumentParams<T> extends DatabaseGenericParams {
timeout?: string;
timestamp?: Date | number;
ttl?: string;
version?: number;
versionType?: DatabaseVersionType;
ifSeqNo?: number;
ifPrimaryTerm?: number;
pipeline?: string;
id?: string;
index: string;
@ -247,8 +252,8 @@ export interface DatabaseCreateDocumentParams extends DatabaseGenericParams {
timeout?: string;
timestamp?: Date | number;
ttl?: string;
version?: number;
versionType?: DatabaseVersionType;
ifSeqNo?: number;
ifPrimaryTerm?: number;
pipeline?: string;
id?: string;
index: string;
@ -266,8 +271,8 @@ export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams {
refresh?: DatabaseRefresh;
routing?: string;
timeout?: string;
version?: number;
versionType?: DatabaseVersionType;
ifSeqNo?: number;
ifPrimaryTerm?: number;
index: string;
type: string;
id: string;
@ -283,8 +288,8 @@ export interface DatabaseGetParams extends DatabaseGenericParams {
_source?: DatabaseNameList;
_sourceExclude?: DatabaseNameList;
_source_includes?: DatabaseNameList;
version?: number;
versionType?: DatabaseVersionType;
ifSeqNo?: number;
ifPrimaryTerm?: number;
id: string;
index: string;
type: string;
@ -292,7 +297,6 @@ export interface DatabaseGetParams extends DatabaseGenericParams {
export type DatabaseNameList = string | string[] | boolean;
export type DatabaseRefresh = boolean | 'true' | 'false' | 'wait_for' | '';
export type DatabaseVersionType = 'internal' | 'external' | 'external_gte' | 'force';
export type ExpandWildcards = 'open' | 'closed' | 'none' | 'all';
export type DefaultOperator = 'AND' | 'OR';
export type DatabaseConflicts = 'abort' | 'proceed';
@ -311,6 +315,7 @@ export interface DatabaseDeleteDocumentResponse {
_index: string;
_type: string;
_id: string;
_version: number;
_seq_no: number;
_primary_term: number;
result: string;
}

View file

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash';
import { FrameworkUser } from '../framework/adapter_types';
import { internalAuthData } from './../framework/adapter_types';
import {
@ -114,43 +114,6 @@ export class KibanaDatabaseAdapter implements DatabaseAdapter {
return result;
}
private async fetchAllFromScroll<Source>(
user: FrameworkUser,
response: DatabaseSearchResponse<Source>,
hits: DatabaseSearchResponse<Source>['hits']['hits'] = []
): Promise<
Array<{
_index: string;
_type: string;
_id: string;
_score: number;
_source: Source;
_version?: number;
fields?: any;
highlight?: any;
inner_hits?: any;
sort?: string[];
}>
> {
const newHits = get(response, 'hits.hits', []);
const scrollId = get(response, '_scroll_id');
if (newHits.length > 0) {
hits.push(...newHits);
return this.callWithUser(user, 'scroll', {
body: {
scroll: '30s',
scroll_id: scrollId,
},
}).then((innerResponse: DatabaseSearchResponse<Source>) => {
return this.fetchAllFromScroll(user, innerResponse, hits);
});
}
return Promise.resolve(hits);
}
private callWithUser(user: FrameworkUser, esMethod: string, options: any = {}): any {
if (user.kind === 'authenticated') {
return this.es.callWithRequest(

View file

@ -89,7 +89,7 @@
"name": "version",
"description": "The version number the source configuration was last persisted with",
"args": [],
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
@ -430,6 +430,16 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "String",
"description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Float",
@ -511,16 +521,6 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "String",
"description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "InfraSourceFields",

View file

@ -19,7 +19,7 @@ export interface InfraSource {
/** The id of the source */
id: string;
/** The version number the source configuration was last persisted with */
version?: number | null;
version?: string | null;
/** The timestamp the source configuration was last persisted at */
updatedAt?: number | null;
/** The raw configuration of the source */

View file

@ -12,7 +12,7 @@ export const sourcesSchema = gql`
"The id of the source"
id: ID!
"The version number the source configuration was last persisted with"
version: Float
version: String
"The timestamp the source configuration was last persisted at"
updatedAt: Float
"The raw configuration of the source"

View file

@ -47,7 +47,7 @@ export interface InfraSource {
/** The id of the source */
id: string;
/** The version number the source configuration was last persisted with */
version?: number | null;
version?: string | null;
/** The timestamp the source configuration was last persisted at */
updatedAt?: number | null;
/** The raw configuration of the source */
@ -606,7 +606,7 @@ export namespace InfraSourceResolvers {
/** The id of the source */
id?: IdResolver<string, TypeParent, Context>;
/** The version number the source configuration was last persisted with */
version?: VersionResolver<number | null, TypeParent, Context>;
version?: VersionResolver<string | null, TypeParent, Context>;
/** The timestamp the source configuration was last persisted at */
updatedAt?: UpdatedAtResolver<number | null, TypeParent, Context>;
/** The raw configuration of the source */
@ -635,7 +635,7 @@ export namespace InfraSourceResolvers {
Context
>;
export type VersionResolver<
R = number | null,
R = string | null,
Parent = InfraSource,
Context = InfraContext
> = Resolver<R, Parent, Context>;

View file

@ -14,7 +14,7 @@ describe('the InfraSources lib', () => {
configuration: createMockStaticConfiguration({}),
savedObjects: createMockSavedObjectsService({
id: 'TEST_ID',
version: 1,
version: 'foo',
updated_at: '2000-01-01T00:00:00.000Z',
attributes: {
metricAlias: 'METRIC_ALIAS',
@ -34,7 +34,7 @@ describe('the InfraSources lib', () => {
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
id: 'TEST_ID',
version: 1,
version: 'foo',
updatedAt: 946684800000,
configuration: {
metricAlias: 'METRIC_ALIAS',
@ -66,7 +66,7 @@ describe('the InfraSources lib', () => {
}),
savedObjects: createMockSavedObjectsService({
id: 'TEST_ID',
version: 1,
version: 'foo',
updated_at: '2000-01-01T00:00:00.000Z',
attributes: {
fields: {
@ -80,7 +80,7 @@ describe('the InfraSources lib', () => {
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
id: 'TEST_ID',
version: 1,
version: 'foo',
updatedAt: 946684800000,
configuration: {
metricAlias: 'METRIC_ALIAS',
@ -101,7 +101,7 @@ describe('the InfraSources lib', () => {
configuration: createMockStaticConfiguration({}),
savedObjects: createMockSavedObjectsService({
id: 'TEST_ID',
version: 1,
version: 'foo',
updated_at: '2000-01-01T00:00:00.000Z',
attributes: {},
}),
@ -111,7 +111,7 @@ describe('the InfraSources lib', () => {
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
id: 'TEST_ID',
version: 1,
version: 'foo',
updatedAt: 946684800000,
configuration: {
metricAlias: expect.any(String),

View file

@ -51,7 +51,7 @@ export const InfraSavedSourceConfigurationRuntimeType = runtimeTypes.intersectio
attributes: PartialInfraSourceConfigurationRuntimeType,
}),
runtimeTypes.partial({
version: runtimeTypes.number,
version: runtimeTypes.string,
updated_at: TimestampFromString,
}),
]);

View file

@ -17,7 +17,7 @@ describe('field format map', function () {
const indexPatternSavedObject = {
id: 'logstash-*',
type: 'index-pattern',
version: 4,
version: 'abc',
attributes: {
title: 'logstash-*',
timeFieldName: '@timestamp',

View file

@ -18,7 +18,8 @@ ClientMock.prototype.index = function (params = {}) {
_index: params.index || 'index',
_type: params.type || constants.DEFAULT_SETTING_DOCTYPE,
_id: params.id || uniqueId('testDoc'),
_version: 1,
_seq_no: 1,
_primary_term: 1,
_shards: { total: shardCount, successful: shardCount, failed: 0 },
created: true
});
@ -53,7 +54,8 @@ ClientMock.prototype.get = function (params = {}, source = {}) {
_index: params.index || 'index',
_type: params.type || constants.DEFAULT_SETTING_DOCTYPE,
_id: params.id || 'AVRPRLnlp7Ur1SZXfT-T',
_version: params.version || 1,
_seq_no: params._seq_no || 1,
_primary_term: params._primary_term || 1,
found: true,
_source: _source
});
@ -65,7 +67,8 @@ ClientMock.prototype.search = function (params = {}, count = 5, source = {}) {
_index: params.index || 'index',
_type: params.type || constants.DEFAULT_SETTING_DOCTYPE,
_id: uniqueId('documentId'),
_version: random(1, 5),
_seq_no: random(1, 5),
_primar_term: random(1, 5),
_score: null,
_source: {
created_at: new Date().toString(),
@ -96,7 +99,8 @@ ClientMock.prototype.update = function (params = {}) {
_index: params.index || 'index',
_type: params.type || constants.DEFAULT_SETTING_DOCTYPE,
_id: params.id || uniqueId('testDoc'),
_version: params.version + 1 || 2,
_seq_no: params.if_seq_no + 1 || 2,
_primary_term: params.if_primary_term + 1 || 2,
_shards: { total: shardCount, successful: shardCount, failed: 0 },
created: true
});

View file

@ -135,7 +135,8 @@ describe('Job Class', function () {
expect(jobDoc).to.have.property('id');
expect(jobDoc).to.have.property('index');
expect(jobDoc).to.have.property('type');
expect(jobDoc).to.have.property('version');
expect(jobDoc).to.have.property('_seq_no');
expect(jobDoc).to.have.property('_primary_term');
done();
} catch (e) {
done(e);
@ -383,7 +384,8 @@ describe('Job Class', function () {
expect(doc).to.have.property('index', index);
expect(doc).to.have.property('type', jobDoc.type);
expect(doc).to.have.property('id', jobDoc.id);
expect(doc).to.have.property('version', jobDoc.version);
expect(doc).to.have.property('_seq_no', jobDoc._seq_no);
expect(doc).to.have.property('_primary_term', jobDoc._primary_term);
expect(doc).to.have.property('created_by', defaultCreatedBy);
expect(doc).to.have.property('payload');

View file

@ -336,11 +336,6 @@ describe('Worker class', function () {
searchStub = sinon.stub(mockQueue.client, 'search').callsFake(() => Promise.resolve({ hits: { hits: [] } }));
});
it('should query with version', function () {
const params = getSearchParams();
expect(params).to.have.property('version', true);
});
it('should query by default doctype', function () {
const params = getSearchParams();
expect(params).to.have.property('type', constants.DEFAULT_SETTING_DOCTYPE);
@ -367,6 +362,11 @@ describe('Worker class', function () {
clock.restore();
});
it('should query with seq_no_primary_term', function () {
const { body } = getSearchParams(jobtype);
expect(body).to.have.property('seq_no_primary_term', true);
});
it('should filter unwanted source data', function () {
const excludedFields = [ 'output.content' ];
const { body } = getSearchParams(jobtype);
@ -432,7 +432,6 @@ describe('Worker class', function () {
index: 'myIndex',
type: 'test',
id: 12345,
version: 3
};
return mockQueue.client.get(params)
.then((jobDoc) => {
@ -446,13 +445,14 @@ describe('Worker class', function () {
clock.restore();
});
it('should use version on update', function () {
it('should use seqNo and primaryTerm on update', function () {
worker._claimJob(job);
const query = updateSpy.firstCall.args[0];
expect(query).to.have.property('index', job._index);
expect(query).to.have.property('type', job._type);
expect(query).to.have.property('id', job._id);
expect(query).to.have.property('version', job._version);
expect(query).to.have.property('if_seq_no', job._seq_no);
expect(query).to.have.property('if_primary_term', job._primary_term);
});
it('should increment the job attempts', function () {
@ -500,7 +500,7 @@ describe('Worker class', function () {
expect(msg).to.equal(false);
});
it('should reject the promise on version errors', function () {
it('should reject the promise on conflict errors', function () {
mockQueue.client.update.restore();
sinon.stub(mockQueue.client, 'update').returns(Promise.reject({ statusCode: 409 }));
return worker._claimJob(job)
@ -524,7 +524,8 @@ describe('Worker class', function () {
_index: 'myIndex',
_type: 'test',
_id: 12345,
_version: 3,
_seq_no: 3,
_primary_term: 3,
found: true,
_source: {
jobtype: 'jobtype',
@ -608,13 +609,14 @@ describe('Worker class', function () {
clock.restore();
});
it('should use version on update', function () {
it('should use _seq_no and _primary_term on update', function () {
worker._failJob(job);
const query = updateSpy.firstCall.args[0];
expect(query).to.have.property('index', job._index);
expect(query).to.have.property('type', job._type);
expect(query).to.have.property('id', job._id);
expect(query).to.have.property('version', job._version);
expect(query).to.have.property('if_seq_no', job._seq_no);
expect(query).to.have.property('if_primary_term', job._primary_term);
});
it('should set status to failed', function () {
@ -631,7 +633,7 @@ describe('Worker class', function () {
expect(doc.output).to.have.property('content', msg);
});
it('should return true on version mismatch errors', function () {
it('should return true on conflict errors', function () {
mockQueue.client.update.restore();
sinon.stub(mockQueue.client, 'update').returns(Promise.reject({ statusCode: 409 }));
return worker._failJob(job)
@ -735,7 +737,8 @@ describe('Worker class', function () {
expect(query).to.have.property('index', job._index);
expect(query).to.have.property('type', job._type);
expect(query).to.have.property('id', job._id);
expect(query).to.have.property('version', job._version);
expect(query).to.have.property('if_seq_no', job._seq_no);
expect(query).to.have.property('if_primary_term', job._primary_term);
expect(query.body.doc).to.have.property('output');
expect(query.body.doc.output).to.have.property('content_type', false);
expect(query.body.doc.output).to.have.property('content', payload);

View file

@ -82,7 +82,8 @@ export class Job extends events.EventEmitter {
id: doc._id,
type: doc._type,
index: doc._index,
version: doc._version,
_seq_no: doc._seq_no,
_primary_term: doc._primary_term,
};
this.debug(`Job created in index ${this.index}`);
@ -118,7 +119,8 @@ export class Job extends events.EventEmitter {
index: doc._index,
id: doc._id,
type: doc._type,
version: doc._version,
_seq_no: doc._seq_no,
_primary_term: doc._primary_term,
});
});
}

View file

@ -130,7 +130,8 @@ export class Worker extends events.EventEmitter {
index: job._index,
type: job._type,
id: job._id,
version: job._version,
if_seq_no: job._seq_no,
if_primary_term: job._primary_term,
body: { doc }
})
.then((response) => {
@ -167,7 +168,8 @@ export class Worker extends events.EventEmitter {
index: job._index,
type: job._type,
id: job._id,
version: job._version,
if_seq_no: job._seq_no,
if_primary_term: job._primary_term,
body: { doc }
})
.then(() => true)
@ -244,7 +246,8 @@ export class Worker extends events.EventEmitter {
index: job._index,
type: job._type,
id: job._id,
version: job._version,
if_seq_no: job._seq_no,
if_primary_term: job._primary_term,
body: { doc }
})
.then(() => {
@ -351,6 +354,7 @@ export class Worker extends events.EventEmitter {
_getPendingJobs() {
const nowTime = moment().toISOString();
const query = {
seq_no_primary_term: true,
_source: {
excludes: [ 'output.content' ]
},
@ -385,7 +389,6 @@ export class Worker extends events.EventEmitter {
return this.client.search({
index: `${this.queue.index}-*`,
type: this.doctype,
version: true,
body: query
})
.then((results) => {

View file

@ -215,7 +215,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClient {
* @param {string} type
* @param {string} id
* @param {object} [options={}]
* @property {integer} options.version - ensures version matches that of persisted object
* @property {string} options.version - ensures version matches that of persisted object
* @property {string} [options.namespace]
* @returns {promise}
*/

View file

@ -20,7 +20,8 @@ const getMockTaskInstance = () => ({
const getMockConcreteTaskInstance = () => {
const concrete: {
id: string;
version: number;
sequenceNumber: number;
primaryTerm: number;
attempts: number;
status: TaskStatus;
runAt: Date;
@ -30,7 +31,8 @@ const getMockConcreteTaskInstance = () => {
params: any;
} = {
id: 'hy8o99o83',
version: 1,
sequenceNumber: 1,
primaryTerm: 1,
attempts: 0,
status: 'idle',
runAt: new Date(moment('2018-09-18T05:33:09.588Z').valueOf()),
@ -150,12 +152,13 @@ Object {
"params": Object {
"abc": "def",
},
"primaryTerm": 1,
"runAt": 2018-09-18T05:33:09.588Z,
"scheduledAt": 2018-09-18T05:33:09.588Z,
"sequenceNumber": 1,
"state": Object {},
"status": "idle",
"taskType": "nice_task",
"version": 1,
},
}
`);

View file

@ -212,9 +212,14 @@ export interface ConcreteTaskInstance extends TaskInstance {
id: string;
/**
* The version of the Elaticsearch document.
* The sequence number from the Elaticsearch document.
*/
version: number;
sequenceNumber: number;
/**
* The primary term from the Elaticsearch document.
*/
primaryTerm: number;
/**
* The date and time that this task was originally scheduled. This is used

View file

@ -230,7 +230,8 @@ describe('TaskManagerRunner', () => {
{
id: 'foo',
taskType: 'bar',
version: 32,
sequenceNumber: 32,
primaryTerm: 32,
runAt: new Date(),
scheduledAt: new Date(),
attempts: 0,

View file

@ -86,7 +86,8 @@ describe('TaskStore', () => {
callCluster.withArgs('index').returns(
Promise.resolve({
_id: 'testid',
_version: 3344,
_seq_no: 3344,
_primary_term: 3344,
})
);
callCluster.withArgs('indices.getTemplate').returns(Promise.resolve({ tasky: {} }));
@ -138,7 +139,8 @@ describe('TaskStore', () => {
expect(result).toMatchObject({
...task,
version: 3344,
sequenceNumber: 3344,
primaryTerm: 3344,
id: 'testid',
});
});
@ -307,7 +309,8 @@ describe('TaskStore', () => {
status: 'idle',
taskType: 'foo',
user: 'jimbo',
version: undefined,
sequenceNumber: undefined,
primaryTerm: undefined,
},
{
attempts: 2,
@ -320,7 +323,8 @@ describe('TaskStore', () => {
status: 'running',
taskType: 'bar',
user: 'dabo',
version: undefined,
sequenceNumber: undefined,
primaryTerm: undefined,
},
],
searchAfter: ['b', 2],
@ -401,7 +405,7 @@ describe('TaskStore', () => {
},
size: 10,
sort: { 'task.runAt': { order: 'asc' } },
version: true,
seq_no_primary_term: true,
},
index,
type: '_doc',
@ -462,7 +466,8 @@ describe('TaskStore', () => {
status: 'idle',
taskType: 'foo',
user: 'jimbo',
version: undefined,
sequenceNumber: undefined,
primaryTerm: undefined,
},
{
attempts: 2,
@ -475,7 +480,8 @@ describe('TaskStore', () => {
status: 'running',
taskType: 'bar',
user: 'dabo',
version: undefined,
sequenceNumber: undefined,
primaryTerm: undefined,
},
]);
});
@ -491,12 +497,17 @@ describe('TaskStore', () => {
params: { hello: 'world' },
state: { foo: 'bar' },
taskType: 'report',
version: 2,
sequenceNumber: 2,
primaryTerm: 2,
attempts: 3,
status: 'idle' as TaskStatus,
};
const callCluster = sinon.spy(async () => ({ _version: task.version + 1 }));
const callCluster = sinon.spy(async () => ({
_seq_no: task.sequenceNumber + 1,
_primary_term: task.primaryTerm + 1,
}));
const store = new TaskStore({
callCluster,
getKibanaUuid,
@ -515,12 +526,13 @@ describe('TaskStore', () => {
id: task.id,
index: 'tasky',
type: '_doc',
version: 2,
if_seq_no: 2,
if_primary_term: 2,
refresh: true,
body: {
doc: {
task: {
...['id', 'version'].reduce((acc, prop) => _.omit(acc, prop), task),
..._.omit(task, ['id', 'sequenceNumber', 'primaryTerm']),
params: JSON.stringify(task.params),
state: JSON.stringify(task.state),
},
@ -528,7 +540,11 @@ describe('TaskStore', () => {
},
});
expect(result).toEqual({ ...task, version: 3 });
expect(result).toEqual({
...task,
sequenceNumber: 3,
primaryTerm: 3,
});
});
});
@ -539,7 +555,8 @@ describe('TaskStore', () => {
Promise.resolve({
_index: 'myindex',
_id: id,
_version: 32,
_seq_no: 32,
_primary_term: 32,
result: 'deleted',
})
);
@ -559,7 +576,8 @@ describe('TaskStore', () => {
expect(result).toEqual({
id,
index: 'myindex',
version: 32,
sequenceNumber: 32,
primaryTerm: 32,
result: 'deleted',
});

View file

@ -40,7 +40,8 @@ export interface FetchResult {
export interface RemoveResult {
index: string;
id: string;
version: string;
sequenceNumber: number;
primaryTerm: number;
result: string;
}
@ -49,7 +50,8 @@ export interface RawTaskDoc {
_id: string;
_index: string;
_type: string;
_version: number;
_seq_no: number;
_primary_term: number;
_source: {
type: string;
kibana: {
@ -255,7 +257,8 @@ export class TaskStore {
return {
...taskInstance,
id: result._id,
version: result._version,
sequenceNumber: result._seq_no,
primaryTerm: result._primary_term,
attempts: 0,
status: task.status,
scheduledAt: task.scheduledAt,
@ -303,7 +306,7 @@ export class TaskStore {
},
size: 10,
sort: { 'task.runAt': { order: 'asc' } },
version: true,
seq_no_primary_term: true,
});
return docs;
@ -319,14 +322,15 @@ export class TaskStore {
public async update(doc: ConcreteTaskInstance): Promise<ConcreteTaskInstance> {
const rawDoc = taskDocToRaw(doc, this);
const { _version } = await this.callCluster('update', {
const result = await this.callCluster('update', {
body: {
doc: rawDoc._source,
},
id: doc.id,
index: this.index,
type: DOC_TYPE,
version: doc.version,
if_seq_no: doc.sequenceNumber,
if_primary_term: doc.primaryTerm,
// The refresh is important so that if we immediately look for work,
// we don't pick up this task.
refresh: true,
@ -334,7 +338,8 @@ export class TaskStore {
return {
...doc,
version: _version,
sequenceNumber: result._seq_no,
primaryTerm: result._primary_term,
};
}
@ -357,7 +362,8 @@ export class TaskStore {
return {
index: result._index,
id: result._id,
version: result._version,
sequenceNumber: result._seq_no,
primaryTerm: result._primary_term,
result: result.result,
};
}
@ -415,7 +421,8 @@ function rawSource(doc: TaskInstance, store: TaskStore) {
};
delete (source as any).id;
delete (source as any).version;
delete (source as any).sequenceNumber;
delete (source as any).primaryTerm;
delete (source as any).type;
return {
@ -438,7 +445,8 @@ function taskDocToRaw(doc: ConcreteTaskInstance, store: TaskStore): RawTaskDoc {
_index: store.index,
_source: { type, task, kibana },
_type: DOC_TYPE,
_version: doc.version,
_seq_no: doc.sequenceNumber,
_primary_term: doc.primaryTerm,
};
}
@ -446,7 +454,8 @@ function rawToTaskDoc(doc: RawTaskDoc): ConcreteTaskInstance {
return {
...doc._source.task,
id: doc._id,
version: doc._version,
sequenceNumber: doc._seq_no,
primaryTerm: doc._primary_term,
params: parseJSONField(doc._source.task.params, 'params', doc),
state: parseJSONField(doc._source.task.state, 'state', doc),
};

View file

@ -119,7 +119,7 @@ describe('ReindexActions', () => {
type: REINDEX_OP_TYPE,
id: '9',
attributes: { indexName: 'hi', locked: moment().format() },
version: 1,
version: 'foo',
} as ReindexSavedObject,
{ newIndexName: 'test' }
);
@ -129,7 +129,7 @@ describe('ReindexActions', () => {
expect(args[1]).toEqual('9');
expect(args[2].indexName).toEqual('hi');
expect(args[2].newIndexName).toEqual('test');
expect(args[3]).toEqual({ version: 1 });
expect(args[3]).toEqual({ version: 'foo' });
});
it('throws if the reindexOp is not locked', async () => {
@ -139,7 +139,7 @@ describe('ReindexActions', () => {
type: REINDEX_OP_TYPE,
id: '10',
attributes: { indexName: 'hi', locked: null },
version: 1,
version: 'foo',
} as ReindexSavedObject,
{ newIndexName: 'test' }
)

View file

@ -73,7 +73,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version, updatedAt, configuration, status } =
response.data && response.data.createSource.source;
expect(version).to.be.greaterThan(0);
expect(version).to.be.a('string');
expect(updatedAt).to.be.greaterThan(0);
expect(configuration.name).to.be('NAME');
expect(configuration.description).to.be('DESCRIPTION');
@ -102,7 +102,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version, updatedAt, configuration, status } =
response.data && response.data.createSource.source;
expect(version).to.be.greaterThan(0);
expect(version).to.be.a('string');
expect(updatedAt).to.be.greaterThan(0);
expect(configuration.name).to.be('NAME');
expect(configuration.description).to.be('');
@ -163,7 +163,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version } = creationResponse.data && creationResponse.data.createSource.source;
expect(version).to.be.greaterThan(0);
expect(version).to.be.a('string');
const deletionResponse = await client.mutate<any>({
mutation: deleteSourceMutation,
@ -193,7 +193,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version: initialVersion, updatedAt: createdAt } =
creationResponse.data && creationResponse.data.createSource.source;
expect(initialVersion).to.be.greaterThan(0);
expect(initialVersion).to.be.a('string');
expect(createdAt).to.be.greaterThan(0);
const updateResponse = await client.mutate<any>({
@ -233,7 +233,8 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version, updatedAt, configuration, status } =
updateResponse.data && updateResponse.data.updateSource.source;
expect(version).to.be.greaterThan(initialVersion);
expect(version).to.be.a('string');
expect(version).to.not.be(initialVersion);
expect(updatedAt).to.be.greaterThan(createdAt);
expect(configuration.name).to.be('UPDATED_NAME');
expect(configuration.description).to.be('UPDATED_DESCRIPTION');
@ -262,7 +263,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version: initialVersion, updatedAt: createdAt } =
creationResponse.data && creationResponse.data.createSource.source;
expect(initialVersion).to.be.greaterThan(0);
expect(initialVersion).to.be.a('string');
expect(createdAt).to.be.greaterThan(0);
const updateResponse = await client.mutate<any>({
@ -282,7 +283,8 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version, updatedAt, configuration, status } =
updateResponse.data && updateResponse.data.updateSource.source;
expect(version).to.be.greaterThan(initialVersion);
expect(version).to.be.a('string');
expect(version).to.not.be(initialVersion);
expect(updatedAt).to.be.greaterThan(createdAt);
expect(configuration.metricAlias).to.be('metricbeat-**');
expect(configuration.logAlias).to.be('filebeat-*,kibana_sample_data_logs*');
@ -304,7 +306,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version: initialVersion, updatedAt: createdAt } =
creationResponse.data && creationResponse.data.createSource.source;
expect(initialVersion).to.be.greaterThan(0);
expect(initialVersion).to.be.a('string');
expect(createdAt).to.be.greaterThan(0);
const updateResponse = await client.mutate<any>({
@ -324,7 +326,8 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
const { version, updatedAt, configuration } =
updateResponse.data && updateResponse.data.updateSource.source;
expect(version).to.be.greaterThan(initialVersion);
expect(version).to.be.a('string');
expect(version).to.not.be(initialVersion);
expect(updatedAt).to.be.greaterThan(createdAt);
expect(configuration.fields.container).to.be('UPDATED_CONTAINER');
expect(configuration.fields.host).to.be('host.name');

View file

@ -93,7 +93,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest:
type: 'dashboard',
id: `${getIdPrefix(spaceId)}a01b2f57-fcfd-4864-b735-09e28f0d815e`,
updated_at: resp.body.saved_objects[1].updated_at,
version: 1,
version: resp.body.saved_objects[1].version,
attributes: {
title: 'A great new dashboard',
},
@ -102,7 +102,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest:
type: 'globaltype',
id: `05976c65-1145-4858-bbf0-d225cc78a06e`,
updated_at: resp.body.saved_objects[2].updated_at,
version: 1,
version: resp.body.saved_objects[2].version,
attributes: {
name: 'A new globaltype object',
},

View file

@ -69,7 +69,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe
id: resp.body.id,
type: spaceAwareType,
updated_at: resp.body.updated_at,
version: 1,
version: resp.body.version,
attributes: {
title: 'My favorite vis',
},
@ -109,7 +109,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe
id: resp.body.id,
type: notSpaceAwareType,
updated_at: resp.body.updated_at,
version: 1,
version: resp.body.version,
attributes: {
name: `Can't be contained to a space`,
},

View file

@ -72,7 +72,7 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>)
{
type: 'globaltype',
id: `8121a00-8efd-21e7-1cb3-34ab966434445`,
version: 1,
version: resp.body.saved_objects[0].version,
attributes: {
name: 'My favorite global object',
},
@ -104,7 +104,7 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>)
{
type: 'visualization',
id: `${getIdPrefix(spaceId)}dd7caf20-9efd-11e7-acb3-3dab96693fab`,
version: 1,
version: resp.body.saved_objects[0].version,
attributes: {
title: 'Count of requests',
},

View file

@ -83,7 +83,7 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
id: resp.body.id,
type: 'globaltype',
updated_at: resp.body.updated_at,
version: 2,
version: resp.body.version,
attributes: {
name: 'My second favorite',
},
@ -107,7 +107,7 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
id: resp.body.id,
type: 'visualization',
updated_at: resp.body.updated_at,
version: 2,
version: resp.body.version,
attributes: {
title: 'My second favorite vis',
},