Clean up client-side saved object data loading infrastructure (#30241)
This commit is contained in:
parent
97684d176e
commit
6b0fa77fcb
|
@ -18,13 +18,13 @@
|
|||
*/
|
||||
|
||||
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||
import { SavedObject } from 'ui/saved_objects';
|
||||
import { SimpleSavedObject } from 'ui/saved_objects';
|
||||
|
||||
export function FixturesStubbedSavedObjectIndexPatternProvider(Private) {
|
||||
const mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||
|
||||
return function (id) {
|
||||
return new SavedObject(undefined, {
|
||||
return new SimpleSavedObject(undefined, {
|
||||
id,
|
||||
type: 'index-pattern',
|
||||
attributes: {
|
||||
|
|
|
@ -21,7 +21,7 @@ import angular from 'angular';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import { createDashboardEditUrl } from '../dashboard_constants';
|
||||
import { createLegacyClass } from 'ui/utils/legacy_class';
|
||||
import { SavedObjectProvider } from 'ui/courier';
|
||||
import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
|
||||
import {
|
||||
extractReferences,
|
||||
injectReferences,
|
||||
|
|
|
@ -20,9 +20,8 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import './saved_dashboard';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { SavedObjectLoader } from 'ui/courier/saved_object/saved_object_loader';
|
||||
import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { savedObjectManagementRegistry } from '../../management/saved_object_registry';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
const module = uiModules.get('app/dashboard');
|
||||
|
||||
|
@ -38,5 +37,5 @@ savedObjectManagementRegistry.register({
|
|||
// This is the only thing that gets injected into controllers
|
||||
module.service('savedDashboards', function (Private, SavedDashboard, kbnIndex, kbnUrl, $http, chrome) {
|
||||
const savedObjectClient = Private(SavedObjectsClientProvider);
|
||||
return new SavedObjectLoader(SavedDashboard, kbnIndex, kbnUrl, $http, chrome, savedObjectClient);
|
||||
return new SavedObjectLoader(SavedDashboard, kbnUrl, chrome, savedObjectClient);
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ import 'ui/private';
|
|||
import '../../components/field_chooser/field_chooser';
|
||||
import FixturesHitsProvider from 'fixtures/hits';
|
||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { SavedObject } from 'ui/saved_objects';
|
||||
import { SimpleSavedObject } from 'ui/saved_objects';
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
|
||||
|
@ -91,9 +91,9 @@ describe('discover field chooser directives', function () {
|
|||
hits = Private(FixturesHitsProvider);
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
indexPatternList = [
|
||||
new SavedObject(undefined, { id: '0', attributes: { title: 'b' } }),
|
||||
new SavedObject(undefined, { id: '1', attributes: { title: 'a' } }),
|
||||
new SavedObject(undefined, { id: '2', attributes: { title: 'c' } })
|
||||
new SimpleSavedObject(undefined, { id: '0', attributes: { title: 'b' } }),
|
||||
new SimpleSavedObject(undefined, { id: '1', attributes: { title: 'a' } }),
|
||||
new SimpleSavedObject(undefined, { id: '2', attributes: { title: 'c' } })
|
||||
];
|
||||
|
||||
const fieldCounts = _.transform(hits, function (counts, hit) {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import 'ui/notify';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { createLegacyClass } from 'ui/utils/legacy_class';
|
||||
import { SavedObjectProvider } from 'ui/courier';
|
||||
import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
|
||||
|
||||
const module = uiModules.get('discover/saved_searches', [
|
||||
'kibana/notify',
|
||||
|
|
|
@ -20,9 +20,8 @@
|
|||
import './_saved_search';
|
||||
import 'ui/notify';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { SavedObjectLoader } from 'ui/courier/saved_object/saved_object_loader';
|
||||
import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { savedObjectManagementRegistry } from '../../management/saved_object_registry';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
const module = uiModules.get('discover/saved_searches', [
|
||||
'kibana/notify'
|
||||
]);
|
||||
|
@ -36,7 +35,7 @@ savedObjectManagementRegistry.register({
|
|||
|
||||
module.service('savedSearches', function (Private, Promise, config, kbnIndex, createNotifier, SavedSearch, kbnUrl, $http, chrome) {
|
||||
const savedObjectClient = Private(SavedObjectsClientProvider);
|
||||
const savedSearchLoader = new SavedObjectLoader(SavedSearch, kbnIndex, kbnUrl, $http, chrome, savedObjectClient);
|
||||
const savedSearchLoader = new SavedObjectLoader(SavedSearch, kbnUrl, chrome, savedObjectClient);
|
||||
// Customize loader properties since adding an 's' on type doesn't work for type 'search' .
|
||||
savedSearchLoader.loaderProperties = {
|
||||
name: 'searches',
|
||||
|
|
|
@ -30,7 +30,7 @@ import { uiModules } from 'ui/modules';
|
|||
import { updateOldState } from 'ui/vis/vis_update_state';
|
||||
import { VisualizeConstants } from '../visualize_constants';
|
||||
import { createLegacyClass } from 'ui/utils/legacy_class';
|
||||
import { SavedObjectProvider } from 'ui/courier';
|
||||
import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
|
||||
import {
|
||||
extractReferences,
|
||||
injectReferences,
|
||||
|
|
|
@ -20,9 +20,8 @@
|
|||
import './_saved_vis';
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { SavedObjectLoader } from 'ui/courier/saved_object/saved_object_loader';
|
||||
import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { savedObjectManagementRegistry } from '../../management/saved_object_registry';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
const app = uiModules.get('app/visualize');
|
||||
|
||||
|
@ -37,7 +36,7 @@ app.service('savedVisualizations', function (Promise, kbnIndex, SavedVis, Privat
|
|||
const visTypes = Private(VisTypesRegistryProvider);
|
||||
|
||||
const savedObjectClient = Private(SavedObjectsClientProvider);
|
||||
const saveVisualizationLoader = new SavedObjectLoader(SavedVis, kbnIndex, kbnUrl, $http, chrome, savedObjectClient);
|
||||
const saveVisualizationLoader = new SavedObjectLoader(SavedVis, kbnUrl, chrome, savedObjectClient);
|
||||
|
||||
saveVisualizationLoader.mapHitSource = function (source, id) {
|
||||
source.id = id;
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { createLegacyClass } from 'ui/utils/legacy_class';
|
||||
import { SavedObjectProvider } from 'ui/courier';
|
||||
import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
|
||||
const module = uiModules.get('app/timelion');
|
||||
|
||||
// Used only by the savedSheets service, usually no reason to change this
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { SavedObjectLoader } from 'ui/courier/saved_object/saved_object_loader';
|
||||
import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { savedObjectManagementRegistry } from 'plugins/kibana/management/saved_object_registry';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import './_saved_sheet.js';
|
||||
|
||||
|
@ -35,7 +34,7 @@ savedObjectManagementRegistry.register({
|
|||
// This is the only thing that gets injected into controllers
|
||||
module.service('savedSheets', function (Private, Promise, SavedSheet, kbnIndex, kbnUrl, $http, chrome) {
|
||||
const savedObjectClient = Private(SavedObjectsClientProvider);
|
||||
const savedSheetLoader = new SavedObjectLoader(SavedSheet, kbnIndex, kbnUrl, $http, chrome, savedObjectClient);
|
||||
const savedSheetLoader = new SavedObjectLoader(SavedSheet, kbnUrl, chrome, savedObjectClient);
|
||||
savedSheetLoader.urlFor = function (id) {
|
||||
return kbnUrl.eval('#/{{id}}', { id: id });
|
||||
};
|
||||
|
|
|
@ -50,7 +50,7 @@ import '../tooltip';
|
|||
import '../url';
|
||||
import '../validate_date_interval';
|
||||
import '../watch_multi';
|
||||
import '../courier/saved_object/ui/saved_object_save_as_checkbox';
|
||||
import '../saved_objects/ui/saved_object_save_as_checkbox';
|
||||
import '../react_components';
|
||||
import '../i18n';
|
||||
import '../query_bar/directive';
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import './courier';
|
||||
|
||||
export { SavedObjectProvider } from './saved_object';
|
||||
export { SearchSourceProvider } from './search_source';
|
||||
|
||||
export {
|
||||
|
|
|
@ -1,823 +0,0 @@
|
|||
/*
|
||||
* 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 ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import BluebirdPromise from 'bluebird';
|
||||
|
||||
import { SavedObjectProvider } from '../saved_object';
|
||||
import { IndexPatternProvider } from '../../../index_patterns/_index_pattern';
|
||||
import { SavedObjectsClientProvider } from '../../../saved_objects';
|
||||
import { StubIndexPatternsApiClientModule } from '../../../index_patterns/__tests__/stub_index_patterns_api_client';
|
||||
import { InvalidJSONProperty } from '../../../errors';
|
||||
|
||||
describe('Saved Object', function () {
|
||||
require('test_utils/no_digest_promises').activateForSuite();
|
||||
|
||||
let SavedObject;
|
||||
let IndexPattern;
|
||||
let esDataStub;
|
||||
let savedObjectsClientStub;
|
||||
let window;
|
||||
|
||||
/**
|
||||
* Returns a fake doc response with the given index and id, of type dashboard
|
||||
* 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: string}}
|
||||
*/
|
||||
function getMockedDocResponse(indexPatternId, additionalOptions = {}) {
|
||||
return {
|
||||
type: 'dashboard',
|
||||
id: indexPatternId,
|
||||
_version: 'foo',
|
||||
attributes: {},
|
||||
...additionalOptions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubs some of the es retrieval calls so it returns the given response.
|
||||
* @param {Object} mockDocResponse
|
||||
*/
|
||||
function stubESResponse(mockDocResponse) {
|
||||
// Stub out search for duplicate title:
|
||||
sinon.stub(savedObjectsClientStub, 'get').returns(BluebirdPromise.resolve(mockDocResponse));
|
||||
sinon.stub(savedObjectsClientStub, 'update').returns(BluebirdPromise.resolve(mockDocResponse));
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'find').returns(BluebirdPromise.resolve({ savedObjects: [], total: 0 }));
|
||||
sinon.stub(savedObjectsClientStub, 'bulkGet').returns(BluebirdPromise.resolve({ savedObjects: [mockDocResponse] }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new saved object with the given configuration and initializes it.
|
||||
* Returns the promise that will be completed when the initialization finishes.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @returns {Promise<SavedObject>} A promise that resolves with an instance of
|
||||
* SavedObject
|
||||
*/
|
||||
function createInitializedSavedObject(config = {}) {
|
||||
const savedObject = new SavedObject(config);
|
||||
savedObject.title = 'my saved object';
|
||||
return savedObject.init();
|
||||
}
|
||||
|
||||
const mock409FetchError = {
|
||||
res: { status: 409 }
|
||||
};
|
||||
|
||||
beforeEach(ngMock.module(
|
||||
'kibana',
|
||||
StubIndexPatternsApiClientModule,
|
||||
// Use the native window.confirm instead of our specialized version to make testing
|
||||
// this easier.
|
||||
function ($provide) {
|
||||
const overrideConfirm = message => window.confirm(message) ? Promise.resolve() : Promise.reject();
|
||||
$provide.decorator('confirmModalPromise', () => overrideConfirm);
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(ngMock.inject(function (es, Private, $window) {
|
||||
SavedObject = Private(SavedObjectProvider);
|
||||
IndexPattern = Private(IndexPatternProvider);
|
||||
esDataStub = es;
|
||||
savedObjectsClientStub = Private(SavedObjectsClientProvider);
|
||||
window = $window;
|
||||
}));
|
||||
|
||||
describe('save', function () {
|
||||
describe('with confirmOverwrite', function () {
|
||||
function stubConfirmOverwrite() {
|
||||
window.confirm = sinon.stub().returns(true);
|
||||
sinon.stub(esDataStub, 'create').returns(BluebirdPromise.reject(mock409FetchError));
|
||||
}
|
||||
|
||||
describe('when true', function () {
|
||||
it('requests confirmation and updates on yes response', function () {
|
||||
stubESResponse(getMockedDocResponse('myId'));
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
|
||||
const createStub = sinon.stub(savedObjectsClientStub, 'create');
|
||||
createStub.onFirstCall().returns(BluebirdPromise.reject(mock409FetchError));
|
||||
createStub.onSecondCall().returns(BluebirdPromise.resolve({ id: 'myId' }));
|
||||
|
||||
stubConfirmOverwrite();
|
||||
|
||||
savedObject.lastSavedTitle = 'original title';
|
||||
savedObject.title = 'new title';
|
||||
return savedObject.save({ confirmOverwrite: true })
|
||||
.then(() => {
|
||||
expect(window.confirm.called).to.be(true);
|
||||
expect(savedObject.id).to.be('myId');
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
expect(savedObject.lastSavedTitle).to.be('new title');
|
||||
expect(savedObject.title).to.be('new title');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not update on no response', function () {
|
||||
stubESResponse(getMockedDocResponse('HI'));
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'HI' }).then(savedObject => {
|
||||
window.confirm = sinon.stub().returns(false);
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'create').returns(BluebirdPromise.reject(mock409FetchError));
|
||||
|
||||
savedObject.lastSavedTitle = 'original title';
|
||||
savedObject.title = 'new title';
|
||||
return savedObject.save({ confirmOverwrite: true })
|
||||
.then(() => {
|
||||
expect(savedObject.id).to.be('HI');
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
expect(savedObject.lastSavedTitle).to.be('original title');
|
||||
expect(savedObject.title).to.be('new title');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles create failures', function () {
|
||||
stubESResponse(getMockedDocResponse('myId'));
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
|
||||
stubConfirmOverwrite();
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'create').returns(BluebirdPromise.reject(mock409FetchError));
|
||||
|
||||
return savedObject.save({ confirmOverwrite: true })
|
||||
.then(() => {
|
||||
expect(true).to.be(false); // Force failure, the save should not succeed.
|
||||
})
|
||||
.catch(() => {
|
||||
expect(window.confirm.called).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('when false does not request overwrite', function () {
|
||||
const mockDocResponse = getMockedDocResponse('myId');
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
|
||||
stubConfirmOverwrite();
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'create').returns(BluebirdPromise.resolve({ id: 'myId' }));
|
||||
|
||||
return savedObject.save({ confirmOverwrite: false }).then(() => {
|
||||
expect(window.confirm.called).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with copyOnSave', function () {
|
||||
it('as true creates a copy on save success', function () {
|
||||
const mockDocResponse = getMockedDocResponse('myId');
|
||||
stubESResponse(mockDocResponse);
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({ type: 'dashboard', id: 'newUniqueId' });
|
||||
});
|
||||
|
||||
savedObject.copyOnSave = true;
|
||||
return savedObject.save().then((id) => {
|
||||
expect(id).to.be('newUniqueId');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('as true does not create a copy when save fails', function () {
|
||||
const originalId = 'id1';
|
||||
const mockDocResponse = getMockedDocResponse(originalId);
|
||||
stubESResponse(mockDocResponse);
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: originalId }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.reject('simulated error');
|
||||
});
|
||||
savedObject.copyOnSave = true;
|
||||
return savedObject.save().then(() => {
|
||||
throw new Error('Expected a rejection');
|
||||
}).catch(() => {
|
||||
expect(savedObject.id).to.be(originalId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('as false does not create a copy', function () {
|
||||
const id = 'myId';
|
||||
const mockDocResponse = getMockedDocResponse(id);
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: id }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
expect(savedObject.id).to.be(id);
|
||||
return BluebirdPromise.resolve(id);
|
||||
});
|
||||
savedObject.copyOnSave = false;
|
||||
return savedObject.save().then((id) => {
|
||||
expect(id).to.be(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('returns id from server on success', function () {
|
||||
return createInitializedSavedObject({ type: 'dashboard' }).then(savedObject => {
|
||||
const mockDocResponse = getMockedDocResponse('myId');
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
type: 'dashboard',
|
||||
id: 'myId',
|
||||
_version: 'foo'
|
||||
});
|
||||
});
|
||||
|
||||
stubESResponse(mockDocResponse);
|
||||
return savedObject.save().then(id => {
|
||||
expect(id).to.be('myId');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updates isSaving variable', function () {
|
||||
it('on success', function () {
|
||||
const id = 'id';
|
||||
stubESResponse(getMockedDocResponse(id));
|
||||
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: id }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
expect(savedObject.isSaving).to.be(true);
|
||||
return BluebirdPromise.resolve({
|
||||
type: 'dashboard',
|
||||
id,
|
||||
version: 'foo'
|
||||
});
|
||||
});
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
return savedObject.save().then(() => {
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('on failure', function () {
|
||||
stubESResponse(getMockedDocResponse('id'));
|
||||
return createInitializedSavedObject({ type: 'dashboard' }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
expect(savedObject.isSaving).to.be(true);
|
||||
return BluebirdPromise.reject();
|
||||
});
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
return savedObject.save().catch(() => {
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('to extract references', () => {
|
||||
it('when "extractReferences" function when passed in', async () => {
|
||||
const id = '123';
|
||||
stubESResponse(getMockedDocResponse(id));
|
||||
const extractReferences = ({ attributes, references }) => {
|
||||
references.push({
|
||||
name: 'test',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index',
|
||||
});
|
||||
return { attributes, references };
|
||||
};
|
||||
return createInitializedSavedObject({ type: 'dashboard', extractReferences })
|
||||
.then((savedObject) => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
id,
|
||||
version: 'foo',
|
||||
type: 'dashboard',
|
||||
});
|
||||
});
|
||||
return savedObject
|
||||
.save()
|
||||
.then(() => {
|
||||
const { references } = savedObjectsClientStub.create.getCall(0).args[2];
|
||||
expect(references).to.have.length(1);
|
||||
expect(references[0]).to.eql({
|
||||
name: 'test',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('when index exists in searchSourceJSON', () => {
|
||||
const id = '123';
|
||||
stubESResponse(getMockedDocResponse(id));
|
||||
return createInitializedSavedObject({ type: 'dashboard', searchSource: true })
|
||||
.then((savedObject) => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
id,
|
||||
version: 2,
|
||||
type: 'dashboard',
|
||||
});
|
||||
});
|
||||
savedObject.searchSource.setField('index', new IndexPattern('my-index', null, []));
|
||||
return savedObject
|
||||
.save()
|
||||
.then(() => {
|
||||
expect(savedObjectsClientStub.create.getCall(0).args[1]).to.eql({
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: JSON.stringify({
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
}),
|
||||
},
|
||||
});
|
||||
const { references } = savedObjectsClientStub.create.getCall(0).args[2];
|
||||
expect(references).to.have.length(1);
|
||||
expect(references[0]).to.eql({
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('when indexes exists in filter of searchSourceJSON', () => {
|
||||
const id = '123';
|
||||
stubESResponse(getMockedDocResponse(id));
|
||||
return createInitializedSavedObject({ type: 'dashboard', searchSource: true })
|
||||
.then((savedObject) => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
id,
|
||||
version: 2,
|
||||
type: 'dashboard',
|
||||
});
|
||||
});
|
||||
savedObject.searchSource.setField('filter', [{
|
||||
meta: {
|
||||
index: 'my-index',
|
||||
}
|
||||
}]);
|
||||
return savedObject
|
||||
.save()
|
||||
.then(() => {
|
||||
expect(savedObjectsClientStub.create.getCall(0).args[1]).to.eql({
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: JSON.stringify({
|
||||
filter: [
|
||||
{
|
||||
meta: {
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||
}
|
||||
}
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
const { references } = savedObjectsClientStub.create.getCall(0).args[2];
|
||||
expect(references).to.have.length(1);
|
||||
expect(references[0]).to.eql({
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyESResp', function () {
|
||||
it('throws error if not found', function () {
|
||||
return createInitializedSavedObject({ type: 'dashboard' }).then(savedObject => {
|
||||
const response = {};
|
||||
try {
|
||||
savedObject.applyESResp(response);
|
||||
expect(true).to.be(false);
|
||||
} catch (err) {
|
||||
expect(!!err).to.be(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throws error invalid JSON is detected', async function () {
|
||||
const savedObject = await createInitializedSavedObject({ type: 'dashboard', searchSource: true });
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: '\"{\\n \\\"filter\\\": []\\n}\"'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await savedObject.applyESResp(response);
|
||||
throw new Error('applyESResp should have failed, but did not.');
|
||||
} catch (err) {
|
||||
expect(err instanceof InvalidJSONProperty).to.be(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves original defaults if not overridden', function () {
|
||||
const id = 'anid';
|
||||
const preserveMeValue = 'here to stay!';
|
||||
const config = {
|
||||
defaults: {
|
||||
preserveMe: preserveMeValue
|
||||
},
|
||||
type: 'dashboard',
|
||||
id: id
|
||||
};
|
||||
|
||||
const mockDocResponse = getMockedDocResponse(id);
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
expect(savedObject._source.preserveMe).to.equal(preserveMeValue);
|
||||
const response = { found: true, _source: {} };
|
||||
return savedObject.applyESResp(response);
|
||||
}).then(() => {
|
||||
expect(savedObject._source.preserveMe).to.equal(preserveMeValue);
|
||||
});
|
||||
});
|
||||
|
||||
it('overrides defaults', function () {
|
||||
const id = 'anid';
|
||||
const config = {
|
||||
defaults: {
|
||||
flower: 'rose'
|
||||
},
|
||||
type: 'dashboard',
|
||||
id: id
|
||||
};
|
||||
|
||||
const mockDocResponse = getMockedDocResponse(id);
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
expect(savedObject._source.flower).to.equal('rose');
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
flower: 'orchid'
|
||||
}
|
||||
};
|
||||
return savedObject.applyESResp(response);
|
||||
}).then(() => {
|
||||
expect(savedObject._source.flower).to.equal('orchid');
|
||||
});
|
||||
});
|
||||
|
||||
it('overrides previous _source and default values', function () {
|
||||
const id = 'anid';
|
||||
const config = {
|
||||
defaults: {
|
||||
dinosaurs: {
|
||||
tRex: 'is the scariest'
|
||||
}
|
||||
},
|
||||
type: 'dashboard',
|
||||
id: id
|
||||
};
|
||||
|
||||
const mockDocResponse = getMockedDocResponse(
|
||||
id,
|
||||
{ attributes: { dinosaurs: { tRex: 'is not so bad' }, } });
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: { dinosaurs: { tRex: 'has big teeth' } }
|
||||
};
|
||||
|
||||
return savedObject.applyESResp(response);
|
||||
})
|
||||
.then(() => {
|
||||
expect(savedObject._source.dinosaurs.tRex).to.equal('has big teeth');
|
||||
});
|
||||
});
|
||||
|
||||
it('does not inject references when references array is missing', async () => {
|
||||
const injectReferences = sinon.stub();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
injectReferences,
|
||||
};
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
dinosaurs: { tRex: 'has big teeth' },
|
||||
},
|
||||
};
|
||||
return savedObject.applyESResp(response);
|
||||
})
|
||||
.then(() => {
|
||||
expect(injectReferences).to.have.property('notCalled', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not inject references when references array is empty', async () => {
|
||||
const injectReferences = sinon.stub();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
injectReferences,
|
||||
};
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
dinosaurs: { tRex: 'has big teeth' },
|
||||
},
|
||||
references: [],
|
||||
};
|
||||
return savedObject.applyESResp(response);
|
||||
})
|
||||
.then(() => {
|
||||
expect(injectReferences).to.have.property('notCalled', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('injects references when function is provided and references exist', async () => {
|
||||
const injectReferences = sinon.stub();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
injectReferences,
|
||||
};
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
dinosaurs: { tRex: 'has big teeth' },
|
||||
},
|
||||
references: [{}],
|
||||
};
|
||||
return savedObject.applyESResp(response);
|
||||
})
|
||||
.then(() => {
|
||||
expect(injectReferences).to.have.property('calledOnce', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('injects references from searchSourceJSON', async () => {
|
||||
const savedObject = new SavedObject({ type: 'dashboard', searchSource: true });
|
||||
return savedObject
|
||||
.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: JSON.stringify({
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
filter: [
|
||||
{
|
||||
meta: {
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index-1',
|
||||
},
|
||||
{
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index-2',
|
||||
},
|
||||
],
|
||||
};
|
||||
savedObject.applyESResp(response);
|
||||
expect(savedObject.searchSource.getFields()).to.eql({
|
||||
index: 'my-index-1',
|
||||
filter: [
|
||||
{
|
||||
meta: {
|
||||
index: 'my-index-2',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe ('config', function () {
|
||||
|
||||
it('afterESResp is called', function () {
|
||||
const afterESRespCallback = sinon.spy();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
afterESResp: afterESRespCallback
|
||||
};
|
||||
|
||||
return createInitializedSavedObject(config).then(() => {
|
||||
expect(afterESRespCallback.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('init is called', function () {
|
||||
const initCallback = sinon.spy();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
init: initCallback
|
||||
};
|
||||
|
||||
return createInitializedSavedObject(config).then(() => {
|
||||
expect(initCallback.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchSource', function () {
|
||||
it('when true, creates index', function () {
|
||||
const indexPatternId = 'testIndexPattern';
|
||||
const afterESRespCallback = sinon.spy();
|
||||
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
afterESResp: afterESRespCallback,
|
||||
searchSource: true,
|
||||
indexPattern: indexPatternId
|
||||
};
|
||||
|
||||
stubESResponse({
|
||||
id: indexPatternId,
|
||||
type: 'dashboard',
|
||||
attributes: {
|
||||
title: 'testIndexPattern'
|
||||
},
|
||||
_version: 'foo'
|
||||
});
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
expect(!!savedObject.searchSource.getField('index')).to.be(false);
|
||||
|
||||
return savedObject.init().then(() => {
|
||||
expect(afterESRespCallback.called).to.be(true);
|
||||
const index = savedObject.searchSource.getField('index');
|
||||
expect(index instanceof IndexPattern).to.be(true);
|
||||
expect(index.id).to.equal(indexPatternId);
|
||||
});
|
||||
});
|
||||
|
||||
it('when false, does not create index', function () {
|
||||
const indexPatternId = 'testIndexPattern';
|
||||
const afterESRespCallback = sinon.spy();
|
||||
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
afterESResp: afterESRespCallback,
|
||||
searchSource: false,
|
||||
indexPattern: indexPatternId
|
||||
};
|
||||
|
||||
stubESResponse(getMockedDocResponse(indexPatternId));
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
expect(!!savedObject.searchSource).to.be(false);
|
||||
|
||||
return savedObject.init().then(() => {
|
||||
expect(afterESRespCallback.called).to.be(true);
|
||||
expect(!!savedObject.searchSource).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('type', function () {
|
||||
it('that is not specified throws an error', function () {
|
||||
const config = {};
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
try {
|
||||
savedObject.init();
|
||||
expect(false).to.be(true);
|
||||
} catch (err) {
|
||||
expect(err).to.not.be(null);
|
||||
}
|
||||
});
|
||||
|
||||
it('that is invalid invalid throws an error', function () {
|
||||
const config = { type: 'notypeexists' };
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
try {
|
||||
savedObject.init();
|
||||
expect(false).to.be(true);
|
||||
} catch (err) {
|
||||
expect(err).to.not.be(null);
|
||||
}
|
||||
});
|
||||
|
||||
it('that is valid passes', function () {
|
||||
const config = { type: 'dashboard' };
|
||||
return new SavedObject(config).init();
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaults', function () {
|
||||
|
||||
function getTestDefaultConfig(extraOptions) {
|
||||
return {
|
||||
defaults: { testDefault: 'hi' },
|
||||
type: 'dashboard',
|
||||
...extraOptions
|
||||
};
|
||||
}
|
||||
|
||||
function expectDefaultApplied(config) {
|
||||
return createInitializedSavedObject(config).then((savedObject) => {
|
||||
expect(savedObject.defaults).to.be(config.defaults);
|
||||
});
|
||||
}
|
||||
|
||||
describe('applied to object when id', function () {
|
||||
|
||||
it('is not specified', function () {
|
||||
expectDefaultApplied(getTestDefaultConfig());
|
||||
});
|
||||
|
||||
it('is undefined', function () {
|
||||
expectDefaultApplied(getTestDefaultConfig({ id: undefined }));
|
||||
});
|
||||
|
||||
it('is 0', function () {
|
||||
expectDefaultApplied(getTestDefaultConfig({ id: 0 }));
|
||||
});
|
||||
|
||||
it('is false', function () {
|
||||
expectDefaultApplied(getTestDefaultConfig({ id: false }));
|
||||
});
|
||||
});
|
||||
|
||||
it('applied to source if an id is given', function () {
|
||||
const myId = 'myid';
|
||||
const customDefault = 'hi';
|
||||
const initialOverwriteMeValue = 'this should get overwritten by the server response';
|
||||
|
||||
const config = {
|
||||
defaults: {
|
||||
overwriteMe: initialOverwriteMeValue,
|
||||
customDefault: customDefault
|
||||
},
|
||||
type: 'dashboard',
|
||||
id: myId
|
||||
};
|
||||
|
||||
const serverValue = 'this should override the initial default value given';
|
||||
|
||||
const mockDocResponse = getMockedDocResponse(
|
||||
myId,
|
||||
{ attributes: { overwriteMe: serverValue } });
|
||||
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
return createInitializedSavedObject(config).then((savedObject) => {
|
||||
expect(!!savedObject._source).to.be(true);
|
||||
expect(savedObject.defaults).to.be(config.defaults);
|
||||
expect(savedObject._source.overwriteMe).to.be(serverValue);
|
||||
expect(savedObject._source.customDefault).to.be(customDefault);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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 { SavedObjectProvider } from './saved_object';
|
|
@ -20,7 +20,7 @@
|
|||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
import { findObjectByTitle } from '../find_object_by_title';
|
||||
import { SavedObject } from '../saved_object';
|
||||
import { SimpleSavedObject } from '../simple_saved_object';
|
||||
|
||||
describe('findObjectByTitle', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
@ -38,7 +38,7 @@ describe('findObjectByTitle', () => {
|
|||
});
|
||||
|
||||
it('matches any case', async () => {
|
||||
const indexPattern = new SavedObject(savedObjectsClient, { attributes: { title: 'foo' } });
|
||||
const indexPattern = new SimpleSavedObject(savedObjectsClient, { attributes: { title: 'foo' } });
|
||||
savedObjectsClient.find.returns(Promise.resolve({
|
||||
savedObjects: [indexPattern]
|
||||
}));
|
||||
|
|
|
@ -17,36 +17,807 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
import { SavedObject } from '../saved_object';
|
||||
import sinon from 'sinon';
|
||||
import BluebirdPromise from 'bluebird';
|
||||
|
||||
describe('SavedObject', () => {
|
||||
it('persists type and id', () => {
|
||||
const id = 'logstash-*';
|
||||
const type = 'index-pattern';
|
||||
import { SavedObjectProvider } from '../saved_object';
|
||||
import { IndexPatternProvider } from '../../index_patterns/_index_pattern';
|
||||
import { SavedObjectsClientProvider } from '../saved_objects_client_provider';
|
||||
import { StubIndexPatternsApiClientModule } from '../../index_patterns/__tests__/stub_index_patterns_api_client';
|
||||
import { InvalidJSONProperty } from '../../errors';
|
||||
|
||||
const client = sinon.stub();
|
||||
const savedObject = new SavedObject(client, { id, type });
|
||||
describe('Saved Object', function () {
|
||||
require('test_utils/no_digest_promises').activateForSuite();
|
||||
|
||||
expect(savedObject.id).to.be(id);
|
||||
expect(savedObject.type).to.be(type);
|
||||
let SavedObject;
|
||||
let IndexPattern;
|
||||
let esDataStub;
|
||||
let savedObjectsClientStub;
|
||||
let window;
|
||||
|
||||
/**
|
||||
* Returns a fake doc response with the given index and id, of type dashboard
|
||||
* 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: string}}
|
||||
*/
|
||||
function getMockedDocResponse(indexPatternId, additionalOptions = {}) {
|
||||
return {
|
||||
type: 'dashboard',
|
||||
id: indexPatternId,
|
||||
_version: 'foo',
|
||||
attributes: {},
|
||||
...additionalOptions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubs some of the es retrieval calls so it returns the given response.
|
||||
* @param {Object} mockDocResponse
|
||||
*/
|
||||
function stubESResponse(mockDocResponse) {
|
||||
// Stub out search for duplicate title:
|
||||
sinon.stub(savedObjectsClientStub, 'get').returns(BluebirdPromise.resolve(mockDocResponse));
|
||||
sinon.stub(savedObjectsClientStub, 'update').returns(BluebirdPromise.resolve(mockDocResponse));
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'find').returns(BluebirdPromise.resolve({ savedObjects: [], total: 0 }));
|
||||
sinon.stub(savedObjectsClientStub, 'bulkGet').returns(BluebirdPromise.resolve({ savedObjects: [mockDocResponse] }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new saved object with the given configuration and initializes it.
|
||||
* Returns the promise that will be completed when the initialization finishes.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @returns {Promise<SavedObject>} A promise that resolves with an instance of
|
||||
* SavedObject
|
||||
*/
|
||||
function createInitializedSavedObject(config = {}) {
|
||||
const savedObject = new SavedObject(config);
|
||||
savedObject.title = 'my saved object';
|
||||
return savedObject.init();
|
||||
}
|
||||
|
||||
const mock409FetchError = {
|
||||
res: { status: 409 }
|
||||
};
|
||||
|
||||
beforeEach(ngMock.module(
|
||||
'kibana',
|
||||
StubIndexPatternsApiClientModule,
|
||||
// Use the native window.confirm instead of our specialized version to make testing
|
||||
// this easier.
|
||||
function ($provide) {
|
||||
const overrideConfirm = message => window.confirm(message) ? Promise.resolve() : Promise.reject();
|
||||
$provide.decorator('confirmModalPromise', () => overrideConfirm);
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(ngMock.inject(function (es, Private, $window) {
|
||||
SavedObject = Private(SavedObjectProvider);
|
||||
IndexPattern = Private(IndexPatternProvider);
|
||||
esDataStub = es;
|
||||
savedObjectsClientStub = Private(SavedObjectsClientProvider);
|
||||
window = $window;
|
||||
}));
|
||||
|
||||
describe('save', function () {
|
||||
describe('with confirmOverwrite', function () {
|
||||
function stubConfirmOverwrite() {
|
||||
window.confirm = sinon.stub().returns(true);
|
||||
sinon.stub(esDataStub, 'create').returns(BluebirdPromise.reject(mock409FetchError));
|
||||
}
|
||||
|
||||
describe('when true', function () {
|
||||
it('requests confirmation and updates on yes response', function () {
|
||||
stubESResponse(getMockedDocResponse('myId'));
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
|
||||
const createStub = sinon.stub(savedObjectsClientStub, 'create');
|
||||
createStub.onFirstCall().returns(BluebirdPromise.reject(mock409FetchError));
|
||||
createStub.onSecondCall().returns(BluebirdPromise.resolve({ id: 'myId' }));
|
||||
|
||||
stubConfirmOverwrite();
|
||||
|
||||
savedObject.lastSavedTitle = 'original title';
|
||||
savedObject.title = 'new title';
|
||||
return savedObject.save({ confirmOverwrite: true })
|
||||
.then(() => {
|
||||
expect(window.confirm.called).to.be(true);
|
||||
expect(savedObject.id).to.be('myId');
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
expect(savedObject.lastSavedTitle).to.be('new title');
|
||||
expect(savedObject.title).to.be('new title');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('does not update on no response', function () {
|
||||
stubESResponse(getMockedDocResponse('HI'));
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'HI' }).then(savedObject => {
|
||||
window.confirm = sinon.stub().returns(false);
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'create').returns(BluebirdPromise.reject(mock409FetchError));
|
||||
|
||||
savedObject.lastSavedTitle = 'original title';
|
||||
savedObject.title = 'new title';
|
||||
return savedObject.save({ confirmOverwrite: true })
|
||||
.then(() => {
|
||||
expect(savedObject.id).to.be('HI');
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
expect(savedObject.lastSavedTitle).to.be('original title');
|
||||
expect(savedObject.title).to.be('new title');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('handles create failures', function () {
|
||||
stubESResponse(getMockedDocResponse('myId'));
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
|
||||
stubConfirmOverwrite();
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'create').returns(BluebirdPromise.reject(mock409FetchError));
|
||||
|
||||
return savedObject.save({ confirmOverwrite: true })
|
||||
.then(() => {
|
||||
expect(true).to.be(false); // Force failure, the save should not succeed.
|
||||
})
|
||||
.catch(() => {
|
||||
expect(window.confirm.called).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('when false does not request overwrite', function () {
|
||||
const mockDocResponse = getMockedDocResponse('myId');
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
|
||||
stubConfirmOverwrite();
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'create').returns(BluebirdPromise.resolve({ id: 'myId' }));
|
||||
|
||||
return savedObject.save({ confirmOverwrite: false }).then(() => {
|
||||
expect(window.confirm.called).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with copyOnSave', function () {
|
||||
it('as true creates a copy on save success', function () {
|
||||
const mockDocResponse = getMockedDocResponse('myId');
|
||||
stubESResponse(mockDocResponse);
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: 'myId' }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({ type: 'dashboard', id: 'newUniqueId' });
|
||||
});
|
||||
|
||||
savedObject.copyOnSave = true;
|
||||
return savedObject.save().then((id) => {
|
||||
expect(id).to.be('newUniqueId');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('as true does not create a copy when save fails', function () {
|
||||
const originalId = 'id1';
|
||||
const mockDocResponse = getMockedDocResponse(originalId);
|
||||
stubESResponse(mockDocResponse);
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: originalId }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.reject('simulated error');
|
||||
});
|
||||
savedObject.copyOnSave = true;
|
||||
return savedObject.save().then(() => {
|
||||
throw new Error('Expected a rejection');
|
||||
}).catch(() => {
|
||||
expect(savedObject.id).to.be(originalId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('as false does not create a copy', function () {
|
||||
const id = 'myId';
|
||||
const mockDocResponse = getMockedDocResponse(id);
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: id }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
expect(savedObject.id).to.be(id);
|
||||
return BluebirdPromise.resolve(id);
|
||||
});
|
||||
savedObject.copyOnSave = false;
|
||||
return savedObject.save().then((id) => {
|
||||
expect(id).to.be(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('returns id from server on success', function () {
|
||||
return createInitializedSavedObject({ type: 'dashboard' }).then(savedObject => {
|
||||
const mockDocResponse = getMockedDocResponse('myId');
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
type: 'dashboard',
|
||||
id: 'myId',
|
||||
_version: 'foo'
|
||||
});
|
||||
});
|
||||
|
||||
stubESResponse(mockDocResponse);
|
||||
return savedObject.save().then(id => {
|
||||
expect(id).to.be('myId');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updates isSaving variable', function () {
|
||||
it('on success', function () {
|
||||
const id = 'id';
|
||||
stubESResponse(getMockedDocResponse(id));
|
||||
|
||||
return createInitializedSavedObject({ type: 'dashboard', id: id }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
expect(savedObject.isSaving).to.be(true);
|
||||
return BluebirdPromise.resolve({
|
||||
type: 'dashboard',
|
||||
id,
|
||||
version: 'foo'
|
||||
});
|
||||
});
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
return savedObject.save().then(() => {
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('on failure', function () {
|
||||
stubESResponse(getMockedDocResponse('id'));
|
||||
return createInitializedSavedObject({ type: 'dashboard' }).then(savedObject => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
expect(savedObject.isSaving).to.be(true);
|
||||
return BluebirdPromise.reject();
|
||||
});
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
return savedObject.save().catch(() => {
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('to extract references', () => {
|
||||
it('when "extractReferences" function when passed in', async () => {
|
||||
const id = '123';
|
||||
stubESResponse(getMockedDocResponse(id));
|
||||
const extractReferences = ({ attributes, references }) => {
|
||||
references.push({
|
||||
name: 'test',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index',
|
||||
});
|
||||
return { attributes, references };
|
||||
};
|
||||
return createInitializedSavedObject({ type: 'dashboard', extractReferences })
|
||||
.then((savedObject) => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
id,
|
||||
version: 'foo',
|
||||
type: 'dashboard',
|
||||
});
|
||||
});
|
||||
return savedObject
|
||||
.save()
|
||||
.then(() => {
|
||||
const { references } = savedObjectsClientStub.create.getCall(0).args[2];
|
||||
expect(references).to.have.length(1);
|
||||
expect(references[0]).to.eql({
|
||||
name: 'test',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('when index exists in searchSourceJSON', () => {
|
||||
const id = '123';
|
||||
stubESResponse(getMockedDocResponse(id));
|
||||
return createInitializedSavedObject({ type: 'dashboard', searchSource: true })
|
||||
.then((savedObject) => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
id,
|
||||
version: 2,
|
||||
type: 'dashboard',
|
||||
});
|
||||
});
|
||||
savedObject.searchSource.setField('index', new IndexPattern('my-index', null, []));
|
||||
return savedObject
|
||||
.save()
|
||||
.then(() => {
|
||||
expect(savedObjectsClientStub.create.getCall(0).args[1]).to.eql({
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: JSON.stringify({
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
}),
|
||||
},
|
||||
});
|
||||
const { references } = savedObjectsClientStub.create.getCall(0).args[2];
|
||||
expect(references).to.have.length(1);
|
||||
expect(references[0]).to.eql({
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('when indexes exists in filter of searchSourceJSON', () => {
|
||||
const id = '123';
|
||||
stubESResponse(getMockedDocResponse(id));
|
||||
return createInitializedSavedObject({ type: 'dashboard', searchSource: true })
|
||||
.then((savedObject) => {
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
id,
|
||||
version: 2,
|
||||
type: 'dashboard',
|
||||
});
|
||||
});
|
||||
savedObject.searchSource.setField('filter', [{
|
||||
meta: {
|
||||
index: 'my-index',
|
||||
}
|
||||
}]);
|
||||
return savedObject
|
||||
.save()
|
||||
.then(() => {
|
||||
expect(savedObjectsClientStub.create.getCall(0).args[1]).to.eql({
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: JSON.stringify({
|
||||
filter: [
|
||||
{
|
||||
meta: {
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||
}
|
||||
}
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
const { references } = savedObjectsClientStub.create.getCall(0).args[2];
|
||||
expect(references).to.have.length(1);
|
||||
expect(references[0]).to.eql({
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('persists attributes', () => {
|
||||
const attributes = { title: 'My title' };
|
||||
describe('applyESResp', function () {
|
||||
it('throws error if not found', function () {
|
||||
return createInitializedSavedObject({ type: 'dashboard' }).then(savedObject => {
|
||||
const response = {};
|
||||
try {
|
||||
savedObject.applyESResp(response);
|
||||
expect(true).to.be(false);
|
||||
} catch (err) {
|
||||
expect(!!err).to.be(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const client = sinon.stub();
|
||||
const savedObject = new SavedObject(client, { attributes });
|
||||
it('throws error invalid JSON is detected', async function () {
|
||||
const savedObject = await createInitializedSavedObject({ type: 'dashboard', searchSource: true });
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: '\"{\\n \\\"filter\\\": []\\n}\"'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
expect(savedObject.attributes).to.be(attributes);
|
||||
try {
|
||||
await savedObject.applyESResp(response);
|
||||
throw new Error('applyESResp should have failed, but did not.');
|
||||
} catch (err) {
|
||||
expect(err instanceof InvalidJSONProperty).to.be(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves original defaults if not overridden', function () {
|
||||
const id = 'anid';
|
||||
const preserveMeValue = 'here to stay!';
|
||||
const config = {
|
||||
defaults: {
|
||||
preserveMe: preserveMeValue
|
||||
},
|
||||
type: 'dashboard',
|
||||
id: id
|
||||
};
|
||||
|
||||
const mockDocResponse = getMockedDocResponse(id);
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
expect(savedObject._source.preserveMe).to.equal(preserveMeValue);
|
||||
const response = { found: true, _source: {} };
|
||||
return savedObject.applyESResp(response);
|
||||
}).then(() => {
|
||||
expect(savedObject._source.preserveMe).to.equal(preserveMeValue);
|
||||
});
|
||||
});
|
||||
|
||||
it('overrides defaults', function () {
|
||||
const id = 'anid';
|
||||
const config = {
|
||||
defaults: {
|
||||
flower: 'rose'
|
||||
},
|
||||
type: 'dashboard',
|
||||
id: id
|
||||
};
|
||||
|
||||
const mockDocResponse = getMockedDocResponse(id);
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
expect(savedObject._source.flower).to.equal('rose');
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
flower: 'orchid'
|
||||
}
|
||||
};
|
||||
return savedObject.applyESResp(response);
|
||||
}).then(() => {
|
||||
expect(savedObject._source.flower).to.equal('orchid');
|
||||
});
|
||||
});
|
||||
|
||||
it('overrides previous _source and default values', function () {
|
||||
const id = 'anid';
|
||||
const config = {
|
||||
defaults: {
|
||||
dinosaurs: {
|
||||
tRex: 'is the scariest'
|
||||
}
|
||||
},
|
||||
type: 'dashboard',
|
||||
id: id
|
||||
};
|
||||
|
||||
const mockDocResponse = getMockedDocResponse(
|
||||
id,
|
||||
{ attributes: { dinosaurs: { tRex: 'is not so bad' }, } });
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: { dinosaurs: { tRex: 'has big teeth' } }
|
||||
};
|
||||
|
||||
return savedObject.applyESResp(response);
|
||||
})
|
||||
.then(() => {
|
||||
expect(savedObject._source.dinosaurs.tRex).to.equal('has big teeth');
|
||||
});
|
||||
});
|
||||
|
||||
it('does not inject references when references array is missing', async () => {
|
||||
const injectReferences = sinon.stub();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
injectReferences,
|
||||
};
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
dinosaurs: { tRex: 'has big teeth' },
|
||||
},
|
||||
};
|
||||
return savedObject.applyESResp(response);
|
||||
})
|
||||
.then(() => {
|
||||
expect(injectReferences).to.have.property('notCalled', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not inject references when references array is empty', async () => {
|
||||
const injectReferences = sinon.stub();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
injectReferences,
|
||||
};
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
dinosaurs: { tRex: 'has big teeth' },
|
||||
},
|
||||
references: [],
|
||||
};
|
||||
return savedObject.applyESResp(response);
|
||||
})
|
||||
.then(() => {
|
||||
expect(injectReferences).to.have.property('notCalled', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('injects references when function is provided and references exist', async () => {
|
||||
const injectReferences = sinon.stub();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
injectReferences,
|
||||
};
|
||||
const savedObject = new SavedObject(config);
|
||||
return savedObject.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
dinosaurs: { tRex: 'has big teeth' },
|
||||
},
|
||||
references: [{}],
|
||||
};
|
||||
return savedObject.applyESResp(response);
|
||||
})
|
||||
.then(() => {
|
||||
expect(injectReferences).to.have.property('calledOnce', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('injects references from searchSourceJSON', async () => {
|
||||
const savedObject = new SavedObject({ type: 'dashboard', searchSource: true });
|
||||
return savedObject
|
||||
.init()
|
||||
.then(() => {
|
||||
const response = {
|
||||
found: true,
|
||||
_source: {
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON: JSON.stringify({
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
filter: [
|
||||
{
|
||||
meta: {
|
||||
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index-1',
|
||||
},
|
||||
{
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index',
|
||||
type: 'index-pattern',
|
||||
id: 'my-index-2',
|
||||
},
|
||||
],
|
||||
};
|
||||
savedObject.applyESResp(response);
|
||||
expect(savedObject.searchSource.getFields()).to.eql({
|
||||
index: 'my-index-1',
|
||||
filter: [
|
||||
{
|
||||
meta: {
|
||||
index: 'my-index-2',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('persists version', () => {
|
||||
const version = 2;
|
||||
describe ('config', function () {
|
||||
|
||||
const client = sinon.stub();
|
||||
const savedObject = new SavedObject(client, { version });
|
||||
expect(savedObject._version).to.be(version);
|
||||
it('afterESResp is called', function () {
|
||||
const afterESRespCallback = sinon.spy();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
afterESResp: afterESRespCallback
|
||||
};
|
||||
|
||||
return createInitializedSavedObject(config).then(() => {
|
||||
expect(afterESRespCallback.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('init is called', function () {
|
||||
const initCallback = sinon.spy();
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
init: initCallback
|
||||
};
|
||||
|
||||
return createInitializedSavedObject(config).then(() => {
|
||||
expect(initCallback.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchSource', function () {
|
||||
it('when true, creates index', function () {
|
||||
const indexPatternId = 'testIndexPattern';
|
||||
const afterESRespCallback = sinon.spy();
|
||||
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
afterESResp: afterESRespCallback,
|
||||
searchSource: true,
|
||||
indexPattern: indexPatternId
|
||||
};
|
||||
|
||||
stubESResponse({
|
||||
id: indexPatternId,
|
||||
type: 'dashboard',
|
||||
attributes: {
|
||||
title: 'testIndexPattern'
|
||||
},
|
||||
_version: 'foo'
|
||||
});
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
expect(!!savedObject.searchSource.getField('index')).to.be(false);
|
||||
|
||||
return savedObject.init().then(() => {
|
||||
expect(afterESRespCallback.called).to.be(true);
|
||||
const index = savedObject.searchSource.getField('index');
|
||||
expect(index instanceof IndexPattern).to.be(true);
|
||||
expect(index.id).to.equal(indexPatternId);
|
||||
});
|
||||
});
|
||||
|
||||
it('when false, does not create index', function () {
|
||||
const indexPatternId = 'testIndexPattern';
|
||||
const afterESRespCallback = sinon.spy();
|
||||
|
||||
const config = {
|
||||
type: 'dashboard',
|
||||
afterESResp: afterESRespCallback,
|
||||
searchSource: false,
|
||||
indexPattern: indexPatternId
|
||||
};
|
||||
|
||||
stubESResponse(getMockedDocResponse(indexPatternId));
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
expect(!!savedObject.searchSource).to.be(false);
|
||||
|
||||
return savedObject.init().then(() => {
|
||||
expect(afterESRespCallback.called).to.be(true);
|
||||
expect(!!savedObject.searchSource).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('type', function () {
|
||||
it('that is not specified throws an error', function () {
|
||||
const config = {};
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
try {
|
||||
savedObject.init();
|
||||
expect(false).to.be(true);
|
||||
} catch (err) {
|
||||
expect(err).to.not.be(null);
|
||||
}
|
||||
});
|
||||
|
||||
it('that is invalid invalid throws an error', function () {
|
||||
const config = { type: 'notypeexists' };
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
try {
|
||||
savedObject.init();
|
||||
expect(false).to.be(true);
|
||||
} catch (err) {
|
||||
expect(err).to.not.be(null);
|
||||
}
|
||||
});
|
||||
|
||||
it('that is valid passes', function () {
|
||||
const config = { type: 'dashboard' };
|
||||
return new SavedObject(config).init();
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaults', function () {
|
||||
|
||||
function getTestDefaultConfig(extraOptions) {
|
||||
return {
|
||||
defaults: { testDefault: 'hi' },
|
||||
type: 'dashboard',
|
||||
...extraOptions
|
||||
};
|
||||
}
|
||||
|
||||
function expectDefaultApplied(config) {
|
||||
return createInitializedSavedObject(config).then((savedObject) => {
|
||||
expect(savedObject.defaults).to.be(config.defaults);
|
||||
});
|
||||
}
|
||||
|
||||
describe('applied to object when id', function () {
|
||||
|
||||
it('is not specified', function () {
|
||||
expectDefaultApplied(getTestDefaultConfig());
|
||||
});
|
||||
|
||||
it('is undefined', function () {
|
||||
expectDefaultApplied(getTestDefaultConfig({ id: undefined }));
|
||||
});
|
||||
|
||||
it('is 0', function () {
|
||||
expectDefaultApplied(getTestDefaultConfig({ id: 0 }));
|
||||
});
|
||||
|
||||
it('is false', function () {
|
||||
expectDefaultApplied(getTestDefaultConfig({ id: false }));
|
||||
});
|
||||
});
|
||||
|
||||
it('applied to source if an id is given', function () {
|
||||
const myId = 'myid';
|
||||
const customDefault = 'hi';
|
||||
const initialOverwriteMeValue = 'this should get overwritten by the server response';
|
||||
|
||||
const config = {
|
||||
defaults: {
|
||||
overwriteMe: initialOverwriteMeValue,
|
||||
customDefault: customDefault
|
||||
},
|
||||
type: 'dashboard',
|
||||
id: myId
|
||||
};
|
||||
|
||||
const serverValue = 'this should override the initial default value given';
|
||||
|
||||
const mockDocResponse = getMockedDocResponse(
|
||||
myId,
|
||||
{ attributes: { overwriteMe: serverValue } });
|
||||
|
||||
stubESResponse(mockDocResponse);
|
||||
|
||||
return createInitializedSavedObject(config).then((savedObject) => {
|
||||
expect(!!savedObject._source).to.be(true);
|
||||
expect(savedObject.defaults).to.be(config.defaults);
|
||||
expect(savedObject._source.overwriteMe).to.be(serverValue);
|
||||
expect(savedObject._source.customDefault).to.be(customDefault);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
import { SimpleSavedObject } from '../simple_saved_object';
|
||||
|
||||
describe('SimpleSavedObject', () => {
|
||||
it('persists type and id', () => {
|
||||
const id = 'logstash-*';
|
||||
const type = 'index-pattern';
|
||||
|
||||
const client = sinon.stub();
|
||||
const savedObject = new SimpleSavedObject(client, { id, type });
|
||||
|
||||
expect(savedObject.id).to.be(id);
|
||||
expect(savedObject.type).to.be(type);
|
||||
});
|
||||
|
||||
it('persists attributes', () => {
|
||||
const attributes = { title: 'My title' };
|
||||
|
||||
const client = sinon.stub();
|
||||
const savedObject = new SimpleSavedObject(client, { attributes });
|
||||
|
||||
expect(savedObject.attributes).to.be(attributes);
|
||||
});
|
||||
|
||||
it('persists version', () => {
|
||||
const version = 2;
|
||||
|
||||
const client = sinon.stub();
|
||||
const savedObject = new SimpleSavedObject(client, { version });
|
||||
expect(savedObject._version).to.be(version);
|
||||
});
|
||||
});
|
|
@ -36,13 +36,13 @@ import { injectI18n } from '@kbn/i18n/react';
|
|||
|
||||
import { SavedObjectAttributes } from '../../../../server/saved_objects';
|
||||
import { VisTypesRegistryProvider } from '../../registry/vis_types';
|
||||
import { SavedObject } from '../saved_object';
|
||||
import { SimpleSavedObject } from '../simple_saved_object';
|
||||
|
||||
interface SavedObjectFinderUIState {
|
||||
items: Array<{
|
||||
title: string | null;
|
||||
id: SavedObject<SavedObjectAttributes>['id'];
|
||||
type: SavedObject<SavedObjectAttributes>['type'];
|
||||
id: SimpleSavedObject<SavedObjectAttributes>['id'];
|
||||
type: SimpleSavedObject<SavedObjectAttributes>['type'];
|
||||
}>;
|
||||
filter: string;
|
||||
isFetchingItems: boolean;
|
||||
|
@ -55,10 +55,10 @@ interface SavedObjectFinderUIState {
|
|||
interface SavedObjectFinderUIProps extends InjectedIntlProps {
|
||||
callToActionButton?: React.ReactNode;
|
||||
onChoose?: (
|
||||
id: SavedObject<SavedObjectAttributes>['id'],
|
||||
type: SavedObject<SavedObjectAttributes>['type']
|
||||
id: SimpleSavedObject<SavedObjectAttributes>['id'],
|
||||
type: SimpleSavedObject<SavedObjectAttributes>['type']
|
||||
) => void;
|
||||
makeUrl?: (id: SavedObject<SavedObjectAttributes>['id']) => void;
|
||||
makeUrl?: (id: SimpleSavedObject<SavedObjectAttributes>['id']) => void;
|
||||
noItemsMessage?: React.ReactNode;
|
||||
savedObjectType: 'visualization' | 'search';
|
||||
visTypes?: VisTypesRegistryProvider;
|
||||
|
@ -273,7 +273,7 @@ class SavedObjectFinderUI extends React.Component<
|
|||
defaultMessage: 'Title',
|
||||
}),
|
||||
sortable: true,
|
||||
render: (title: string, record: SavedObject<SavedObjectAttributes>) => {
|
||||
render: (title: string, record: SimpleSavedObject<SavedObjectAttributes>) => {
|
||||
const { onChoose, makeUrl } = this.props;
|
||||
|
||||
if (!onChoose && !makeUrl) {
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
import { find } from 'lodash';
|
||||
import { SavedObjectAttributes } from '../../../server/saved_objects';
|
||||
import { SavedObject } from './saved_object';
|
||||
import { SavedObjectsClient } from './saved_objects_client';
|
||||
import { SimpleSavedObject } from './simple_saved_object';
|
||||
|
||||
/**
|
||||
* Returns an object matching a given title
|
||||
|
@ -28,13 +28,13 @@ import { SavedObjectsClient } from './saved_objects_client';
|
|||
* @param savedObjectsClient {SavedObjectsClient}
|
||||
* @param type {string}
|
||||
* @param title {string}
|
||||
* @returns {Promise<SavedObject|undefined>}
|
||||
* @returns {Promise<SimpleSavedObject|undefined>}
|
||||
*/
|
||||
export function findObjectByTitle<T extends SavedObjectAttributes>(
|
||||
savedObjectsClient: SavedObjectsClient,
|
||||
type: string,
|
||||
title: string
|
||||
): Promise<SavedObject<T> | void> {
|
||||
): Promise<SimpleSavedObject<T> | void> {
|
||||
if (!title) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -20,5 +20,7 @@
|
|||
export { SavedObjectsClient } from './saved_objects_client';
|
||||
export { SavedObjectRegistryProvider } from './saved_object_registry';
|
||||
export { SavedObjectsClientProvider } from './saved_objects_client_provider';
|
||||
export { SavedObject } from './saved_object';
|
||||
// @ts-ignore
|
||||
export { SavedObjectLoader } from './saved_object_loader';
|
||||
export { SimpleSavedObject } from './simple_saved_object';
|
||||
export { findObjectByTitle } from './find_object_by_title';
|
||||
|
|
|
@ -31,20 +31,21 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { InvalidJSONProperty, SavedObjectNotFound } from '../../errors';
|
||||
import MappingSetupProvider from '../../utils/mapping_setup';
|
||||
import { InvalidJSONProperty, SavedObjectNotFound } from '../errors';
|
||||
import MappingSetupProvider from '../utils/mapping_setup';
|
||||
|
||||
import { SearchSourceProvider } from '../search_source';
|
||||
import { SavedObjectsClientProvider, findObjectByTitle } from '../../saved_objects';
|
||||
import { migrateLegacyQuery } from '../../utils/migrate_legacy_query';
|
||||
import { recentlyAccessed } from '../../persisted_log';
|
||||
import { SearchSourceProvider } from '../courier/search_source';
|
||||
import { findObjectByTitle } from './find_object_by_title';
|
||||
import { SavedObjectsClientProvider } from './saved_objects_client_provider';
|
||||
import { migrateLegacyQuery } from '../utils/migrate_legacy_query';
|
||||
import { recentlyAccessed } from '../persisted_log';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
/**
|
||||
* An error message to be used when the user rejects a confirm overwrite.
|
||||
* @type {string}
|
||||
*/
|
||||
const OVERWRITE_REJECTED = i18n.translate('common.ui.courier.savedObject.overwriteRejectedDescription', {
|
||||
const OVERWRITE_REJECTED = i18n.translate('common.ui.savedObjects.overwriteRejectedDescription', {
|
||||
defaultMessage: 'Overwrite confirmation was rejected'
|
||||
});
|
||||
|
||||
|
@ -52,7 +53,7 @@ const OVERWRITE_REJECTED = i18n.translate('common.ui.courier.savedObject.overwri
|
|||
* An error message to be used when the user rejects a confirm save with duplicate title.
|
||||
* @type {string}
|
||||
*/
|
||||
const SAVE_DUPLICATE_REJECTED = i18n.translate('common.ui.courier.savedObject.saveDuplicateRejectedDescription', {
|
||||
const SAVE_DUPLICATE_REJECTED = i18n.translate('common.ui.savedObjects.saveDuplicateRejectedDescription', {
|
||||
defaultMessage: 'Save with duplicate title confirmation was rejected'
|
||||
});
|
||||
|
||||
|
@ -70,6 +71,15 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm
|
|||
const SearchSource = Private(SearchSourceProvider);
|
||||
const mappingSetup = Private(MappingSetupProvider);
|
||||
|
||||
/**
|
||||
* The SavedObject class is a base class for saved objects loaded from the server and
|
||||
* provides additional functionality besides loading/saving/deleting/etc.
|
||||
*
|
||||
* It is overloaded and configured to provide type-aware functionality.
|
||||
* To just retrieve the attributes of saved objects, it is recommended to use SavedObjectLoader
|
||||
* which returns instances of SimpleSavedObject which don't introduce additional type-specific complexity.
|
||||
* @param {*} config
|
||||
*/
|
||||
function SavedObject(config) {
|
||||
if (!_.isObject(config)) config = {};
|
||||
|
||||
|
@ -388,16 +398,16 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm
|
|||
.catch(err => {
|
||||
// record exists, confirm overwriting
|
||||
if (_.get(err, 'res.status') === 409) {
|
||||
const confirmMessage = i18n.translate('common.ui.courier.savedObject.confirmModal.overwriteConfirmationMessage', {
|
||||
const confirmMessage = i18n.translate('common.ui.savedObjects.confirmModal.overwriteConfirmationMessage', {
|
||||
defaultMessage: 'Are you sure you want to overwrite {title}?',
|
||||
values: { title: this.title }
|
||||
});
|
||||
|
||||
return confirmModalPromise(confirmMessage, {
|
||||
confirmButtonText: i18n.translate('common.ui.courier.savedObject.confirmModal.overwriteButtonLabel', {
|
||||
confirmButtonText: i18n.translate('common.ui.savedObjects.confirmModal.overwriteButtonLabel', {
|
||||
defaultMessage: 'Overwrite',
|
||||
}),
|
||||
title: i18n.translate('common.ui.courier.savedObject.confirmModal.overwriteTitle', {
|
||||
title: i18n.translate('common.ui.savedObjects.confirmModal.overwriteTitle', {
|
||||
defaultMessage: 'Overwrite {name}?',
|
||||
values: { name: this.getDisplayName() }
|
||||
}),
|
||||
|
@ -410,13 +420,13 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm
|
|||
};
|
||||
|
||||
const displayDuplicateTitleConfirmModal = () => {
|
||||
const confirmMessage = i18n.translate('common.ui.courier.savedObject.confirmModal.saveDuplicateConfirmationMessage', {
|
||||
const confirmMessage = i18n.translate('common.ui.savedObjects.confirmModal.saveDuplicateConfirmationMessage', {
|
||||
defaultMessage: `A {name} with the title '{title}' already exists. Would you like to save anyway?`,
|
||||
values: { title: this.title, name: this.getDisplayName() }
|
||||
});
|
||||
|
||||
return confirmModalPromise(confirmMessage, {
|
||||
confirmButtonText: i18n.translate('common.ui.courier.savedObject.confirmModal.saveDuplicateButtonLabel', {
|
||||
confirmButtonText: i18n.translate('common.ui.savedObjects.confirmModal.saveDuplicateButtonLabel', {
|
||||
defaultMessage: 'Save {name}',
|
||||
values: { name: this.getDisplayName() }
|
||||
})
|
|
@ -17,23 +17,24 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Scanner } from '../../utils/scanner';
|
||||
import { StringUtils } from '../../utils/string_utils';
|
||||
import { StringUtils } from '../utils/string_utils';
|
||||
|
||||
/**
|
||||
* The SavedObjectLoader class provides some convenience functions
|
||||
* to load and save one kind of saved objects (specified in the constructor).
|
||||
*
|
||||
* It is based on the SavedObjectClient which implements loading and saving
|
||||
* in an abstract, type-agnostic way. If possible, use SavedObjectClient directly
|
||||
* to avoid pulling in extra functionality which isn't used.
|
||||
*/
|
||||
export class SavedObjectLoader {
|
||||
constructor(SavedObjectClass, kbnIndex, kbnUrl, $http, chrome, savedObjectClient) {
|
||||
constructor(SavedObjectClass, kbnUrl, chrome, savedObjectClient) {
|
||||
this.type = SavedObjectClass.type;
|
||||
this.Class = SavedObjectClass;
|
||||
this.lowercaseType = this.type.toLowerCase();
|
||||
this.kbnIndex = kbnIndex;
|
||||
this.kbnUrl = kbnUrl;
|
||||
this.chrome = chrome;
|
||||
|
||||
this.scanner = new Scanner($http, {
|
||||
index: kbnIndex,
|
||||
type: this.lowercaseType
|
||||
});
|
||||
|
||||
this.loaderProperties = {
|
||||
name: `${ this.lowercaseType }s`,
|
||||
noun: StringUtils.upperFirst(this.type),
|
||||
|
@ -85,13 +86,6 @@ export class SavedObjectLoader {
|
|||
return source;
|
||||
}
|
||||
|
||||
scanAll(queryString, pageSize = 1000) {
|
||||
return this.scanner.scanAndMap(queryString, {
|
||||
pageSize,
|
||||
docCount: Infinity
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates hit.attributes to contain an id and url field, and returns the updated
|
||||
* attributes object.
|
|
@ -21,8 +21,8 @@ jest.mock('ui/kfetch', () => ({}));
|
|||
|
||||
import * as sinon from 'sinon';
|
||||
import { FindOptions } from '../../../server/saved_objects/service';
|
||||
import { SavedObject } from './saved_object';
|
||||
import { SavedObjectsClient } from './saved_objects_client';
|
||||
import { SimpleSavedObject } from './simple_saved_object';
|
||||
|
||||
describe('SavedObjectsClient', () => {
|
||||
const doc = {
|
||||
|
@ -78,7 +78,7 @@ describe('SavedObjectsClient', () => {
|
|||
|
||||
test('resolves with instantiated SavedObject', async () => {
|
||||
const response = await savedObjectsClient.get(doc.type, doc.id);
|
||||
expect(response).toBeInstanceOf(SavedObject);
|
||||
expect(response).toBeInstanceOf(SimpleSavedObject);
|
||||
expect(response.type).toBe('config');
|
||||
expect(response.get('title')).toBe('Example title');
|
||||
});
|
||||
|
@ -292,7 +292,7 @@ describe('SavedObjectsClient', () => {
|
|||
const response = await savedObjectsClient.bulkCreate([doc], {});
|
||||
expect(response).toHaveProperty('savedObjects');
|
||||
expect(response.savedObjects.length).toBe(1);
|
||||
expect(response.savedObjects[0]).toBeInstanceOf(SavedObject);
|
||||
expect(response.savedObjects[0]).toBeInstanceOf(SimpleSavedObject);
|
||||
});
|
||||
|
||||
test('makes HTTP call', async () => {
|
||||
|
|
|
@ -31,7 +31,7 @@ import { CreateResponse, FindOptions, UpdateResponse } from '../../../server/sav
|
|||
import { isAutoCreateIndexError, showAutoCreateIndexErrorPage } from '../error_auto_create_index';
|
||||
import { kfetch, KFetchQuery } from '../kfetch';
|
||||
import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../utils/case_conversion';
|
||||
import { SavedObject } from './saved_object';
|
||||
import { SimpleSavedObject } from './simple_saved_object';
|
||||
|
||||
interface RequestParams {
|
||||
method: 'POST' | 'GET' | 'PUT' | 'DELETE';
|
||||
|
@ -60,7 +60,7 @@ interface UpdateOptions {
|
|||
}
|
||||
|
||||
interface BatchResponse<T extends SavedObjectAttributes = SavedObjectAttributes> {
|
||||
savedObjects: Array<SavedObject<T>>;
|
||||
savedObjects: Array<SimpleSavedObject<T>>;
|
||||
}
|
||||
|
||||
interface FindResults<T extends SavedObjectAttributes = SavedObjectAttributes>
|
||||
|
@ -73,7 +73,9 @@ interface FindResults<T extends SavedObjectAttributes = SavedObjectAttributes>
|
|||
interface BatchQueueEntry {
|
||||
type: string;
|
||||
id: string;
|
||||
resolve: <T extends SavedObjectAttributes>(value: SavedObject<T> | PlainSavedObject<T>) => void;
|
||||
resolve: <T extends SavedObjectAttributes>(
|
||||
value: SimpleSavedObject<T> | PlainSavedObject<T>
|
||||
) => void;
|
||||
reject: (reason?: any) => void;
|
||||
}
|
||||
|
||||
|
@ -91,6 +93,14 @@ const BATCH_INTERVAL = 100;
|
|||
|
||||
const API_BASE_URL = '/api/saved_objects/';
|
||||
|
||||
/**
|
||||
* The SavedObjectsClient class acts as a generic data fetcher
|
||||
* and data saver for saved objects regardless of type.
|
||||
*
|
||||
* If possible, this class should be used to load saved objects
|
||||
* instead of the SavedObjectLoader class which implements some
|
||||
* additional functionality.
|
||||
*/
|
||||
export class SavedObjectsClient {
|
||||
/**
|
||||
* Throttled processing of get requests into bulk requests at 100ms interval
|
||||
|
@ -145,7 +155,7 @@ export class SavedObjectsClient {
|
|||
type: string,
|
||||
attributes: T,
|
||||
options: CreateOptions = {}
|
||||
): Promise<SavedObject<T>> => {
|
||||
): Promise<SimpleSavedObject<T>> => {
|
||||
if (!type || !attributes) {
|
||||
return Promise.reject(new Error('requires type and attributes'));
|
||||
}
|
||||
|
@ -257,7 +267,7 @@ export class SavedObjectsClient {
|
|||
public get = <T extends SavedObjectAttributes>(
|
||||
type: string,
|
||||
id: string
|
||||
): Promise<SavedObject<T>> => {
|
||||
): Promise<SimpleSavedObject<T>> => {
|
||||
if (!type || !id) {
|
||||
return Promise.reject(new Error('requires type and id'));
|
||||
}
|
||||
|
@ -311,7 +321,7 @@ export class SavedObjectsClient {
|
|||
id: string,
|
||||
attributes: T,
|
||||
{ version, migrationVersion, references }: UpdateOptions = {}
|
||||
): Promise<SavedObject<T>> {
|
||||
): Promise<SimpleSavedObject<T>> {
|
||||
if (!type || !id || !attributes) {
|
||||
return Promise.reject(new Error('requires type, id and attributes'));
|
||||
}
|
||||
|
@ -336,8 +346,8 @@ export class SavedObjectsClient {
|
|||
|
||||
private createSavedObject<T extends SavedObjectAttributes>(
|
||||
options: PlainSavedObject<T>
|
||||
): SavedObject<T> {
|
||||
return new SavedObject(this, options);
|
||||
): SimpleSavedObject<T> {
|
||||
return new SimpleSavedObject(this, options);
|
||||
}
|
||||
|
||||
private getPath(path: Array<string | undefined>): string {
|
||||
|
|
|
@ -24,7 +24,15 @@ import {
|
|||
} from '../../../server/saved_objects';
|
||||
import { SavedObjectsClient } from './saved_objects_client';
|
||||
|
||||
export class SavedObject<T extends SavedObjectAttributes> {
|
||||
/**
|
||||
* This class is a very simple wrapper for SavedObjects loaded from the server.
|
||||
*
|
||||
* It provides basic functionality for updating/deleting/etc. saved objects but
|
||||
* doesn't include any type-specific implementations.
|
||||
*
|
||||
* For more sophisiticated use cases, the SavedObject class implements additional functions
|
||||
*/
|
||||
export class SimpleSavedObject<T extends SavedObjectAttributes> {
|
||||
public attributes: T;
|
||||
// tslint:disable-next-line variable-name We want to use the same interface this class had in JS
|
||||
public _version?: SavedObjectType<T>['version'];
|
|
@ -2,10 +2,10 @@
|
|||
<div
|
||||
ng-hide="!savedObject.isTitleChanged() || savedObject.copyOnSave"
|
||||
class="kuiLocalDropdownWarning kuiVerticalRhythmSmall"
|
||||
i18n-id="common.ui.courier.savedObject.howToSaveAsNewDescription"
|
||||
i18n-id="common.ui.savedObjects.howToSaveAsNewDescription"
|
||||
i18n-default-message="In previous versions of Kibana, changing the name of a {savedObjectName} would make a copy with the new name. Use the 'Save as a new {savedObjectName}' checkbox to do this now."
|
||||
i18n-values="{ savedObjectName: savedObject.getDisplayName() }"
|
||||
i18n-description="'Save as a new {savedObjectName}' refers to common.ui.courier.savedObject.saveAsNewLabel and should be the same text."
|
||||
i18n-description="'Save as a new {savedObjectName}' refers to common.ui.savedObjects.saveAsNewLabel and should be the same text."
|
||||
></div>
|
||||
|
||||
<label class="kuiCheckBoxLabel kuiVerticalRhythmSmall">
|
||||
|
@ -19,7 +19,7 @@
|
|||
|
||||
<span
|
||||
class="kuiCheckBoxLabel__text"
|
||||
i18n-id="common.ui.courier.savedObject.saveAsNewLabel"
|
||||
i18n-id="common.ui.savedObjects.saveAsNewLabel"
|
||||
i18n-default-message="Save as a new {savedObjectName}"
|
||||
i18n-values="{ savedObjectName: savedObject.getDisplayName() }"
|
||||
></span>
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { uiModules } from '../../../modules';
|
||||
import { uiModules } from '../../modules';
|
||||
import saveObjectSaveAsCheckboxTemplate from './saved_object_save_as_checkbox.html';
|
||||
|
||||
uiModules
|
|
@ -1,141 +0,0 @@
|
|||
/*
|
||||
* 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 { Scanner } from '../scanner';
|
||||
import expect from 'expect.js';
|
||||
import 'elasticsearch-browser';
|
||||
import ngMock from 'ng_mock';
|
||||
import sinon from 'sinon';
|
||||
|
||||
describe('Scanner', function () {
|
||||
let http;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function ($http) {
|
||||
http = $http;
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
http = null;
|
||||
});
|
||||
|
||||
describe('initialization', function () {
|
||||
it('should throw errors if missing arguments on initialization', function () {
|
||||
expect(() => new Scanner()).to.throwError();
|
||||
expect(() => new Scanner(http)).to.throwError();
|
||||
expect(() => new Scanner(http, {
|
||||
index: 'foo',
|
||||
type: 'bar'
|
||||
})).not.to.throwError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('scan', function () {
|
||||
let httpPost;
|
||||
let search;
|
||||
let scroll;
|
||||
let scanner;
|
||||
const mockSearch = { '_scroll_id': 'abc', 'took': 1, 'timed_out': false, '_shards': { 'total': 1, 'successful': 1, 'failed': 0 }, 'hits': { 'total': 2, 'max_score': 0.0, 'hits': [] } }; // eslint-disable-line max-len
|
||||
const hits = [{
|
||||
_id: 'one',
|
||||
_type: 'config',
|
||||
_source: { title: 'First title' }
|
||||
}, {
|
||||
_id: 'two',
|
||||
_type: 'config',
|
||||
_source: { title: 'Second title' }
|
||||
}];
|
||||
const mockScroll = { 'took': 1, 'timed_out': false, '_shards': { 'total': 1, 'successful': 1, 'failed': 0 }, 'hits': { 'total': 2, 'max_score': 0.0, 'hits': hits } }; // eslint-disable-line max-len
|
||||
|
||||
beforeEach(function () {
|
||||
scanner = new Scanner(http, {
|
||||
index: 'foo',
|
||||
type: 'bar'
|
||||
});
|
||||
|
||||
search = sinon.stub().returns(Promise.resolve({ data: mockSearch }));
|
||||
scroll = sinon.stub().returns(Promise.resolve({ data: mockScroll }));
|
||||
httpPost = sinon.stub(scanner.$http, 'post').callsFake((path, ...args) => {
|
||||
if (path.includes('legacy_scroll_start')) {
|
||||
return search(...args);
|
||||
}
|
||||
if (path.includes('legacy_scroll_continue')) {
|
||||
return scroll(...args);
|
||||
}
|
||||
throw new Error(`Unexpected path to $http.post(): ${path}`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject when an error occurs', function () {
|
||||
search = search.returns(Promise.reject(new Error('fail.')));
|
||||
return scanner.scanAndMap('')
|
||||
.then(function () {
|
||||
throw new Error('should reject');
|
||||
})
|
||||
.catch(function (error) {
|
||||
expect(error.message).to.be('fail.');
|
||||
});
|
||||
});
|
||||
|
||||
it('should search and then scroll for results', function () {
|
||||
return scanner.scanAndMap('')
|
||||
.then(function () {
|
||||
expect(search.called).to.be(true);
|
||||
expect(scroll.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should map results if a function is provided', function () {
|
||||
return scanner.scanAndMap(null, null, function (hit) {
|
||||
return hit._id.toUpperCase();
|
||||
})
|
||||
.then(function (response) {
|
||||
expect(response.hits[0]).to.be('ONE');
|
||||
expect(response.hits[1]).to.be('TWO');
|
||||
});
|
||||
});
|
||||
|
||||
it('should only return the requested number of documents', function () {
|
||||
return scanner.scanAndMap(null, { docCount: 1 }, function (hit) {
|
||||
return hit._id.toUpperCase();
|
||||
})
|
||||
.then(function (response) {
|
||||
expect(response.hits[0]).to.be('ONE');
|
||||
expect(response.hits[1]).to.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it('should scroll across multiple pages', function () {
|
||||
const oneResult = { 'took': 1, 'timed_out': false, '_shards': { 'total': 1, 'successful': 1, 'failed': 0 }, 'hits': { 'total': 2, 'max_score': 0.0, 'hits': ['one'] } }; // eslint-disable-line max-len
|
||||
scroll = sinon.stub().returns(Promise.resolve({ data: oneResult }));
|
||||
return scanner.scanAndMap(null, { pageSize: 1 })
|
||||
.then(function (response) {
|
||||
expect(scroll.calledTwice);
|
||||
expect(response.hits.length).to.be(2);
|
||||
expect(scroll.getCall(1).args[0].scrollId).to.be('abc');
|
||||
expect(scroll.getCall(0).args[0].scrollId).to.be('abc');
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
httpPost.restore();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
* 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 _ from 'lodash';
|
||||
import chrome from '../chrome';
|
||||
|
||||
export const Scanner = function ($http, { index, type } = {}) {
|
||||
if (!index) throw new Error('Expected index');
|
||||
if (!type) throw new Error('Expected type');
|
||||
if (!$http) throw new Error('Expected $http');
|
||||
|
||||
this.$http = $http;
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
};
|
||||
|
||||
Scanner.prototype.start = function (searchBody) {
|
||||
const { addBasePath } = chrome;
|
||||
const scrollStartPath = addBasePath('/api/kibana/legacy_scroll_start');
|
||||
return this.$http.post(scrollStartPath, searchBody);
|
||||
};
|
||||
|
||||
Scanner.prototype.continue = function (scrollId) {
|
||||
const { addBasePath } = chrome;
|
||||
const scrollContinuePath = addBasePath('/api/kibana/legacy_scroll_continue');
|
||||
return this.$http.post(scrollContinuePath, { scrollId });
|
||||
};
|
||||
|
||||
Scanner.prototype.scanAndMap = function (searchString, options, mapFn) {
|
||||
const bool = { must: [], filter: [] };
|
||||
|
||||
let scrollId;
|
||||
const allResults = {
|
||||
hits: [],
|
||||
total: 0
|
||||
};
|
||||
const opts = _.defaults(options || {}, {
|
||||
pageSize: 100,
|
||||
docCount: 1000
|
||||
});
|
||||
|
||||
if (this.type) {
|
||||
bool.filter.push({
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
_type: this.type
|
||||
}
|
||||
},
|
||||
{
|
||||
term: {
|
||||
type: this.type
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (searchString) {
|
||||
bool.must.push({
|
||||
simple_query_string: {
|
||||
query: searchString + '*',
|
||||
fields: ['title^3', 'description'],
|
||||
default_operator: 'AND'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
bool.must.push({
|
||||
match_all: {}
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const getMoreUntilDone = (error, response) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
const scanAllResults = opts.docCount === Infinity;
|
||||
allResults.total = scanAllResults ? response.hits.total : Math.min(response.hits.total, opts.docCount);
|
||||
scrollId = response._scroll_id || scrollId;
|
||||
|
||||
let hits = response.hits.hits
|
||||
.slice(0, allResults.total - allResults.hits.length);
|
||||
|
||||
hits = hits.map(hit => {
|
||||
if (hit._type === 'doc') {
|
||||
return {
|
||||
_id: hit._id.replace(`${this.type}:`, ''),
|
||||
_type: this.type,
|
||||
_source: hit._source[this.type],
|
||||
_meta: {
|
||||
savedObjectVersion: 2
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return _.pick(hit, ['_id', '_type', '_source']);
|
||||
});
|
||||
|
||||
if (mapFn) hits = hits.map(mapFn);
|
||||
|
||||
allResults.hits = allResults.hits.concat(hits);
|
||||
|
||||
const collectedAllResults = allResults.total === allResults.hits.length;
|
||||
if (collectedAllResults) {
|
||||
resolve(allResults);
|
||||
} else {
|
||||
this.continue(scrollId)
|
||||
.then(response => getMoreUntilDone(null, response.data))
|
||||
.catch(error => getMoreUntilDone(error));
|
||||
}
|
||||
};
|
||||
|
||||
const searchBody = {
|
||||
index: this.index,
|
||||
size: opts.pageSize,
|
||||
body: { query: { bool } },
|
||||
};
|
||||
this.start(searchBody)
|
||||
.then(response => getMoreUntilDone(null, response.data))
|
||||
.catch(error => getMoreUntilDone(error));
|
||||
});
|
||||
};
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { SavedObjectProvider } from 'ui/courier';
|
||||
import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
extractReferences,
|
||||
|
|
|
@ -6,9 +6,8 @@
|
|||
|
||||
import './saved_gis_map';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { SavedObjectLoader } from 'ui/courier/saved_object/saved_object_loader';
|
||||
import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
const module = uiModules.get('app/maps');
|
||||
|
||||
|
@ -22,5 +21,5 @@ SavedObjectRegistryProvider.register({
|
|||
// This is the only thing that gets injected into controllers
|
||||
module.service('gisMapSavedObjectLoader', function (Private, SavedGisMap, kbnIndex, kbnUrl, $http, chrome) {
|
||||
const savedObjectClient = Private(SavedObjectsClientProvider);
|
||||
return new SavedObjectLoader(SavedGisMap, kbnIndex, kbnUrl, $http, chrome, savedObjectClient);
|
||||
return new SavedObjectLoader(SavedGisMap, kbnUrl, chrome, savedObjectClient);
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import _ from 'lodash';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { createLegacyClass } from 'ui/utils/legacy_class';
|
||||
import { SavedObjectProvider } from 'ui/courier';
|
||||
import { SavedObjectProvider } from 'ui/saved_objects/saved_object';
|
||||
import {
|
||||
getTimeFilters,
|
||||
getMapZoom,
|
||||
|
|
|
@ -299,15 +299,6 @@
|
|||
"common.ui.courier.requestTimeDescription": "请求从浏览器到 Elasticsearch 以及返回的时间。不包括请求在队列中等候的时间。",
|
||||
"common.ui.courier.requestTimeLabel": "请求时间",
|
||||
"common.ui.courier.requestTimeValue": "{requestTime}ms",
|
||||
"common.ui.courier.savedObject.confirmModal.overwriteButtonLabel": "覆盖",
|
||||
"common.ui.courier.savedObject.confirmModal.overwriteConfirmationMessage": "确定要覆盖 “{title}”?",
|
||||
"common.ui.courier.savedObject.confirmModal.overwriteTitle": "覆盖“{name}”?",
|
||||
"common.ui.courier.savedObject.confirmModal.saveDuplicateButtonLabel": "保存“{name}”",
|
||||
"common.ui.courier.savedObject.confirmModal.saveDuplicateConfirmationMessage": "具有标题 “{title}” 的 “{name}” 已存在。是否确定要保存?",
|
||||
"common.ui.courier.savedObject.howToSaveAsNewDescription": "在 Kibana 的以前版本中,更改 {savedObjectName} 的名称将创建具有新名称的副本。使用“另存为新的 {savedObjectName}” 复选框可立即达到此目的。",
|
||||
"common.ui.courier.savedObject.overwriteRejectedDescription": "已拒绝覆盖确认",
|
||||
"common.ui.courier.savedObject.saveAsNewLabel": "另存为新的 {savedObjectName}",
|
||||
"common.ui.courier.savedObject.saveDuplicateRejectedDescription": "已拒绝使用重复标题保存确认",
|
||||
"common.ui.directives.confirmClickButtonLabel": "是否确定?",
|
||||
"common.ui.directives.fieldNameIcons.booleanAriaLabel": "布尔字段",
|
||||
"common.ui.directives.fieldNameIcons.conflictFieldAriaLabel": "冲突字段",
|
||||
|
@ -586,8 +577,17 @@
|
|||
"common.ui.savedObjectFinder.sortByButtonLabeDescendingScreenReaderOnly": "降序",
|
||||
"common.ui.savedObjectFinder.sortByButtonLabel": "名称",
|
||||
"common.ui.savedObjectFinder.sortByButtonLabelScreenReaderOnly": "排序依据",
|
||||
"common.ui.savedObjects.confirmModal.overwriteButtonLabel": "覆盖",
|
||||
"common.ui.savedObjects.confirmModal.overwriteConfirmationMessage": "确定要覆盖 “{title}”?",
|
||||
"common.ui.savedObjects.confirmModal.overwriteTitle": "覆盖“{name}”?",
|
||||
"common.ui.savedObjects.confirmModal.saveDuplicateButtonLabel": "保存“{name}”",
|
||||
"common.ui.savedObjects.confirmModal.saveDuplicateConfirmationMessage": "具有标题 “{title}” 的 “{name}” 已存在。是否确定要保存?",
|
||||
"common.ui.savedObjects.finder.searchPlaceholder": "搜索……",
|
||||
"common.ui.savedObjects.finder.titleLabel": "标题",
|
||||
"common.ui.savedObjects.howToSaveAsNewDescription": "在 Kibana 的以前版本中,更改 {savedObjectName} 的名称将创建具有新名称的副本。使用“另存为新的 {savedObjectName}” 复选框可立即达到此目的。",
|
||||
"common.ui.savedObjects.overwriteRejectedDescription": "已拒绝覆盖确认",
|
||||
"common.ui.savedObjects.saveAsNewLabel": "另存为新的 {savedObjectName}",
|
||||
"common.ui.savedObjects.saveDuplicateRejectedDescription": "已拒绝使用重复标题保存确认",
|
||||
"common.ui.savedObjects.saveModal.cancelButtonLabel": "取消",
|
||||
"common.ui.savedObjects.saveModal.confirmSaveButtonLabel": "确认保存",
|
||||
"common.ui.savedObjects.saveModal.duplicateTitleDescription": "单击 “{confirmSaveLabel}” 以保存标题重复的{objectType}",
|
||||
|
|
Loading…
Reference in a new issue