mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* removing angular from indexPatterns (#34418) # Conflicts: # src/legacy/ui/public/index_patterns/__tests__/_index_pattern.js # src/legacy/ui/public/index_patterns/_index_pattern.js # src/legacy/ui/public/index_patterns/fields_fetcher.js # src/legacy/ui/public/saved_objects/__tests__/saved_object.js # x-pack/plugins/translations/translations/ja-JP.json # x-pack/plugins/translations/translations/zh-CN.json * i18n fix * removing basePath from apiClient
This commit is contained in:
parent
623783b731
commit
0063a9279a
41 changed files with 1177 additions and 1381 deletions
|
@ -17,11 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import stubbedLogstashFields from 'fixtures/logstash_fields';
|
||||
import { SimpleSavedObject } from 'ui/saved_objects';
|
||||
import fixturesLogstashFieldsProvider from './logstash_fields';
|
||||
import { SimpleSavedObject } from '../legacy/ui/public/saved_objects/simple_saved_object';
|
||||
|
||||
export function FixturesStubbedSavedObjectIndexPatternProvider() {
|
||||
const mockLogstashFields = stubbedLogstashFields();
|
||||
|
||||
export function fixturesStubbedSavedObjectIndexPatternProvider() {
|
||||
const mockLogstashFields = fixturesLogstashFieldsProvider();
|
||||
|
||||
return function (id) {
|
||||
return new SimpleSavedObject(undefined, {
|
||||
|
@ -29,7 +30,7 @@ export function FixturesStubbedSavedObjectIndexPatternProvider() {
|
|||
type: 'index-pattern',
|
||||
attributes: {
|
||||
customFormats: '{}',
|
||||
fields: JSON.stringify(mockLogstashFields)
|
||||
fields: mockLogstashFields
|
||||
},
|
||||
version: 2
|
||||
});
|
||||
|
|
|
@ -17,42 +17,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { Field } from 'ui/index_patterns/_field.js';
|
||||
// @ts-ignore
|
||||
import { FieldList } from 'ui/index_patterns/_field_list';
|
||||
// @ts-ignore
|
||||
import { IndexPatternsFlattenHitProvider } from 'ui/index_patterns/_flatten_hit';
|
||||
// @ts-ignore
|
||||
import { getComputedFields } from 'ui/index_patterns/_get_computed_fields';
|
||||
// @ts-ignore
|
||||
import { getRoutes, IndexPatternProvider } from 'ui/index_patterns/_index_pattern';
|
||||
import chrome from 'ui/chrome';
|
||||
// @ts-ignore
|
||||
import { mockFields, mockIndexPattern } from 'ui/index_patterns/fixtures';
|
||||
// @ts-ignore
|
||||
import { CONTAINS_SPACES } from 'ui/index_patterns/index';
|
||||
// @ts-ignore
|
||||
import { ILLEGAL_CHARACTERS } from 'ui/index_patterns/index';
|
||||
// @ts-ignore
|
||||
import { INDEX_PATTERN_ILLEGAL_CHARACTERS } from 'ui/index_patterns/index';
|
||||
// @ts-ignore
|
||||
import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/index_patterns/index';
|
||||
// @ts-ignore
|
||||
import { IndexPatternsApiClientProvider } from 'ui/index_patterns/index';
|
||||
// @ts-ignore
|
||||
import { IndexPatternSelect } from 'ui/index_patterns/index';
|
||||
// @ts-ignore
|
||||
import { IndexPatternsProvider } from 'ui/index_patterns/index';
|
||||
import { IndexPatterns } from 'ui/index_patterns/index';
|
||||
// @ts-ignore
|
||||
import { validateIndexPattern } from 'ui/index_patterns/index';
|
||||
// @ts-ignore
|
||||
import setupRouteWithDefaultPattern from 'ui/index_patterns/route_setup/load_default';
|
||||
// @ts-ignore
|
||||
import { getFromSavedObject, isFilterable } from 'ui/index_patterns/static_utils';
|
||||
|
||||
// IndexPattern, StaticIndexPattern, StaticIndexPatternField, Field
|
||||
import * as types from 'ui/index_patterns';
|
||||
|
||||
const config = chrome.getUiSettingsClient();
|
||||
const savedObjectsClient = chrome.getSavedObjectsClient();
|
||||
/**
|
||||
* Index Patterns Service
|
||||
*
|
||||
|
@ -67,33 +50,7 @@ import * as types from 'ui/index_patterns';
|
|||
export class IndexPatternsService {
|
||||
public setup() {
|
||||
return {
|
||||
getRoutes,
|
||||
IndexPatternProvider,
|
||||
IndexPatternsApiClientProvider,
|
||||
IndexPatternsFlattenHitProvider,
|
||||
IndexPatternsProvider,
|
||||
setupRouteWithDefaultPattern, // only used in kibana/management
|
||||
validateIndexPattern,
|
||||
constants: {
|
||||
ILLEGAL_CHARACTERS,
|
||||
CONTAINS_SPACES,
|
||||
INDEX_PATTERN_ILLEGAL_CHARACTERS,
|
||||
INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE,
|
||||
},
|
||||
fields: {
|
||||
Field,
|
||||
FieldList,
|
||||
getComputedFields,
|
||||
getFromSavedObject,
|
||||
isFilterable,
|
||||
},
|
||||
fixtures: {
|
||||
mockFields,
|
||||
mockIndexPattern,
|
||||
},
|
||||
ui: {
|
||||
IndexPatternSelect,
|
||||
},
|
||||
indexPatterns: new IndexPatterns(config, savedObjectsClient),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -102,6 +59,24 @@ export class IndexPatternsService {
|
|||
}
|
||||
}
|
||||
|
||||
// static exports
|
||||
|
||||
const constants = {
|
||||
INDEX_PATTERN_ILLEGAL_CHARACTERS,
|
||||
INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE,
|
||||
};
|
||||
|
||||
const fixtures = {
|
||||
mockFields,
|
||||
mockIndexPattern,
|
||||
};
|
||||
|
||||
const ui = {
|
||||
IndexPatternSelect,
|
||||
};
|
||||
|
||||
export { validateIndexPattern, constants, fixtures, ui };
|
||||
|
||||
/** @public */
|
||||
export type IndexPatternsSetup = ReturnType<IndexPatternsService['setup']>;
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ uiRoutes
|
|||
redirectTo: '/management'
|
||||
});
|
||||
|
||||
require('ui/index_patterns/route_setup/load_default')({
|
||||
require('./route_setup/load_default')({
|
||||
whenMissingRedirectTo: '/management/kibana/index_pattern'
|
||||
});
|
||||
|
||||
|
|
|
@ -19,10 +19,9 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import { banners } from '../../notify';
|
||||
import { NoDefaultIndexPattern } from '../../errors';
|
||||
import { IndexPatternsGetProvider } from '../_get';
|
||||
import uiRoutes from '../../routes';
|
||||
import { banners } from 'ui/notify';
|
||||
import { NoDefaultIndexPattern } from 'ui/index_patterns/errors';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import {
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
@ -44,7 +43,7 @@ function displayBanner() {
|
|||
color="warning"
|
||||
iconType="iInCircle"
|
||||
title={
|
||||
i18n.translate('common.ui.indexPattern.bannerLabel',
|
||||
i18n.translate('kbn.management.indexPattern.bannerLabel',
|
||||
//eslint-disable-next-line max-len
|
||||
{ defaultMessage: 'In order to visualize and explore data in Kibana, you\'ll need to create an index pattern to retrieve data from Elasticsearch.' })
|
||||
}
|
||||
|
@ -65,15 +64,14 @@ export default function (opts) {
|
|||
const whenMissingRedirectTo = opts.whenMissingRedirectTo || null;
|
||||
|
||||
uiRoutes
|
||||
.addSetupWork(function loadDefaultIndexPattern(Private, $route, config) {
|
||||
const getIds = Private(IndexPatternsGetProvider)('id');
|
||||
.addSetupWork(function loadDefaultIndexPattern(Promise, $route, config, indexPatterns) {
|
||||
const route = _.get($route, 'current.$$route');
|
||||
|
||||
if (!route.requireDefaultIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
return getIds()
|
||||
return indexPatterns.getIds()
|
||||
.then(function (patterns) {
|
||||
let defaultId = config.get('defaultIndex');
|
||||
let defined = !!defaultId;
|
|
@ -96,8 +96,8 @@ uiRoutes
|
|||
});
|
||||
},
|
||||
resolve: {
|
||||
indexPattern: function ($route, redirectWhenMissing, indexPatterns) {
|
||||
return indexPatterns.get($route.current.params.indexPatternId)
|
||||
indexPattern: function ($route, Promise, redirectWhenMissing, indexPatterns) {
|
||||
return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId))
|
||||
.catch(redirectWhenMissing('/management/kibana/index_patterns'));
|
||||
}
|
||||
},
|
||||
|
|
|
@ -161,9 +161,8 @@ uiRoutes
|
|||
template,
|
||||
k7Breadcrumbs: getEditBreadcrumbs,
|
||||
resolve: {
|
||||
indexPattern: function ($route, redirectWhenMissing, indexPatterns) {
|
||||
return indexPatterns
|
||||
.get($route.current.params.indexPatternId)
|
||||
indexPattern: function ($route, Promise, redirectWhenMissing, indexPatterns) {
|
||||
return Promise.resolve(indexPatterns.get($route.current.params.indexPatternId))
|
||||
.catch(redirectWhenMissing('/management/kibana/index_patterns'));
|
||||
}
|
||||
},
|
||||
|
@ -171,7 +170,7 @@ uiRoutes
|
|||
|
||||
uiModules.get('apps/management')
|
||||
.controller('managementIndexPatternsEdit', function (
|
||||
$scope, $location, $route, config, indexPatterns, Private, AppState, confirmModal) {
|
||||
$scope, $location, $route, Promise, config, indexPatterns, Private, AppState, confirmModal) {
|
||||
const $state = $scope.state = new AppState();
|
||||
const { fieldWildcardMatcher } = Private(FieldWildcardProvider);
|
||||
const indexPatternListProvider = Private(IndexPatternListFactory)();
|
||||
|
@ -272,7 +271,7 @@ uiModules.get('apps/management')
|
|||
}
|
||||
}
|
||||
|
||||
indexPatterns.delete($scope.indexPattern)
|
||||
Promise.resolve(indexPatterns.delete($scope.indexPattern))
|
||||
.then(function () {
|
||||
$location.url('/management/kibana/index_patterns');
|
||||
})
|
||||
|
|
|
@ -28,9 +28,6 @@ import {
|
|||
FieldNotFoundInCache,
|
||||
DuplicateField,
|
||||
SavedObjectNotFound,
|
||||
IndexPatternMissingIndices,
|
||||
NoDefinedIndexPatterns,
|
||||
NoDefaultIndexPattern,
|
||||
PersistedStateError,
|
||||
VislibError,
|
||||
ContainerTooSmall,
|
||||
|
@ -53,9 +50,6 @@ describe('ui/errors', () => {
|
|||
new FieldNotFoundInCache('aname'),
|
||||
new DuplicateField('dupfield'),
|
||||
new SavedObjectNotFound('dashboard', '123'),
|
||||
new IndexPatternMissingIndices(),
|
||||
new NoDefinedIndexPatterns(),
|
||||
new NoDefaultIndexPattern(),
|
||||
new PersistedStateError(),
|
||||
new VislibError('err'),
|
||||
new ContainerTooSmall(),
|
||||
|
|
|
@ -156,18 +156,6 @@ export class DuplicateField extends KbnError {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* when a mapping already exists for a field the user is attempting to add
|
||||
* @param {String} name - the field name
|
||||
*/
|
||||
export class IndexPatternAlreadyExists extends KbnError {
|
||||
constructor(name) {
|
||||
super(
|
||||
`An index pattern of "${name}" already exists`,
|
||||
IndexPatternAlreadyExists);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A saved object was not found
|
||||
*/
|
||||
|
@ -187,41 +175,6 @@ export class SavedObjectNotFound extends KbnError {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tried to call a method that relies on SearchSource having an indexPattern assigned
|
||||
*/
|
||||
export class IndexPatternMissingIndices extends KbnError {
|
||||
constructor(message) {
|
||||
const defaultMessage = 'IndexPattern\'s configured pattern does not match any indices';
|
||||
|
||||
super(
|
||||
(message && message.length) ? `No matching indices found: ${message}` : defaultMessage,
|
||||
IndexPatternMissingIndices);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tried to call a method that relies on SearchSource having an indexPattern assigned
|
||||
*/
|
||||
export class NoDefinedIndexPatterns extends KbnError {
|
||||
constructor() {
|
||||
super(
|
||||
'Define at least one index pattern to continue',
|
||||
NoDefinedIndexPatterns);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tried to load a route besides management/kibana/index but you don't have a default index pattern!
|
||||
*/
|
||||
export class NoDefaultIndexPattern extends KbnError {
|
||||
constructor() {
|
||||
super(
|
||||
'Please specify a default index pattern',
|
||||
NoDefaultIndexPattern);
|
||||
}
|
||||
}
|
||||
|
||||
export class PersistedStateError extends KbnError {
|
||||
constructor() {
|
||||
super(
|
||||
|
|
|
@ -1,300 +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 sinon from 'sinon';
|
||||
import ngMock from 'ng_mock';
|
||||
import expect from '@kbn/expect';
|
||||
import Promise from 'bluebird';
|
||||
import { DuplicateField } from '../../errors';
|
||||
import { IndexedArray } from '../../indexed_array';
|
||||
import stubbedLogstashFields from 'fixtures/logstash_fields';
|
||||
import { FixturesStubbedSavedObjectIndexPatternProvider } from 'fixtures/stubbed_saved_object_index_pattern';
|
||||
import { IndexPatternProvider } from '../_index_pattern';
|
||||
import NoDigestPromises from 'test_utils/no_digest_promises';
|
||||
|
||||
import { FieldsFetcherProvider } from '../fields_fetcher_provider';
|
||||
import { StubIndexPatternsApiClientModule } from './stub_index_patterns_api_client';
|
||||
import { IndexPatternsApiClientProvider } from '../index_patterns_api_client_provider';
|
||||
import { SavedObjectsClientProvider } from '../../saved_objects';
|
||||
|
||||
describe('index pattern', function () {
|
||||
NoDigestPromises.activateForSuite();
|
||||
|
||||
let IndexPattern;
|
||||
let fieldsFetcher;
|
||||
let mockLogstashFields;
|
||||
let savedObjectsClient;
|
||||
let savedObjectsResponse;
|
||||
const indexPatternId = 'test-pattern';
|
||||
let indexPattern;
|
||||
let indexPatternsApiClient;
|
||||
|
||||
beforeEach(ngMock.module('kibana', StubIndexPatternsApiClientModule));
|
||||
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
mockLogstashFields = stubbedLogstashFields();
|
||||
savedObjectsResponse = Private(FixturesStubbedSavedObjectIndexPatternProvider);
|
||||
|
||||
savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
sinon.stub(savedObjectsClient, 'create');
|
||||
sinon.stub(savedObjectsClient, 'get');
|
||||
sinon.stub(savedObjectsClient, 'update');
|
||||
|
||||
IndexPattern = Private(IndexPatternProvider);
|
||||
fieldsFetcher = Private(FieldsFetcherProvider);
|
||||
indexPatternsApiClient = Private(IndexPatternsApiClientProvider);
|
||||
}));
|
||||
|
||||
// create an indexPattern instance for each test
|
||||
beforeEach(function () {
|
||||
return create(indexPatternId).then(function (pattern) {
|
||||
indexPattern = pattern;
|
||||
});
|
||||
});
|
||||
|
||||
// helper function to create index patterns
|
||||
function create(id, payload) {
|
||||
const indexPattern = new IndexPattern(id);
|
||||
payload = _.defaults(payload || {}, savedObjectsResponse(id));
|
||||
|
||||
savedObjectsClient.create.returns(Promise.resolve(payload));
|
||||
setDocsourcePayload(payload);
|
||||
|
||||
return indexPattern.init();
|
||||
}
|
||||
|
||||
function setDocsourcePayload(payload) {
|
||||
savedObjectsClient.get.returns(Promise.resolve(payload));
|
||||
savedObjectsClient.update.returns(Promise.resolve(payload));
|
||||
}
|
||||
|
||||
describe('api', function () {
|
||||
it('should have expected properties', function () {
|
||||
return create('test-pattern').then(function (indexPattern) {
|
||||
// methods
|
||||
expect(indexPattern).to.have.property('refreshFields');
|
||||
expect(indexPattern).to.have.property('popularizeField');
|
||||
expect(indexPattern).to.have.property('getScriptedFields');
|
||||
expect(indexPattern).to.have.property('getNonScriptedFields');
|
||||
expect(indexPattern).to.have.property('addScriptedField');
|
||||
expect(indexPattern).to.have.property('removeScriptedField');
|
||||
expect(indexPattern).to.have.property('toString');
|
||||
expect(indexPattern).to.have.property('toJSON');
|
||||
expect(indexPattern).to.have.property('save');
|
||||
|
||||
// properties
|
||||
expect(indexPattern).to.have.property('fields');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('init', function () {
|
||||
it('should append the found fields', function () {
|
||||
expect(savedObjectsClient.get.callCount).to.be(1);
|
||||
expect(indexPattern.fields).to.have.length(mockLogstashFields.length);
|
||||
expect(indexPattern.fields).to.be.an(IndexedArray);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fields', function () {
|
||||
it('should have expected properties on fields', function () {
|
||||
expect(indexPattern.fields[0]).to.have.property('displayName');
|
||||
expect(indexPattern.fields[0]).to.have.property('filterable');
|
||||
expect(indexPattern.fields[0]).to.have.property('format');
|
||||
expect(indexPattern.fields[0]).to.have.property('sortable');
|
||||
expect(indexPattern.fields[0]).to.have.property('scripted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getScriptedFields', function () {
|
||||
it('should return all scripted fields', function () {
|
||||
const scriptedNames = _(mockLogstashFields).where({ scripted: true }).pluck('name').value();
|
||||
const respNames = _.pluck(indexPattern.getScriptedFields(), 'name');
|
||||
expect(respNames).to.eql(scriptedNames);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNonScriptedFields', function () {
|
||||
it('should return all non-scripted fields', function () {
|
||||
const notScriptedNames = _(mockLogstashFields).where({ scripted: false }).pluck('name').value();
|
||||
const respNames = _.pluck(indexPattern.getNonScriptedFields(), 'name');
|
||||
expect(respNames).to.eql(notScriptedNames);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('refresh fields', function () {
|
||||
it('should fetch fields from the fieldsFetcher', async function () {
|
||||
expect(indexPattern.fields.length).to.be.greaterThan(2);
|
||||
|
||||
sinon.spy(fieldsFetcher, 'fetch');
|
||||
indexPatternsApiClient.swapStubNonScriptedFields([
|
||||
{ name: 'foo' },
|
||||
{ name: 'bar' }
|
||||
]);
|
||||
|
||||
await indexPattern.refreshFields();
|
||||
sinon.assert.calledOnce(fieldsFetcher.fetch);
|
||||
|
||||
const newFields = indexPattern.getNonScriptedFields();
|
||||
expect(newFields).to.have.length(2);
|
||||
expect(newFields.map(f => f.name)).to.eql(['foo', 'bar']);
|
||||
});
|
||||
|
||||
it('should preserve the scripted fields', async function () {
|
||||
// add spy to indexPattern.getScriptedFields
|
||||
sinon.spy(indexPattern, 'getScriptedFields');
|
||||
|
||||
// refresh fields, which will fetch
|
||||
await indexPattern.refreshFields();
|
||||
|
||||
// called to append scripted fields to the response from mapper.getFieldsForIndexPattern
|
||||
sinon.assert.calledOnce(indexPattern.getScriptedFields);
|
||||
expect(indexPattern.getScriptedFields().map(f => f.name))
|
||||
.to.eql(mockLogstashFields.filter(f => f.scripted).map(f => f.name));
|
||||
});
|
||||
});
|
||||
|
||||
describe('add and remove scripted fields', function () {
|
||||
it('should append the scripted field', function () {
|
||||
// keep a copy of the current scripted field count
|
||||
const saveSpy = sinon.spy(indexPattern, 'save');
|
||||
const oldCount = indexPattern.getScriptedFields().length;
|
||||
|
||||
// add a new scripted field
|
||||
const scriptedField = {
|
||||
name: 'new scripted field',
|
||||
script: 'false',
|
||||
type: 'boolean'
|
||||
};
|
||||
indexPattern.addScriptedField(scriptedField.name, scriptedField.script, scriptedField.type);
|
||||
const scriptedFields = indexPattern.getScriptedFields();
|
||||
expect(saveSpy.callCount).to.equal(1);
|
||||
expect(scriptedFields).to.have.length(oldCount + 1);
|
||||
expect(indexPattern.fields.byName[scriptedField.name].name).to.equal(scriptedField.name);
|
||||
});
|
||||
|
||||
it('should remove scripted field, by name', function () {
|
||||
const saveSpy = sinon.spy(indexPattern, 'save');
|
||||
const scriptedFields = indexPattern.getScriptedFields();
|
||||
const oldCount = scriptedFields.length;
|
||||
const scriptedField = _.last(scriptedFields);
|
||||
|
||||
indexPattern.removeScriptedField(scriptedField.name);
|
||||
|
||||
expect(saveSpy.callCount).to.equal(1);
|
||||
expect(indexPattern.getScriptedFields().length).to.equal(oldCount - 1);
|
||||
expect(indexPattern.fields.byName[scriptedField.name]).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('should not allow duplicate names', function () {
|
||||
const scriptedFields = indexPattern.getScriptedFields();
|
||||
const scriptedField = _.last(scriptedFields);
|
||||
expect(function () {
|
||||
indexPattern.addScriptedField(scriptedField.name, '\'new script\'', 'string');
|
||||
}).to.throwError(function (e) {
|
||||
expect(e).to.be.a(DuplicateField);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('popularizeField', function () {
|
||||
it('should increment the popularity count by default', function () {
|
||||
const saveSpy = sinon.stub(indexPattern, 'save');
|
||||
indexPattern.fields.forEach(function (field, i) {
|
||||
const oldCount = field.count;
|
||||
|
||||
indexPattern.popularizeField(field.name);
|
||||
|
||||
expect(saveSpy.callCount).to.equal(i + 1);
|
||||
expect(field.count).to.equal(oldCount + 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should increment the popularity count', function () {
|
||||
const saveSpy = sinon.stub(indexPattern, 'save');
|
||||
indexPattern.fields.forEach(function (field, i) {
|
||||
const oldCount = field.count;
|
||||
const incrementAmount = 4;
|
||||
|
||||
indexPattern.popularizeField(field.name, incrementAmount);
|
||||
|
||||
expect(saveSpy.callCount).to.equal(i + 1);
|
||||
expect(field.count).to.equal(oldCount + incrementAmount);
|
||||
});
|
||||
});
|
||||
|
||||
it('should decrement the popularity count', function () {
|
||||
indexPattern.fields.forEach(function (field) {
|
||||
const oldCount = field.count;
|
||||
const incrementAmount = 4;
|
||||
const decrementAmount = -2;
|
||||
|
||||
indexPattern.popularizeField(field.name, incrementAmount);
|
||||
indexPattern.popularizeField(field.name, decrementAmount);
|
||||
|
||||
expect(field.count).to.equal(oldCount + incrementAmount + decrementAmount);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not go below 0', function () {
|
||||
indexPattern.fields.forEach(function (field) {
|
||||
const decrementAmount = -Number.MAX_VALUE;
|
||||
indexPattern.popularizeField(field.name, decrementAmount);
|
||||
expect(field.count).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIndex', () => {
|
||||
it('should return the title when there is no intervalName', () => {
|
||||
expect(indexPattern.getIndex()).to.be(indexPattern.title);
|
||||
});
|
||||
|
||||
it('should convert time-based intervals to wildcards', () => {
|
||||
const oldTitle = indexPattern.title;
|
||||
indexPattern.intervalName = 'daily';
|
||||
|
||||
indexPattern.title = '[logstash-]YYYY.MM.DD';
|
||||
expect(indexPattern.getIndex()).to.be('logstash-*');
|
||||
|
||||
indexPattern.title = 'YYYY.MM.DD[-logstash]';
|
||||
expect(indexPattern.getIndex()).to.be('*-logstash');
|
||||
|
||||
indexPattern.title = 'YYYY[-logstash-]YYYY.MM.DD';
|
||||
expect(indexPattern.getIndex()).to.be('*-logstash-*');
|
||||
|
||||
indexPattern.title = 'YYYY[-logstash-]YYYY[-foo-]MM.DD';
|
||||
expect(indexPattern.getIndex()).to.be('*-logstash-*-foo-*');
|
||||
|
||||
indexPattern.title = 'YYYY[-logstash-]YYYY[-foo-]MM.DD[-bar]';
|
||||
expect(indexPattern.getIndex()).to.be('*-logstash-*-foo-*-bar');
|
||||
|
||||
indexPattern.title = '[logstash-]YYYY[-foo-]MM.DD[-bar]';
|
||||
expect(indexPattern.getIndex()).to.be('logstash-*-foo-*-bar');
|
||||
|
||||
indexPattern.title = '[logstash]';
|
||||
expect(indexPattern.getIndex()).to.be('logstash');
|
||||
|
||||
indexPattern.title = oldTitle;
|
||||
delete indexPattern.intervalName;
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,7 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexPatternProvider } from '../_index_pattern';
|
||||
import _ from 'lodash';
|
||||
import { IndexedArray } from '../../indexed_array';
|
||||
import { IndexPattern } from '../_index_pattern';
|
||||
import mockLogstashFields from '../../../../../fixtures/logstash_fields';
|
||||
import { fixturesStubbedSavedObjectIndexPatternProvider } from '../../../../../fixtures/stubbed_saved_object_index_pattern';
|
||||
|
||||
jest.mock('../../errors', () => ({
|
||||
SavedObjectNotFound: jest.fn(),
|
||||
|
@ -25,6 +29,11 @@ jest.mock('../../errors', () => ({
|
|||
IndexPatternMissingIndices: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../errors', () => ({
|
||||
IndexPatternMissingIndices: jest.fn(),
|
||||
}));
|
||||
|
||||
|
||||
jest.mock('../../registry/field_formats', () => ({
|
||||
fieldFormats: {
|
||||
getDefaultInstance: jest.fn(),
|
||||
|
@ -35,6 +44,9 @@ jest.mock('../../utils/mapping_setup', () => ({
|
|||
expandShorthand: jest.fn().mockImplementation(() => ({
|
||||
id: true,
|
||||
title: true,
|
||||
fieldFormatMap: {
|
||||
_deserialize: jest.fn().mockImplementation(() => ([])),
|
||||
},
|
||||
}))
|
||||
}));
|
||||
|
||||
|
@ -46,7 +58,7 @@ jest.mock('../../notify', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../_format_hit', () => ({
|
||||
formatHit: jest.fn().mockImplementation(() => ({
|
||||
formatHitProvider: jest.fn().mockImplementation(() => ({
|
||||
formatField: jest.fn(),
|
||||
}))
|
||||
}));
|
||||
|
@ -58,80 +70,271 @@ jest.mock('../_get', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../_flatten_hit', () => ({
|
||||
IndexPatternsFlattenHitProvider: jest.fn(),
|
||||
flattenHitWrapper: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../_pattern_cache', () => ({
|
||||
IndexPatternsPatternCacheProvider: {
|
||||
clear: jest.fn(),
|
||||
}
|
||||
}));
|
||||
|
||||
jest.mock('../fields_fetcher_provider', () => ({
|
||||
FieldsFetcherProvider: {
|
||||
fetch: jest.fn().mockImplementation(() => ([]))
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
jest.mock('../../saved_objects', () => {
|
||||
const object = {
|
||||
_version: 'foo',
|
||||
_id: 'foo',
|
||||
attributes: {
|
||||
title: 'something'
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
SavedObjectsClientProvider: {
|
||||
get: async () => object,
|
||||
update: async (type, id, body, { version }) => {
|
||||
if (object._version !== version) {
|
||||
throw {
|
||||
res: {
|
||||
status: 409
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
object.attributes.title = body.title;
|
||||
object._version += 'a';
|
||||
|
||||
return {
|
||||
id: object._id,
|
||||
_version: object._version,
|
||||
};
|
||||
}
|
||||
},
|
||||
findObjectByTitle: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const Private = arg => arg;
|
||||
let object;
|
||||
const savedObjectsClient = {
|
||||
create: jest.fn(),
|
||||
get: jest.fn().mockImplementation(() => object),
|
||||
update: jest.fn().mockImplementation(async (type, id, body, { version }) => {
|
||||
if (object._version !== version) {
|
||||
throw {
|
||||
res: {
|
||||
status: 409
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
object.attributes.title = body.title;
|
||||
object._version += 'a';
|
||||
|
||||
return {
|
||||
id: object._id,
|
||||
_version: object._version,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const patternCache = {
|
||||
clear: jest.fn(),
|
||||
};
|
||||
|
||||
let fields = [];
|
||||
const fieldsFetcher = {
|
||||
fetch: jest.fn().mockImplementation(() => fields),
|
||||
every: jest.fn(),
|
||||
};
|
||||
|
||||
const getIds = {
|
||||
clearCache: jest.fn(),
|
||||
};
|
||||
|
||||
const config = {
|
||||
get: jest.fn(),
|
||||
watchAll: jest.fn(),
|
||||
};
|
||||
const Promise = window.Promise;
|
||||
const confirmModalPromise = jest.fn();
|
||||
const kbnUrl = {
|
||||
eval: jest.fn(),
|
||||
};
|
||||
const i18n = arg => arg;
|
||||
|
||||
const savedObjectsResponse = fixturesStubbedSavedObjectIndexPatternProvider();
|
||||
|
||||
// helper function to create index patterns
|
||||
function create(id, payload) {
|
||||
const indexPattern = new IndexPattern(id, config, savedObjectsClient, patternCache, fieldsFetcher, getIds);
|
||||
|
||||
setDocsourcePayload(id, payload);
|
||||
|
||||
return indexPattern.init();
|
||||
}
|
||||
|
||||
function setDocsourcePayload(id, providedPayload) {
|
||||
object = _.defaults(providedPayload || {}, savedObjectsResponse(id));
|
||||
}
|
||||
|
||||
describe('IndexPattern', () => {
|
||||
it('should handle version conflicts', async () => {
|
||||
const IndexPattern = IndexPatternProvider(Private, config, Promise, confirmModalPromise, kbnUrl, i18n); // eslint-disable-line new-cap
|
||||
const indexPatternId = 'test-pattern';
|
||||
let indexPattern;
|
||||
// create an indexPattern instance for each test
|
||||
beforeEach(function () {
|
||||
return create(indexPatternId).then(function (pattern) {
|
||||
indexPattern = pattern;
|
||||
});
|
||||
});
|
||||
|
||||
describe('api', function () {
|
||||
it('should have expected properties', function () {
|
||||
expect(indexPattern).toHaveProperty('refreshFields');
|
||||
expect(indexPattern).toHaveProperty('popularizeField');
|
||||
expect(indexPattern).toHaveProperty('getScriptedFields');
|
||||
expect(indexPattern).toHaveProperty('getNonScriptedFields');
|
||||
expect(indexPattern).toHaveProperty('addScriptedField');
|
||||
expect(indexPattern).toHaveProperty('removeScriptedField');
|
||||
expect(indexPattern).toHaveProperty('toString');
|
||||
expect(indexPattern).toHaveProperty('toJSON');
|
||||
expect(indexPattern).toHaveProperty('save');
|
||||
|
||||
// properties
|
||||
expect(indexPattern).toHaveProperty('fields');
|
||||
});
|
||||
});
|
||||
|
||||
describe('init', function () {
|
||||
it('should append the found fields', function () {
|
||||
expect(savedObjectsClient.get).toHaveBeenCalled();
|
||||
expect(indexPattern.fields).toHaveLength(mockLogstashFields().length);
|
||||
expect(indexPattern.fields).toBeInstanceOf(IndexedArray);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fields', function () {
|
||||
it('should have expected properties on fields', function () {
|
||||
expect(indexPattern.fields[0]).toHaveProperty('displayName');
|
||||
expect(indexPattern.fields[0]).toHaveProperty('filterable');
|
||||
expect(indexPattern.fields[0]).toHaveProperty('format');
|
||||
expect(indexPattern.fields[0]).toHaveProperty('sortable');
|
||||
expect(indexPattern.fields[0]).toHaveProperty('scripted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getScriptedFields', function () {
|
||||
it('should return all scripted fields', function () {
|
||||
const scriptedNames = _(mockLogstashFields()).where({ scripted: true }).pluck('name').value();
|
||||
const respNames = _.pluck(indexPattern.getScriptedFields(), 'name');
|
||||
expect(respNames).toEqual(scriptedNames);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNonScriptedFields', function () {
|
||||
it('should return all non-scripted fields', function () {
|
||||
const notScriptedNames = _(mockLogstashFields()).where({ scripted: false }).pluck('name').value();
|
||||
const respNames = _.pluck(indexPattern.getNonScriptedFields(), 'name');
|
||||
expect(respNames).toEqual(notScriptedNames);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('refresh fields', function () {
|
||||
it('should fetch fields from the fieldsFetcher', async function () {
|
||||
expect(indexPattern.fields.length).toBeGreaterThan(2);
|
||||
|
||||
fields = [
|
||||
{ name: 'foo' },
|
||||
{ name: 'bar' }
|
||||
];
|
||||
|
||||
|
||||
await indexPattern.refreshFields();
|
||||
expect(fieldsFetcher.fetch).toHaveBeenCalledTimes(1);
|
||||
fields = [];
|
||||
|
||||
const newFields = indexPattern.getNonScriptedFields();
|
||||
expect(newFields).toHaveLength(2);
|
||||
expect(newFields.map(f => f.name)).toEqual(['foo', 'bar']);
|
||||
});
|
||||
|
||||
it('should preserve the scripted fields', async function () {
|
||||
// add spy to indexPattern.getScriptedFields
|
||||
// sinon.spy(indexPattern, 'getScriptedFields');
|
||||
|
||||
// refresh fields, which will fetch
|
||||
await indexPattern.refreshFields();
|
||||
|
||||
// called to append scripted fields to the response from mapper.getFieldsForIndexPattern
|
||||
// sinon.assert.calledOnce(indexPattern.getScriptedFields);
|
||||
expect(indexPattern.getScriptedFields().map(f => f.name))
|
||||
.toEqual(mockLogstashFields().filter(f => f.scripted).map(f => f.name));
|
||||
});
|
||||
});
|
||||
|
||||
describe('add and remove scripted fields', function () {
|
||||
it('should append the scripted field', function () {
|
||||
// keep a copy of the current scripted field count
|
||||
// const saveSpy = sinon.spy(indexPattern, 'save');
|
||||
const oldCount = indexPattern.getScriptedFields().length;
|
||||
|
||||
// add a new scripted field
|
||||
const scriptedField = {
|
||||
name: 'new scripted field',
|
||||
script: 'false',
|
||||
type: 'boolean'
|
||||
};
|
||||
indexPattern.addScriptedField(scriptedField.name, scriptedField.script, scriptedField.type);
|
||||
const scriptedFields = indexPattern.getScriptedFields();
|
||||
// expect(saveSpy.callCount).to.equal(1);
|
||||
expect(scriptedFields).toHaveLength(oldCount + 1);
|
||||
expect(indexPattern.fields.byName[scriptedField.name].name).toEqual(scriptedField.name);
|
||||
});
|
||||
|
||||
it('should remove scripted field, by name', function () {
|
||||
// const saveSpy = sinon.spy(indexPattern, 'save');
|
||||
const scriptedFields = indexPattern.getScriptedFields();
|
||||
const oldCount = scriptedFields.length;
|
||||
const scriptedField = _.last(scriptedFields);
|
||||
|
||||
indexPattern.removeScriptedField(scriptedField.name);
|
||||
|
||||
// expect(saveSpy.callCount).to.equal(1);
|
||||
expect(indexPattern.getScriptedFields().length).toEqual(oldCount - 1);
|
||||
expect(indexPattern.fields.byName[scriptedField.name]).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should not allow duplicate names', function () {
|
||||
const scriptedFields = indexPattern.getScriptedFields();
|
||||
const scriptedField = _.last(scriptedFields);
|
||||
expect(function () {
|
||||
indexPattern.addScriptedField(scriptedField.name, '\'new script\'', 'string');
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('popularizeField', function () {
|
||||
it('should increment the popularity count by default', function () {
|
||||
// const saveSpy = sinon.stub(indexPattern, 'save');
|
||||
indexPattern.fields.forEach(function (field) {
|
||||
const oldCount = field.count;
|
||||
|
||||
indexPattern.popularizeField(field.name);
|
||||
|
||||
// expect(saveSpy.callCount).to.equal(i + 1);
|
||||
expect(field.count).toEqual(oldCount + 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should increment the popularity count', function () {
|
||||
// const saveSpy = sinon.stub(indexPattern, 'save');
|
||||
indexPattern.fields.forEach(function (field) {
|
||||
const oldCount = field.count;
|
||||
const incrementAmount = 4;
|
||||
|
||||
indexPattern.popularizeField(field.name, incrementAmount);
|
||||
|
||||
// expect(saveSpy.callCount).to.equal(i + 1);
|
||||
expect(field.count).toEqual(oldCount + incrementAmount);
|
||||
});
|
||||
});
|
||||
|
||||
it('should decrement the popularity count', function () {
|
||||
indexPattern.fields.forEach(function (field) {
|
||||
const oldCount = field.count;
|
||||
const incrementAmount = 4;
|
||||
const decrementAmount = -2;
|
||||
|
||||
indexPattern.popularizeField(field.name, incrementAmount);
|
||||
indexPattern.popularizeField(field.name, decrementAmount);
|
||||
|
||||
expect(field.count).toEqual(oldCount + incrementAmount + decrementAmount);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not go below 0', function () {
|
||||
indexPattern.fields.forEach(function (field) {
|
||||
const decrementAmount = -Number.MAX_VALUE;
|
||||
indexPattern.popularizeField(field.name, decrementAmount);
|
||||
expect(field.count).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle version conflicts', async () => {
|
||||
setDocsourcePayload(null, {
|
||||
_version: 'foo',
|
||||
_id: 'foo',
|
||||
attributes: {
|
||||
title: 'something'
|
||||
}
|
||||
});
|
||||
// Create a normal index pattern
|
||||
const pattern = new IndexPattern('foo');
|
||||
const pattern = new IndexPattern('foo', config, savedObjectsClient, patternCache, fieldsFetcher, getIds);
|
||||
await pattern.init();
|
||||
|
||||
expect(pattern.version).toBe('fooa');
|
||||
|
||||
// Create the same one - we're going to handle concurrency
|
||||
const samePattern = new IndexPattern('foo');
|
||||
const samePattern = new IndexPattern('foo', config, savedObjectsClient, patternCache, fieldsFetcher, getIds);
|
||||
await samePattern.init();
|
||||
|
||||
expect(samePattern.version).toBe('fooaa');
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexPatterns } from '../index_patterns';
|
||||
|
||||
jest.mock('../errors', () => ({
|
||||
IndexPatternMissingIndices: jest.fn(),
|
||||
}));
|
||||
|
||||
|
||||
jest.mock('../../registry/field_formats', () => ({
|
||||
fieldFormats: {
|
||||
getDefaultInstance: jest.fn(),
|
||||
}
|
||||
}));
|
||||
|
||||
jest.mock('../../notify', () => ({
|
||||
Notifier: jest.fn().mockImplementation(() => ({
|
||||
error: jest.fn(),
|
||||
})),
|
||||
toastNotifications: {
|
||||
addDanger: jest.fn(),
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
jest.mock('../_get', () => ({
|
||||
indexPatternsGetProvider: jest.fn().mockImplementation(() => {
|
||||
return () => {};
|
||||
})
|
||||
}));
|
||||
|
||||
jest.mock('../_index_pattern', () => {
|
||||
class IndexPattern {
|
||||
init = async () => {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
IndexPattern
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../index_patterns_api_client', () => {
|
||||
class IndexPatternsApiClient {
|
||||
getFieldsForWildcard = async () => ({})
|
||||
}
|
||||
|
||||
return {
|
||||
IndexPatternsApiClient
|
||||
};
|
||||
});
|
||||
|
||||
const savedObjectsClient = {
|
||||
create: jest.fn(),
|
||||
get: jest.fn(),
|
||||
update: jest.fn()
|
||||
};
|
||||
|
||||
const config = {
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
|
||||
describe('IndexPatterns', () => {
|
||||
const indexPatterns = new IndexPatterns(config, savedObjectsClient);
|
||||
|
||||
it('does not cache gets without an id', function () {
|
||||
expect(indexPatterns.get()).not.toBe(indexPatterns.get());
|
||||
});
|
||||
|
||||
it('does cache gets for the same id', function () {
|
||||
expect(indexPatterns.get(1)).toBe(indexPatterns.get(1));
|
||||
});
|
||||
});
|
|
@ -19,40 +19,39 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import ngMock from 'ng_mock';
|
||||
import { IndexPatternsFlattenHitProvider } from '../_flatten_hit';
|
||||
import { flattenHitWrapper } from '../_flatten_hit';
|
||||
|
||||
const indexPattern = {
|
||||
fields: {
|
||||
byName: {
|
||||
'tags.text': { type: 'string' },
|
||||
'tags.label': { type: 'string' },
|
||||
'message': { type: 'string' },
|
||||
'geo.coordinates': { type: 'geo_point' },
|
||||
'geo.dest': { type: 'string' },
|
||||
'geo.src': { type: 'string' },
|
||||
'bytes': { type: 'number' },
|
||||
'@timestamp': { type: 'date' },
|
||||
'team': { type: 'nested' },
|
||||
'team.name': { type: 'string' },
|
||||
'team.role': { type: 'string' },
|
||||
'user': { type: 'conflict' },
|
||||
'user.name': { type: 'string' },
|
||||
'user.id': { type: 'conflict' },
|
||||
'delta': { type: 'number', scripted: true }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('IndexPattern#flattenHit()', function () {
|
||||
let flattenHit;
|
||||
let config;
|
||||
let hit;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private, $injector) {
|
||||
const indexPattern = {
|
||||
fields: {
|
||||
byName: {
|
||||
'tags.text': { type: 'string' },
|
||||
'tags.label': { type: 'string' },
|
||||
'message': { type: 'string' },
|
||||
'geo.coordinates': { type: 'geo_point' },
|
||||
'geo.dest': { type: 'string' },
|
||||
'geo.src': { type: 'string' },
|
||||
'bytes': { type: 'number' },
|
||||
'@timestamp': { type: 'date' },
|
||||
'team': { type: 'nested' },
|
||||
'team.name': { type: 'string' },
|
||||
'team.role': { type: 'string' },
|
||||
'user': { type: 'conflict' },
|
||||
'user.name': { type: 'string' },
|
||||
'user.id': { type: 'conflict' },
|
||||
'delta': { type: 'number', scripted: true }
|
||||
}
|
||||
}
|
||||
};
|
||||
beforeEach(ngMock.inject(function () {
|
||||
|
||||
flattenHit = Private(IndexPatternsFlattenHitProvider)(indexPattern);
|
||||
|
||||
config = $injector.get('config');
|
||||
flattenHit = flattenHitWrapper(indexPattern, []);
|
||||
|
||||
hit = {
|
||||
_source: {
|
||||
|
@ -154,14 +153,14 @@ describe('IndexPattern#flattenHit()', function () {
|
|||
});
|
||||
|
||||
it('ignores fields that start with an _ and are not in the metaFields', function () {
|
||||
config.set('metaFields', ['_metaKey']);
|
||||
flattenHit = flattenHitWrapper(indexPattern, ['_metaKey']);
|
||||
hit.fields._notMetaKey = [100];
|
||||
const flat = flattenHit(hit);
|
||||
expect(flat).to.not.have.property('_notMetaKey');
|
||||
});
|
||||
|
||||
it('includes underscore-prefixed keys that are in the metaFields', function () {
|
||||
config.set('metaFields', ['_metaKey']);
|
||||
flattenHit = flattenHitWrapper(indexPattern, ['_metaKey']);
|
||||
hit.fields._metaKey = [100];
|
||||
const flat = flattenHit(hit);
|
||||
expect(flat).to.have.property('_metaKey', 100);
|
||||
|
@ -170,18 +169,18 @@ describe('IndexPattern#flattenHit()', function () {
|
|||
it('adapts to changes in the metaFields', function () {
|
||||
hit.fields._metaKey = [100];
|
||||
|
||||
config.set('metaFields', ['_metaKey']);
|
||||
flattenHit = flattenHitWrapper(indexPattern, ['_metaKey']);
|
||||
let flat = flattenHit(hit);
|
||||
expect(flat).to.have.property('_metaKey', 100);
|
||||
|
||||
config.set('metaFields', []);
|
||||
flattenHit = flattenHitWrapper(indexPattern, []);
|
||||
flat = flattenHit(hit);
|
||||
expect(flat).to.not.have.property('_metaKey');
|
||||
});
|
||||
|
||||
it('handles fields that are not arrays, like _timestamp', function () {
|
||||
hit.fields._metaKey = 20000;
|
||||
config.set('metaFields', ['_metaKey']);
|
||||
flattenHit = flattenHitWrapper(indexPattern, ['_metaKey']);
|
||||
const flat = flattenHit(hit);
|
||||
expect(flat).to.have.property('_metaKey', 20000);
|
||||
});
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import './_index_pattern';
|
||||
import './_get_computed_fields';
|
||||
describe('Index Patterns', function () {
|
||||
});
|
||||
|
|
|
@ -1,54 +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 '@kbn/expect';
|
||||
import sinon from 'sinon';
|
||||
import { IndexPatternProvider } from '../_index_pattern';
|
||||
import { IndexPatternsProvider } from '../index_patterns';
|
||||
|
||||
describe('IndexPatterns service', function () {
|
||||
let indexPatterns;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
const IndexPattern = Private(IndexPatternProvider);
|
||||
indexPatterns = Private(IndexPatternsProvider);
|
||||
|
||||
// prevent IndexPattern initialization from doing anything
|
||||
Private.stub(
|
||||
IndexPatternProvider,
|
||||
function (...args) {
|
||||
const indexPattern = new IndexPattern(...args);
|
||||
sinon.stub(indexPattern, 'init', function () {
|
||||
return new Promise();
|
||||
});
|
||||
return indexPattern;
|
||||
}
|
||||
);
|
||||
}));
|
||||
|
||||
it('does not cache gets without an id', function () {
|
||||
expect(indexPatterns.get()).to.not.be(indexPatterns.get());
|
||||
});
|
||||
|
||||
it('does cache gets for the same id', function () {
|
||||
expect(indexPatterns.get(1)).to.be(indexPatterns.get(1));
|
||||
});
|
||||
});
|
|
@ -1,46 +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 MockLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { IndexPatternsApiClientProvider } from '../index_patterns_api_client_provider';
|
||||
|
||||
// place in a ngMock.module() call to swap out the IndexPatternsApiClient
|
||||
export function StubIndexPatternsApiClientModule(PrivateProvider) {
|
||||
PrivateProvider.swap(
|
||||
IndexPatternsApiClientProvider,
|
||||
(Private, Promise) => {
|
||||
let nonScriptedFields = Private(MockLogstashFieldsProvider).filter(field => (
|
||||
field.scripted !== true
|
||||
));
|
||||
|
||||
class StubIndexPatternsApiClient {
|
||||
getFieldsForTimePattern = sinon.spy(() => Promise.resolve(nonScriptedFields));
|
||||
getFieldsForWildcard = sinon.spy(() => Promise.resolve(nonScriptedFields));
|
||||
|
||||
swapStubNonScriptedFields = (newNonScriptedFields) => {
|
||||
nonScriptedFields = newNonScriptedFields;
|
||||
}
|
||||
}
|
||||
|
||||
return new StubIndexPatternsApiClient();
|
||||
}
|
||||
);
|
||||
}
|
|
@ -17,13 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { ObjDefine } from '../utils/obj_define';
|
||||
import { ObjDefine } from 'ui/utils/obj_define';
|
||||
import { FieldFormat } from '../../field_formats/field_format';
|
||||
import { fieldFormats } from '../registry/field_formats';
|
||||
import { fieldFormats } from 'ui/registry/field_formats';
|
||||
import { getKbnFieldType } from '../../../utils';
|
||||
import { shortenDottedString } from '../../../core_plugins/kibana/common/utils/shorten_dotted_string';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import chrome from 'ui/chrome';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export function Field(indexPattern, spec) {
|
||||
|
@ -39,6 +38,8 @@ export function Field(indexPattern, spec) {
|
|||
spec.type = '_source';
|
||||
}
|
||||
|
||||
const shortDotsEnable = indexPattern.shortDotsEnable;
|
||||
|
||||
// find the type for this field, fallback to unknown type
|
||||
let type = getKbnFieldType(spec.type);
|
||||
if (spec.type && !type) {
|
||||
|
@ -93,7 +94,7 @@ export function Field(indexPattern, spec) {
|
|||
|
||||
// computed values
|
||||
obj.comp('indexPattern', indexPattern);
|
||||
obj.comp('displayName', chrome.getUiSettingsClient().get('shortDots:enable') ? shortenDottedString(spec.name) : spec.name);
|
||||
obj.comp('displayName', shortDotsEnable ? shortenDottedString(spec.name) : spec.name);
|
||||
obj.comp('$$spec', spec);
|
||||
|
||||
// conflict info
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexedArray } from '../indexed_array';
|
||||
import { IndexedArray } from 'ui/indexed_array';
|
||||
import { Field } from './_field';
|
||||
|
||||
export class FieldList extends IndexedArray {
|
||||
|
|
|
@ -23,82 +23,76 @@ const flattenedCache = new WeakMap();
|
|||
|
||||
// Takes a hit, merges it with any stored/scripted fields, and with the metaFields
|
||||
// returns a flattened version
|
||||
export function IndexPatternsFlattenHitProvider(config) {
|
||||
let metaFields = config.get('metaFields');
|
||||
|
||||
config.watch('metaFields', value => {
|
||||
metaFields = value;
|
||||
});
|
||||
function flattenHit(indexPattern, hit, deep) {
|
||||
const flat = {};
|
||||
|
||||
function flattenHit(indexPattern, hit, deep) {
|
||||
const flat = {};
|
||||
// recursively merge _source
|
||||
const fields = indexPattern.fields.byName;
|
||||
(function flatten(obj, keyPrefix) {
|
||||
keyPrefix = keyPrefix ? keyPrefix + '.' : '';
|
||||
_.forOwn(obj, function (val, key) {
|
||||
key = keyPrefix + key;
|
||||
|
||||
// recursively merge _source
|
||||
const fields = indexPattern.fields.byName;
|
||||
(function flatten(obj, keyPrefix) {
|
||||
keyPrefix = keyPrefix ? keyPrefix + '.' : '';
|
||||
_.forOwn(obj, function (val, key) {
|
||||
key = keyPrefix + key;
|
||||
|
||||
if (deep) {
|
||||
const isNestedField = fields[key] && fields[key].type === 'nested';
|
||||
const isArrayOfObjects = Array.isArray(val) && _.isPlainObject(_.first(val));
|
||||
if (isArrayOfObjects && !isNestedField) {
|
||||
_.each(val, v => flatten(v, key));
|
||||
return;
|
||||
}
|
||||
} else if (flat[key] !== void 0) {
|
||||
if (deep) {
|
||||
const isNestedField = fields[key] && fields[key].type === 'nested';
|
||||
const isArrayOfObjects = Array.isArray(val) && _.isPlainObject(_.first(val));
|
||||
if (isArrayOfObjects && !isNestedField) {
|
||||
_.each(val, v => flatten(v, key));
|
||||
return;
|
||||
}
|
||||
|
||||
const hasValidMapping = fields[key] && fields[key].type !== 'conflict';
|
||||
const isValue = !_.isPlainObject(val);
|
||||
|
||||
if (hasValidMapping || isValue) {
|
||||
if (!flat[key]) {
|
||||
flat[key] = val;
|
||||
} else if (Array.isArray(flat[key])) {
|
||||
flat[key].push(val);
|
||||
} else {
|
||||
flat[key] = [ flat[key], val ];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
flatten(val, key);
|
||||
});
|
||||
}(hit._source));
|
||||
|
||||
return flat;
|
||||
}
|
||||
|
||||
function decorateFlattenedWrapper(hit) {
|
||||
return function (flattened) {
|
||||
// assign the meta fields
|
||||
_.each(metaFields, function (meta) {
|
||||
if (meta === '_source') return;
|
||||
flattened[meta] = hit[meta];
|
||||
});
|
||||
|
||||
// unwrap computed fields
|
||||
_.forOwn(hit.fields, function (val, key) {
|
||||
if (key[0] === '_' && !_.contains(metaFields, key)) return;
|
||||
flattened[key] = Array.isArray(val) && val.length === 1 ? val[0] : val;
|
||||
});
|
||||
|
||||
return flattened;
|
||||
};
|
||||
}
|
||||
|
||||
return function flattenHitWrapper(indexPattern) {
|
||||
return function cachedFlatten(hit, deep = false) {
|
||||
const decorateFlattened = decorateFlattenedWrapper(hit);
|
||||
const cached = flattenedCache.get(hit);
|
||||
const flattened = cached || flattenHit(indexPattern, hit, deep);
|
||||
if (!cached) {
|
||||
flattenedCache.set(hit, { ...flattened });
|
||||
} else if (flat[key] !== void 0) {
|
||||
return;
|
||||
}
|
||||
return decorateFlattened(flattened);
|
||||
};
|
||||
|
||||
const hasValidMapping = fields[key] && fields[key].type !== 'conflict';
|
||||
const isValue = !_.isPlainObject(val);
|
||||
|
||||
if (hasValidMapping || isValue) {
|
||||
if (!flat[key]) {
|
||||
flat[key] = val;
|
||||
} else if (Array.isArray(flat[key])) {
|
||||
flat[key].push(val);
|
||||
} else {
|
||||
flat[key] = [ flat[key], val ];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
flatten(val, key);
|
||||
});
|
||||
}(hit._source));
|
||||
|
||||
return flat;
|
||||
}
|
||||
|
||||
function decorateFlattenedWrapper(hit, metaFields) {
|
||||
return function (flattened) {
|
||||
// assign the meta fields
|
||||
_.each(metaFields, function (meta) {
|
||||
if (meta === '_source') return;
|
||||
flattened[meta] = hit[meta];
|
||||
});
|
||||
|
||||
// unwrap computed fields
|
||||
_.forOwn(hit.fields, function (val, key) {
|
||||
if (key[0] === '_' && !_.contains(metaFields, key)) return;
|
||||
flattened[key] = Array.isArray(val) && val.length === 1 ? val[0] : val;
|
||||
});
|
||||
|
||||
return flattened;
|
||||
};
|
||||
}
|
||||
|
||||
export function flattenHitWrapper(indexPattern, metaFields = {}) {
|
||||
|
||||
return function cachedFlatten(hit, deep = false) {
|
||||
const decorateFlattened = decorateFlattenedWrapper(hit, metaFields);
|
||||
const cached = flattenedCache.get(hit);
|
||||
const flattened = cached || flattenHit(indexPattern, hit, deep);
|
||||
if (!cached) {
|
||||
flattenedCache.set(hit, { ...flattened });
|
||||
}
|
||||
return decorateFlattened(flattened);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,14 +18,13 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import chrome from '../chrome';
|
||||
|
||||
const formattedCache = new WeakMap();
|
||||
const partialFormattedCache = new WeakMap();
|
||||
|
||||
// Takes a hit, merges it with any stored/scripted fields, and with the metaFields
|
||||
// returns a formatted version
|
||||
export function formatHit(indexPattern, defaultFormat) {
|
||||
export function formatHitProvider(indexPattern, defaultFormat) {
|
||||
|
||||
function convert(hit, val, fieldName) {
|
||||
const field = indexPattern.fields.byName[fieldName];
|
||||
|
@ -33,7 +32,6 @@ export function formatHit(indexPattern, defaultFormat) {
|
|||
const parsedUrl = {
|
||||
origin: window.location.origin,
|
||||
pathname: window.location.pathname,
|
||||
basePath: chrome.getBasePath(),
|
||||
};
|
||||
return field.format.getConverterFor('html')(val, field, hit, parsedUrl);
|
||||
}
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { SavedObjectsClientProvider } from '../saved_objects';
|
||||
|
||||
export function IndexPatternsGetProvider(Private) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
export function indexPatternsGetProvider(savedObjectsClient) {
|
||||
|
||||
// many places may require the id list, so we will cache it separately
|
||||
// didn't incorporate with the indexPattern cache to prevent id collisions.
|
||||
|
|
|
@ -17,23 +17,24 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { each, reject } from 'lodash';
|
||||
|
||||
export function getComputedFields() {
|
||||
const self = this;
|
||||
const scriptFields = {};
|
||||
let docvalueFields = [];
|
||||
|
||||
// Date value returned in "_source" could be in any number of formats
|
||||
// Use a docvalue for each date field to ensure standardized formats when working with date fields
|
||||
// indexPattern.flattenHit will override "_source" values when the same field is also defined in "fields"
|
||||
docvalueFields = _.reject(self.fields.byType.date, 'scripted')
|
||||
.map((dateField) => ({
|
||||
field: dateField.name,
|
||||
format: dateField.esTypes && dateField.esTypes.indexOf('date_nanos') !== -1 ? 'strict_date_time' : 'date_time',
|
||||
}));
|
||||
const docvalueFields = reject(self.fields.byType.date, 'scripted')
|
||||
.map((dateField) => {
|
||||
return {
|
||||
field: dateField.name,
|
||||
format: dateField.esTypes && dateField.esTypes.indexOf('date_nanos') !== -1 ? 'strict_date_time' : 'date_time',
|
||||
};
|
||||
});
|
||||
|
||||
_.each(self.getScriptedFields(), function (field) {
|
||||
each(self.getScriptedFields(), function (field) {
|
||||
scriptFields[field.name] = {
|
||||
script: {
|
||||
source: field.script,
|
||||
|
|
|
@ -18,91 +18,132 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { SavedObjectNotFound, DuplicateField, IndexPatternMissingIndices } from '../errors';
|
||||
import angular from 'angular';
|
||||
import { fieldFormats } from '../registry/field_formats';
|
||||
import UtilsMappingSetupProvider from '../utils/mapping_setup';
|
||||
import { toastNotifications } from '../notify';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
|
||||
import { getComputedFields } from './_get_computed_fields';
|
||||
import { formatHit } from './_format_hit';
|
||||
import { IndexPatternsGetProvider } from './_get';
|
||||
import { FieldList } from './_field_list';
|
||||
import { IndexPatternsFlattenHitProvider } from './_flatten_hit';
|
||||
import { IndexPatternsPatternCacheProvider } from './_pattern_cache';
|
||||
import { FieldsFetcherProvider } from './fields_fetcher_provider';
|
||||
import { SavedObjectsClientProvider, findObjectByTitle } from '../saved_objects';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export function getRoutes() {
|
||||
return {
|
||||
edit: '/management/kibana/index_patterns/{{id}}',
|
||||
addField: '/management/kibana/index_patterns/{{id}}/create-field',
|
||||
indexedFields: '/management/kibana/index_patterns/{{id}}?_a=(tab:indexedFields)',
|
||||
scriptedFields: '/management/kibana/index_patterns/{{id}}?_a=(tab:scriptedFields)',
|
||||
sourceFilters: '/management/kibana/index_patterns/{{id}}?_a=(tab:sourceFilters)'
|
||||
};
|
||||
}
|
||||
import { SavedObjectNotFound, DuplicateField } from 'ui/errors';
|
||||
import { fieldFormats } from 'ui/registry/field_formats';
|
||||
import { expandShorthand } from 'ui/utils/mapping_setup';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { findObjectByTitle } from 'ui/saved_objects';
|
||||
|
||||
import { IndexPatternMissingIndices } from './errors';
|
||||
import { getComputedFields } from './_get_computed_fields';
|
||||
import { getRoutes } from './get_routes';
|
||||
import { formatHitProvider } from './_format_hit';
|
||||
import { FieldList } from './_field_list';
|
||||
import { flattenHitWrapper } from './_flatten_hit';
|
||||
|
||||
const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3;
|
||||
const type = 'index-pattern';
|
||||
|
||||
export function IndexPatternProvider(Private, config, Promise, kbnUrl) {
|
||||
const getConfig = (...args) => config.get(...args);
|
||||
const getIds = Private(IndexPatternsGetProvider)('id');
|
||||
const fieldsFetcher = Private(FieldsFetcherProvider);
|
||||
const mappingSetup = Private(UtilsMappingSetupProvider);
|
||||
const flattenHit = Private(IndexPatternsFlattenHitProvider);
|
||||
const patternCache = Private(IndexPatternsPatternCacheProvider);
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
const fieldformats = fieldFormats;
|
||||
export class IndexPattern {
|
||||
constructor(id, config, savedObjectsClient, patternCache, fieldsFetcher, getIds) {
|
||||
this._setId(id);
|
||||
this.config = config;
|
||||
this.savedObjectsClient = savedObjectsClient;
|
||||
this.patternCache = patternCache;
|
||||
this.fieldsFetcher = fieldsFetcher;
|
||||
this.getIds = getIds;
|
||||
|
||||
const type = 'index-pattern';
|
||||
const configWatchers = new WeakMap();
|
||||
this.metaFields = config.get('metaFields');
|
||||
this.shortDotsEnable = config.get('shortDots:enable');
|
||||
this.getComputedFields = getComputedFields.bind(this);
|
||||
|
||||
const mapping = mappingSetup.expandShorthand({
|
||||
title: 'text',
|
||||
timeFieldName: 'keyword',
|
||||
intervalName: 'keyword',
|
||||
fields: 'json',
|
||||
sourceFilters: 'json',
|
||||
fieldFormatMap: {
|
||||
type: 'text',
|
||||
_serialize(map = {}) {
|
||||
const serialized = _.transform(map, serializeFieldFormatMap);
|
||||
return _.isEmpty(serialized) ? undefined : angular.toJson(serialized);
|
||||
},
|
||||
_deserialize(map = '{}') {
|
||||
return _.mapValues(angular.fromJson(map), deserializeFieldFormatMap);
|
||||
this.flattenHit = flattenHitWrapper(this, this.metaFields);
|
||||
this.formatHit = formatHitProvider(this, fieldFormats.getDefaultInstance('string'));
|
||||
this.formatField = this.formatHit.formatField;
|
||||
|
||||
const getConfig = cfg => config.get(cfg);
|
||||
function serializeFieldFormatMap(flat, format, field) {
|
||||
if (format) {
|
||||
flat[field] = format;
|
||||
}
|
||||
},
|
||||
type: 'keyword',
|
||||
typeMeta: 'json',
|
||||
});
|
||||
|
||||
function serializeFieldFormatMap(flat, format, field) {
|
||||
if (format) {
|
||||
flat[field] = format;
|
||||
}
|
||||
|
||||
function deserializeFieldFormatMap(mapping) {
|
||||
const FieldFormat = fieldFormats.byId[mapping.id];
|
||||
return FieldFormat && new FieldFormat(mapping.params, getConfig);
|
||||
}
|
||||
|
||||
this.mapping = expandShorthand({
|
||||
title: 'text',
|
||||
timeFieldName: 'keyword',
|
||||
intervalName: 'keyword',
|
||||
fields: 'json',
|
||||
sourceFilters: 'json',
|
||||
fieldFormatMap: {
|
||||
type: 'text',
|
||||
_serialize(map = {}) {
|
||||
const serialized = _.transform(map, serializeFieldFormatMap);
|
||||
return _.isEmpty(serialized) ? undefined : JSON.stringify(serialized);
|
||||
},
|
||||
_deserialize(map = '{}') {
|
||||
return _.mapValues(JSON.parse(map), mapping => { return deserializeFieldFormatMap(mapping); });
|
||||
}
|
||||
},
|
||||
type: 'keyword',
|
||||
typeMeta: 'json',
|
||||
});
|
||||
}
|
||||
|
||||
function deserializeFieldFormatMap(mapping) {
|
||||
const FieldFormat = fieldformats.byId[mapping.id];
|
||||
return FieldFormat && new FieldFormat(mapping.params, getConfig);
|
||||
_setId(id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
function updateFromElasticSearch(indexPattern, response, forceFieldRefresh = false) {
|
||||
_setVersion(version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
_initFields(input) {
|
||||
const oldValue = this.fields;
|
||||
const newValue = input || oldValue || [];
|
||||
this.fields = new FieldList(this, newValue);
|
||||
}
|
||||
|
||||
async _indexFields(forceFieldRefresh = false) {
|
||||
if (!this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
function isFieldRefreshRequired(indexPattern) {
|
||||
if (!indexPattern.fields) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return indexPattern.fields.every(field => {
|
||||
// See https://github.com/elastic/kibana/pull/8421
|
||||
const hasFieldCaps = ('aggregatable' in field) && ('searchable' in field);
|
||||
|
||||
// See https://github.com/elastic/kibana/pull/11969
|
||||
const hasDocValuesFlag = ('readFromDocValues' in field);
|
||||
|
||||
return !hasFieldCaps || !hasDocValuesFlag;
|
||||
});
|
||||
}
|
||||
|
||||
if (forceFieldRefresh || isFieldRefreshRequired(this)) {
|
||||
await this.refreshFields();
|
||||
}
|
||||
|
||||
this._initFields();
|
||||
}
|
||||
|
||||
_updateFromElasticSearch(response, forceFieldRefresh = false) {
|
||||
if (!response.found) {
|
||||
throw new SavedObjectNotFound(
|
||||
type,
|
||||
indexPattern.id,
|
||||
this.id,
|
||||
'#/management/kibana/index_pattern',
|
||||
);
|
||||
}
|
||||
|
||||
_.forOwn(mapping, (fieldMapping, name) => {
|
||||
_.forOwn(this.mapping, (fieldMapping, name) => {
|
||||
if (!fieldMapping._deserialize) {
|
||||
return;
|
||||
}
|
||||
|
@ -110,13 +151,13 @@ export function IndexPatternProvider(Private, config, Promise, kbnUrl) {
|
|||
});
|
||||
|
||||
// give index pattern all of the values in _source
|
||||
_.assign(indexPattern, response._source);
|
||||
_.assign(this, response._source);
|
||||
|
||||
if (!indexPattern.title) {
|
||||
indexPattern.title = indexPattern.id;
|
||||
if (!this.title) {
|
||||
this.title = this.id;
|
||||
}
|
||||
|
||||
if (indexPattern.isUnsupportedTimePattern()) {
|
||||
if (this.isUnsupportedTimePattern()) {
|
||||
const warningTitle = i18n.translate('common.ui.indexPattern.warningTitle', {
|
||||
defaultMessage: 'Support for time interval index patterns removed',
|
||||
});
|
||||
|
@ -124,11 +165,13 @@ export function IndexPatternProvider(Private, config, Promise, kbnUrl) {
|
|||
const warningText = i18n.translate('common.ui.indexPattern.warningText', {
|
||||
defaultMessage: 'Currently querying all indices matching {index}. {title} should be migrated to a wildcard-based index pattern.',
|
||||
values: {
|
||||
title: indexPattern.title,
|
||||
index: indexPattern.getIndex()
|
||||
title: this.title,
|
||||
index: this.getIndex()
|
||||
}
|
||||
});
|
||||
|
||||
const url = `#${this.routes.edit.replace('{{id}}', this.id)}`;
|
||||
|
||||
toastNotifications.addWarning({
|
||||
title: warningTitle,
|
||||
text: (
|
||||
|
@ -136,7 +179,7 @@ export function IndexPatternProvider(Private, config, Promise, kbnUrl) {
|
|||
<p>{warningText}</p>
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="s" href={kbnUrl.getRouteHref(indexPattern, 'edit')}>
|
||||
<EuiButton size="s" href={url}>
|
||||
<FormattedMessage id="common.ui.indexPattern.editIndexPattern" defaultMessage="Edit index pattern" />
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
|
@ -146,392 +189,308 @@ export function IndexPatternProvider(Private, config, Promise, kbnUrl) {
|
|||
});
|
||||
}
|
||||
|
||||
return indexFields(indexPattern, forceFieldRefresh);
|
||||
return this._indexFields(forceFieldRefresh);
|
||||
}
|
||||
|
||||
function isFieldRefreshRequired(indexPattern) {
|
||||
if (!indexPattern.fields) {
|
||||
return true;
|
||||
get routes() {
|
||||
return getRoutes();
|
||||
}
|
||||
|
||||
async init(forceFieldRefresh = false) {
|
||||
|
||||
if (!this.id) {
|
||||
return this; // no id === no elasticsearch document
|
||||
}
|
||||
|
||||
return indexPattern.fields.every(field => {
|
||||
// See https://github.com/elastic/kibana/pull/8421
|
||||
const hasFieldCaps = ('aggregatable' in field) && ('searchable' in field);
|
||||
const savedObject = await this.savedObjectsClient.get(type, this.id);
|
||||
this._setVersion(savedObject._version);
|
||||
|
||||
// See https://github.com/elastic/kibana/pull/11969
|
||||
const hasDocValuesFlag = ('readFromDocValues' in field);
|
||||
const response = {
|
||||
_id: savedObject.id,
|
||||
_type: savedObject.type,
|
||||
_source: _.cloneDeep(savedObject.attributes),
|
||||
found: savedObject._version ? true : false
|
||||
};
|
||||
// Do this before we attempt to update from ES since that call can potentially perform a save
|
||||
this.originalBody = this.prepBody();
|
||||
await this._updateFromElasticSearch(response, forceFieldRefresh);
|
||||
// Do it after to ensure we have the most up to date information
|
||||
this.originalBody = this.prepBody();
|
||||
|
||||
return !hasFieldCaps || !hasDocValuesFlag;
|
||||
return this;
|
||||
}
|
||||
|
||||
migrate(newTitle) {
|
||||
return this.savedObjectsClient.update(type, this.id, {
|
||||
title: newTitle,
|
||||
intervalName: null
|
||||
}).then(({ attributes: { title, intervalName } }) => {
|
||||
this.title = title;
|
||||
this.intervalName = intervalName;
|
||||
}).then(() => this);
|
||||
}
|
||||
|
||||
// Get the source filtering configuration for that index.
|
||||
getSourceFiltering() {
|
||||
return {
|
||||
excludes: this.sourceFilters && this.sourceFilters.map(filter => filter.value) || []
|
||||
};
|
||||
}
|
||||
|
||||
addScriptedField(name, script, type = 'string', lang) {
|
||||
const scriptedFields = this.getScriptedFields();
|
||||
const names = _.pluck(scriptedFields, 'name');
|
||||
|
||||
if (_.contains(names, name)) {
|
||||
throw new DuplicateField(name);
|
||||
}
|
||||
|
||||
this.fields.push({
|
||||
name: name,
|
||||
script: script,
|
||||
type: type,
|
||||
scripted: true,
|
||||
lang: lang
|
||||
});
|
||||
|
||||
this.save();
|
||||
}
|
||||
|
||||
function indexFields(indexPattern, forceFieldRefresh = false) {
|
||||
let promise = Promise.resolve();
|
||||
|
||||
if (!indexPattern.id) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (forceFieldRefresh || isFieldRefreshRequired(indexPattern)) {
|
||||
promise = indexPattern.refreshFields();
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
initFields(indexPattern);
|
||||
removeScriptedField(name) {
|
||||
const fieldIndex = _.findIndex(this.fields, {
|
||||
name: name,
|
||||
scripted: true
|
||||
});
|
||||
|
||||
if(fieldIndex > -1) {
|
||||
this.fields.splice(fieldIndex, 1);
|
||||
delete this.fieldFormatMap[name];
|
||||
return this.save();
|
||||
}
|
||||
}
|
||||
|
||||
function setId(indexPattern, id) {
|
||||
indexPattern.id = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
function setVersion(indexPattern, version) {
|
||||
indexPattern.version = version;
|
||||
return version;
|
||||
}
|
||||
|
||||
function watch(indexPattern) {
|
||||
if (configWatchers.has(indexPattern)) {
|
||||
popularizeField(fieldName, unit = 1) {
|
||||
const field = _.get(this, ['fields', 'byName', fieldName]);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
const unwatch = config.watchAll(() => {
|
||||
if (indexPattern.fields) {
|
||||
initFields(indexPattern); // re-init fields when config changes, but only if we already had fields
|
||||
}
|
||||
});
|
||||
configWatchers.set(indexPattern, { unwatch });
|
||||
}
|
||||
|
||||
function unwatch(indexPattern) {
|
||||
if (!configWatchers.has(indexPattern)) {
|
||||
const count = Math.max((field.count || 0) + unit, 0);
|
||||
if (field.count === count) {
|
||||
return;
|
||||
}
|
||||
configWatchers.get(indexPattern).unwatch();
|
||||
configWatchers.delete(indexPattern);
|
||||
field.count = count;
|
||||
this.save();
|
||||
}
|
||||
|
||||
function initFields(indexPattern, input) {
|
||||
const oldValue = indexPattern.fields;
|
||||
const newValue = input || oldValue || [];
|
||||
indexPattern.fields = new FieldList(indexPattern, newValue);
|
||||
getNonScriptedFields() {
|
||||
return _.where(this.fields, { scripted: false });
|
||||
}
|
||||
|
||||
function fetchFields(indexPattern) {
|
||||
return Promise.resolve()
|
||||
.then(() => fieldsFetcher.fetch(indexPattern))
|
||||
.then(fields => {
|
||||
const scripted = indexPattern.getScriptedFields();
|
||||
const all = fields.concat(scripted);
|
||||
initFields(indexPattern, all);
|
||||
});
|
||||
getScriptedFields() {
|
||||
return _.where(this.fields, { scripted: true });
|
||||
}
|
||||
|
||||
class IndexPattern {
|
||||
constructor(id) {
|
||||
setId(this, id);
|
||||
this.metaFields = config.get('metaFields');
|
||||
this.getComputedFields = getComputedFields.bind(this);
|
||||
|
||||
this.flattenHit = flattenHit(this);
|
||||
this.formatHit = formatHit(this, fieldformats.getDefaultInstance('string'));
|
||||
this.formatField = this.formatHit.formatField;
|
||||
getIndex() {
|
||||
if (!this.isUnsupportedTimePattern()) {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
get routes() {
|
||||
return getRoutes();
|
||||
}
|
||||
// Take a time-based interval index pattern title (like [foo-]YYYY.MM.DD[-bar]) and turn it
|
||||
// into the actual index (like foo-*-bar) by replacing anything not inside square brackets
|
||||
// with a *.
|
||||
const regex = /\[[^\]]*]/g; // Matches text inside brackets
|
||||
const splits = this.title.split(regex); // e.g. ['', 'YYYY.MM.DD', ''] from the above example
|
||||
const matches = this.title.match(regex); // e.g. ['[foo-]', '[-bar]'] from the above example
|
||||
return splits.map((split, i) => {
|
||||
const match = i >= matches.length ? '' : matches[i].replace(/[\[\]]/g, '');
|
||||
return `${split.length ? '*' : ''}${match}`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
init(forceFieldRefresh = false) {
|
||||
watch(this);
|
||||
isTimeBased() {
|
||||
return !!this.timeFieldName && (!this.fields || !!this.getTimeField());
|
||||
}
|
||||
|
||||
if (!this.id) {
|
||||
return Promise.resolve(this); // no id === no elasticsearch document
|
||||
isTimeNanosBased() {
|
||||
const timeField = this.getTimeField();
|
||||
return timeField && timeField.esTypes && timeField.esTypes.indexOf('date_nanos') !== -1;
|
||||
}
|
||||
|
||||
isUnsupportedTimePattern() {
|
||||
return !!this.intervalName;
|
||||
}
|
||||
|
||||
isTimeBasedWildcard() {
|
||||
return this.isTimeBased() && this.isWildcard();
|
||||
}
|
||||
|
||||
getTimeField() {
|
||||
if (!this.timeFieldName || !this.fields || !this.fields.byName) return;
|
||||
return this.fields.byName[this.timeFieldName];
|
||||
}
|
||||
|
||||
isWildcard() {
|
||||
return _.includes(this.title, '*');
|
||||
}
|
||||
|
||||
prepBody() {
|
||||
const body = {};
|
||||
|
||||
// serialize json fields
|
||||
_.forOwn(this.mapping, (fieldMapping, fieldName) => {
|
||||
if (this[fieldName] != null) {
|
||||
body[fieldName] = (fieldMapping._serialize)
|
||||
? fieldMapping._serialize(this[fieldName])
|
||||
: this[fieldName];
|
||||
}
|
||||
});
|
||||
|
||||
// clear the indexPattern list cache
|
||||
this.getIds.clearCache();
|
||||
return body;
|
||||
}
|
||||
|
||||
async create(allowOverride = false) {
|
||||
const _create = async (duplicateId) => {
|
||||
if (duplicateId) {
|
||||
const duplicatePattern = new IndexPattern(duplicateId,
|
||||
this.config,
|
||||
this.savedObjectsClient,
|
||||
this.patternCache,
|
||||
this.fieldsFetcher,
|
||||
this.getIds);
|
||||
await duplicatePattern.destroy();
|
||||
}
|
||||
|
||||
return savedObjectsClient.get(type, this.id)
|
||||
.then(resp => {
|
||||
// temporary compatability for savedObjectsClient
|
||||
|
||||
setVersion(this, resp._version);
|
||||
|
||||
return {
|
||||
_id: resp.id,
|
||||
_type: resp.type,
|
||||
_source: _.cloneDeep(resp.attributes),
|
||||
found: resp._version ? true : false
|
||||
};
|
||||
})
|
||||
// Do this before we attempt to update from ES
|
||||
// since that call can potentially perform a save
|
||||
.then(response => {
|
||||
this.originalBody = this.prepBody();
|
||||
return response;
|
||||
})
|
||||
.then(response => updateFromElasticSearch(this, response, forceFieldRefresh))
|
||||
// Do it after to ensure we have the most up to date information
|
||||
.then(() => {
|
||||
this.originalBody = this.prepBody();
|
||||
})
|
||||
.then(() => this);
|
||||
}
|
||||
|
||||
migrate(newTitle) {
|
||||
return savedObjectsClient.update(type, this.id, {
|
||||
title: newTitle,
|
||||
intervalName: null
|
||||
}).then(({ attributes: { title, intervalName } }) => {
|
||||
this.title = title;
|
||||
this.intervalName = intervalName;
|
||||
}).then(() => this);
|
||||
}
|
||||
|
||||
// Get the source filtering configuration for that index.
|
||||
getSourceFiltering() {
|
||||
return {
|
||||
excludes: this.sourceFilters && this.sourceFilters.map(filter => filter.value) || []
|
||||
};
|
||||
}
|
||||
|
||||
addScriptedField(name, script, type = 'string', lang) {
|
||||
const scriptedFields = this.getScriptedFields();
|
||||
const names = _.pluck(scriptedFields, 'name');
|
||||
|
||||
if (_.contains(names, name)) {
|
||||
throw new DuplicateField(name);
|
||||
}
|
||||
|
||||
this.fields.push({
|
||||
name: name,
|
||||
script: script,
|
||||
type: type,
|
||||
scripted: true,
|
||||
lang: lang
|
||||
});
|
||||
|
||||
this.save();
|
||||
}
|
||||
|
||||
removeScriptedField(name) {
|
||||
const fieldIndex = _.findIndex(this.fields, {
|
||||
name: name,
|
||||
scripted: true
|
||||
});
|
||||
|
||||
if(fieldIndex > -1) {
|
||||
this.fields.splice(fieldIndex, 1);
|
||||
delete this.fieldFormatMap[name];
|
||||
return this.save();
|
||||
}
|
||||
}
|
||||
|
||||
popularizeField(fieldName, unit = 1) {
|
||||
const field = _.get(this, ['fields', 'byName', fieldName]);
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
const count = Math.max((field.count || 0) + unit, 0);
|
||||
if (field.count === count) {
|
||||
return;
|
||||
}
|
||||
field.count = count;
|
||||
this.save();
|
||||
}
|
||||
|
||||
getNonScriptedFields() {
|
||||
return _.where(this.fields, { scripted: false });
|
||||
}
|
||||
|
||||
getScriptedFields() {
|
||||
return _.where(this.fields, { scripted: true });
|
||||
}
|
||||
|
||||
getIndex() {
|
||||
if (!this.isUnsupportedTimePattern()) {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
// Take a time-based interval index pattern title (like [foo-]YYYY.MM.DD[-bar]) and turn it
|
||||
// into the actual index (like foo-*-bar) by replacing anything not inside square brackets
|
||||
// with a *.
|
||||
const regex = /\[[^\]]*]/g; // Matches text inside brackets
|
||||
const splits = this.title.split(regex); // e.g. ['', 'YYYY.MM.DD', ''] from the above example
|
||||
const matches = this.title.match(regex); // e.g. ['[foo-]', '[-bar]'] from the above example
|
||||
return splits.map((split, i) => {
|
||||
const match = i >= matches.length ? '' : matches[i].replace(/[\[\]]/g, '');
|
||||
return `${split.length ? '*' : ''}${match}`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
isTimeBased() {
|
||||
return !!this.timeFieldName && (!this.fields || !!this.getTimeField());
|
||||
}
|
||||
|
||||
isTimeNanosBased() {
|
||||
const timeField = this.getTimeField();
|
||||
return timeField && timeField.esTypes && timeField.esTypes.indexOf('date_nanos') !== -1;
|
||||
}
|
||||
|
||||
isUnsupportedTimePattern() {
|
||||
return !!this.intervalName;
|
||||
}
|
||||
|
||||
isTimeBasedWildcard() {
|
||||
return this.isTimeBased() && this.isWildcard();
|
||||
}
|
||||
|
||||
getTimeField() {
|
||||
if (!this.timeFieldName || !this.fields || !this.fields.byName) return;
|
||||
return this.fields.byName[this.timeFieldName];
|
||||
}
|
||||
|
||||
isWildcard() {
|
||||
return _.includes(this.title, '*');
|
||||
}
|
||||
|
||||
prepBody() {
|
||||
const body = {};
|
||||
|
||||
// serialize json fields
|
||||
_.forOwn(mapping, (fieldMapping, fieldName) => {
|
||||
if (this[fieldName] != null) {
|
||||
body[fieldName] = (fieldMapping._serialize)
|
||||
? fieldMapping._serialize(this[fieldName])
|
||||
: this[fieldName];
|
||||
}
|
||||
});
|
||||
|
||||
// clear the indexPattern list cache
|
||||
getIds.clearCache();
|
||||
return body;
|
||||
}
|
||||
|
||||
async create(allowOverride = false) {
|
||||
const _create = async (duplicateId) => {
|
||||
if (duplicateId) {
|
||||
const duplicatePattern = new IndexPattern(duplicateId);
|
||||
await duplicatePattern.destroy();
|
||||
}
|
||||
|
||||
const body = this.prepBody();
|
||||
const response = await savedObjectsClient.create(type, body, { id: this.id });
|
||||
return setId(this, response.id);
|
||||
};
|
||||
|
||||
const potentialDuplicateByTitle = await findObjectByTitle(savedObjectsClient, type, this.title);
|
||||
// If there is potentially duplicate title, just create it
|
||||
if (!potentialDuplicateByTitle) {
|
||||
return await _create();
|
||||
}
|
||||
|
||||
// We found a duplicate but we aren't allowing override, show the warn modal
|
||||
if (!allowOverride) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await _create(potentialDuplicateByTitle.id);
|
||||
}
|
||||
|
||||
save(saveAttempts = 0) {
|
||||
const body = this.prepBody();
|
||||
// What keys changed since they last pulled the index pattern
|
||||
const originalChangedKeys = Object.keys(body).filter(key => body[key] !== this.originalBody[key]);
|
||||
return savedObjectsClient.update(type, this.id, body, { version: this.version })
|
||||
.then(({ id, _version }) => {
|
||||
setId(this, id);
|
||||
setVersion(this, _version);
|
||||
})
|
||||
.catch(err => {
|
||||
if (_.get(err, 'res.status') === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS) {
|
||||
const samePattern = new IndexPattern(this.id);
|
||||
return samePattern.init()
|
||||
.then(() => {
|
||||
// What keys changed from now and what the server returned
|
||||
const updatedBody = samePattern.prepBody();
|
||||
const response = await this.savedObjectsClient.create(type, body, { id: this.id });
|
||||
|
||||
// Build a list of changed keys from the server response
|
||||
// and ensure we ignore the key if the server response
|
||||
// is the same as the original response (since that is expected
|
||||
// if we made a change in that key)
|
||||
const serverChangedKeys = Object.keys(updatedBody).filter(key => {
|
||||
return updatedBody[key] !== body[key] && this.originalBody[key] !== updatedBody[key];
|
||||
});
|
||||
this._setId(response.id);
|
||||
return response.id;
|
||||
};
|
||||
|
||||
let unresolvedCollision = false;
|
||||
for (const originalKey of originalChangedKeys) {
|
||||
for (const serverKey of serverChangedKeys) {
|
||||
if (originalKey === serverKey) {
|
||||
unresolvedCollision = true;
|
||||
break;
|
||||
}
|
||||
const potentialDuplicateByTitle = await findObjectByTitle(this.savedObjectsClient, type, this.title);
|
||||
// If there is potentially duplicate title, just create it
|
||||
if (!potentialDuplicateByTitle) {
|
||||
return await _create();
|
||||
}
|
||||
|
||||
// We found a duplicate but we aren't allowing override, show the warn modal
|
||||
if (!allowOverride) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return await _create(potentialDuplicateByTitle.id);
|
||||
}
|
||||
|
||||
save(saveAttempts = 0) {
|
||||
const body = this.prepBody();
|
||||
// What keys changed since they last pulled the index pattern
|
||||
const originalChangedKeys = Object.keys(body).filter(key => body[key] !== this.originalBody[key]);
|
||||
return this.savedObjectsClient.update(type, this.id, body, { version: this.version })
|
||||
.then(({ id, _version }) => {
|
||||
this._setId(id);
|
||||
this._setVersion(_version);
|
||||
})
|
||||
.catch(err => {
|
||||
if (_.get(err, 'res.status') === 409 && saveAttempts++ < MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS) {
|
||||
const samePattern = new IndexPattern(this.id,
|
||||
this.config,
|
||||
this.savedObjectsClient,
|
||||
this.patternCache,
|
||||
this.fieldsFetcher,
|
||||
this.getIds);
|
||||
return samePattern.init()
|
||||
.then(() => {
|
||||
// What keys changed from now and what the server returned
|
||||
const updatedBody = samePattern.prepBody();
|
||||
|
||||
// Build a list of changed keys from the server response
|
||||
// and ensure we ignore the key if the server response
|
||||
// is the same as the original response (since that is expected
|
||||
// if we made a change in that key)
|
||||
const serverChangedKeys = Object.keys(updatedBody).filter(key => {
|
||||
return updatedBody[key] !== body[key] && this.originalBody[key] !== updatedBody[key];
|
||||
});
|
||||
|
||||
let unresolvedCollision = false;
|
||||
for (const originalKey of originalChangedKeys) {
|
||||
for (const serverKey of serverChangedKeys) {
|
||||
if (originalKey === serverKey) {
|
||||
unresolvedCollision = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unresolvedCollision) {
|
||||
const message = i18n.translate(
|
||||
'common.ui.indexPattern.unableWriteLabel',
|
||||
{ defaultMessage: 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.' } // eslint-disable-line max-len
|
||||
);
|
||||
toastNotifications.addDanger(message);
|
||||
throw err;
|
||||
}
|
||||
if (unresolvedCollision) {
|
||||
const message = i18n.translate(
|
||||
'common.ui.indexPattern.unableWriteLabel',
|
||||
{ defaultMessage: 'Unable to write index pattern! Refresh the page to get the most up to date changes for this index pattern.' } // eslint-disable-line max-len
|
||||
);
|
||||
toastNotifications.addDanger(message);
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Set the updated response on this object
|
||||
serverChangedKeys.forEach(key => {
|
||||
this[key] = samePattern[key];
|
||||
});
|
||||
|
||||
setVersion(this, samePattern.version);
|
||||
|
||||
// Clear cache
|
||||
patternCache.clear(this.id);
|
||||
|
||||
// Try the save again
|
||||
return this.save(saveAttempts);
|
||||
// Set the updated response on this object
|
||||
serverChangedKeys.forEach(key => {
|
||||
this[key] = samePattern[key];
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
refreshFields() {
|
||||
return fetchFields(this)
|
||||
.then(() => this.save())
|
||||
.catch((err) => {
|
||||
// https://github.com/elastic/kibana/issues/9224
|
||||
// This call will attempt to remap fields from the matching
|
||||
// ES index which may not actually exist. In that scenario,
|
||||
// we still want to notify the user that there is a problem
|
||||
// but we do not want to potentially make any pages unusable
|
||||
// so do not rethrow the error here
|
||||
if (err instanceof IndexPatternMissingIndices) {
|
||||
toastNotifications.addDanger(err.message);
|
||||
return [];
|
||||
}
|
||||
this._setVersion(samePattern.version);
|
||||
|
||||
toastNotifications.addError(err, {
|
||||
title: i18n.translate('common.ui.indexPattern.fetchFieldErrorTitle', {
|
||||
defaultMessage: 'Error fetching fields',
|
||||
}),
|
||||
});
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
// Clear cache
|
||||
this.patternCache.clear(this.id);
|
||||
|
||||
toJSON() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return '' + this.toJSON();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
unwatch(this);
|
||||
patternCache.clear(this.id);
|
||||
return savedObjectsClient.delete(type, this.id);
|
||||
}
|
||||
// Try the save again
|
||||
return this.save(saveAttempts);
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
return IndexPattern;
|
||||
async _fetchFields() {
|
||||
const fields = await this.fieldsFetcher.fetch(this);
|
||||
const scripted = this.getScriptedFields();
|
||||
const all = fields.concat(scripted);
|
||||
await this._initFields(all);
|
||||
}
|
||||
|
||||
refreshFields() {
|
||||
return this._fetchFields()
|
||||
.then(() => this.save())
|
||||
.catch((err) => {
|
||||
// https://github.com/elastic/kibana/issues/9224
|
||||
// This call will attempt to remap fields from the matching
|
||||
// ES index which may not actually exist. In that scenario,
|
||||
// we still want to notify the user that there is a problem
|
||||
// but we do not want to potentially make any pages unusable
|
||||
// so do not rethrow the error here
|
||||
if (err instanceof IndexPatternMissingIndices) {
|
||||
toastNotifications.addDanger(err.message);
|
||||
return [];
|
||||
}
|
||||
|
||||
toastNotifications.addError(err, {
|
||||
title: i18n.translate('common.ui.indexPattern.fetchFieldErrorTitle', {
|
||||
defaultMessage: 'Error fetching fields',
|
||||
}),
|
||||
});
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return '' + this.toJSON();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.patternCache.clear(this.id);
|
||||
return this.savedObjectsClient.delete(type, this.id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,32 +17,35 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export function IndexPatternsPatternCacheProvider() {
|
||||
export function createIndexPatternCache() {
|
||||
|
||||
const vals = {};
|
||||
const cache = {};
|
||||
|
||||
const validId = function (id) {
|
||||
return typeof id !== 'object';
|
||||
};
|
||||
|
||||
this.get = function (id) {
|
||||
cache.get = function (id) {
|
||||
if (validId(id)) return vals[id];
|
||||
};
|
||||
|
||||
this.set = function (id, prom) {
|
||||
cache.set = function (id, prom) {
|
||||
if (validId(id)) vals[id] = prom;
|
||||
return prom;
|
||||
};
|
||||
|
||||
this.clear = this.delete = function (id) {
|
||||
cache.clear = cache.delete = function (id) {
|
||||
if (validId(id)) delete vals[id];
|
||||
};
|
||||
|
||||
this.clearAll = function () {
|
||||
cache.clearAll = function () {
|
||||
for (const id in vals) {
|
||||
if (vals.hasOwnProperty(id)) {
|
||||
delete vals[id];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
|
67
src/legacy/ui/public/index_patterns/errors.js
Normal file
67
src/legacy/ui/public/index_patterns/errors.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { KbnError } from 'ui/errors';
|
||||
|
||||
/**
|
||||
* when a mapping already exists for a field the user is attempting to add
|
||||
* @param {String} name - the field name
|
||||
*/
|
||||
export class IndexPatternAlreadyExists extends KbnError {
|
||||
constructor(name) {
|
||||
super(
|
||||
`An index pattern of "${name}" already exists`,
|
||||
IndexPatternAlreadyExists);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tried to call a method that relies on SearchSource having an indexPattern assigned
|
||||
*/
|
||||
export class IndexPatternMissingIndices extends KbnError {
|
||||
constructor(message) {
|
||||
const defaultMessage = 'IndexPattern\'s configured pattern does not match any indices';
|
||||
|
||||
super(
|
||||
(message && message.length) ? `No matching indices found: ${message}` : defaultMessage,
|
||||
IndexPatternMissingIndices);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tried to call a method that relies on SearchSource having an indexPattern assigned
|
||||
*/
|
||||
export class NoDefinedIndexPatterns extends KbnError {
|
||||
constructor() {
|
||||
super(
|
||||
'Define at least one index pattern to continue',
|
||||
NoDefinedIndexPatterns);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tried to load a route besides management/kibana/index but you don't have a default index pattern!
|
||||
*/
|
||||
export class NoDefaultIndexPattern extends KbnError {
|
||||
constructor() {
|
||||
super(
|
||||
'Please specify a default index pattern',
|
||||
NoDefaultIndexPattern);
|
||||
}
|
||||
}
|
|
@ -17,24 +17,25 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export function createFieldsFetcher(apiClient, config) {
|
||||
class FieldsFetcher {
|
||||
fetch(indexPattern) {
|
||||
return this.fetchForWildcard(indexPattern.getIndex(), {
|
||||
type: indexPattern.type,
|
||||
params: indexPattern.typeMeta && indexPattern.typeMeta.params,
|
||||
});
|
||||
}
|
||||
|
||||
fetchForWildcard(indexPatternId, options = {}) {
|
||||
return apiClient.getFieldsForWildcard({
|
||||
pattern: indexPatternId,
|
||||
metaFields: config.get('metaFields'),
|
||||
type: options.type,
|
||||
params: options.params || {},
|
||||
});
|
||||
}
|
||||
export class FieldsFetcher {
|
||||
constructor(apiClient, metaFields) {
|
||||
this.apiClient = apiClient;
|
||||
this.metaFields = metaFields;
|
||||
}
|
||||
fetch(indexPattern, options) {
|
||||
return this.fetchForWildcard(indexPattern.title, {
|
||||
...options,
|
||||
type: indexPattern.type,
|
||||
params: indexPattern.typeMeta && indexPattern.typeMeta.params,
|
||||
});
|
||||
}
|
||||
|
||||
return new FieldsFetcher();
|
||||
fetchForWildcard(indexPatternId, options = {}) {
|
||||
return this.apiClient.getFieldsForWildcard({
|
||||
pattern: indexPatternId,
|
||||
metaFields: this.metaFields,
|
||||
type: options.type,
|
||||
params: options.params || {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +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 { createFieldsFetcher } from './fields_fetcher';
|
||||
import { IndexPatternsApiClientProvider } from './index_patterns_api_client_provider';
|
||||
|
||||
export function FieldsFetcherProvider(Private, config) {
|
||||
const apiClient = Private(IndexPatternsApiClientProvider);
|
||||
return createFieldsFetcher(apiClient, config);
|
||||
}
|
|
@ -17,9 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import chrome from '../chrome';
|
||||
import { createIndexPatternsApiClient } from './index_patterns_api_client';
|
||||
|
||||
export function IndexPatternsApiClientProvider($http) {
|
||||
return createIndexPatternsApiClient($http, chrome.getBasePath());
|
||||
export function getRoutes() {
|
||||
return {
|
||||
edit: '/management/kibana/index_patterns/{{id}}',
|
||||
addField: '/management/kibana/index_patterns/{{id}}/create-field',
|
||||
indexedFields: '/management/kibana/index_patterns/{{id}}?_a=(tab:indexedFields)',
|
||||
scriptedFields: '/management/kibana/index_patterns/{{id}}?_a=(tab:scriptedFields)',
|
||||
sourceFilters: '/management/kibana/index_patterns/{{id}}?_a=(tab:sourceFilters)'
|
||||
};
|
||||
}
|
|
@ -19,19 +19,11 @@
|
|||
|
||||
export { IndexPatternSelect } from './components/index_pattern_select';
|
||||
|
||||
export { IndexPatternsProvider } from './index_patterns';
|
||||
|
||||
export {
|
||||
IndexPatternsApiClientProvider,
|
||||
} from './index_patterns_api_client_provider';
|
||||
export { IndexPatterns, IndexPatternsProvider } from './index_patterns';
|
||||
|
||||
export {
|
||||
INDEX_PATTERN_ILLEGAL_CHARACTERS,
|
||||
INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE,
|
||||
} from './constants';
|
||||
|
||||
export {
|
||||
ILLEGAL_CHARACTERS,
|
||||
CONTAINS_SPACES,
|
||||
validateIndexPattern,
|
||||
} from './validate';
|
||||
export { validateIndexPattern, CONTAINS_SPACES, ILLEGAL_CHARACTERS } from './validate';
|
||||
|
|
|
@ -17,58 +17,78 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexPatternMissingIndices } from '../errors';
|
||||
import { IndexPatternProvider } from './_index_pattern';
|
||||
import { IndexPatternsPatternCacheProvider } from './_pattern_cache';
|
||||
import { IndexPatternsGetProvider } from './_get';
|
||||
import { FieldsFetcherProvider } from './fields_fetcher_provider';
|
||||
import { fieldFormats } from '../registry/field_formats';
|
||||
import { uiModules } from '../modules';
|
||||
const module = uiModules.get('kibana/index_patterns');
|
||||
|
||||
export function IndexPatternsProvider(Private, config) {
|
||||
const self = this;
|
||||
import { IndexPatternMissingIndices } from './errors';
|
||||
import { IndexPattern } from './_index_pattern';
|
||||
import { createIndexPatternCache } from './_pattern_cache';
|
||||
import { indexPatternsGetProvider } from './_get';
|
||||
import { FieldsFetcher } from './fields_fetcher';
|
||||
import { IndexPatternsApiClient } from './index_patterns_api_client';
|
||||
|
||||
const IndexPattern = Private(IndexPatternProvider);
|
||||
const patternCache = Private(IndexPatternsPatternCacheProvider);
|
||||
const getProvider = Private(IndexPatternsGetProvider);
|
||||
export class IndexPatterns {
|
||||
constructor(config, savedObjectsClient) {
|
||||
const getProvider = indexPatternsGetProvider(savedObjectsClient);
|
||||
const apiClient = new IndexPatternsApiClient();
|
||||
|
||||
this.config = config;
|
||||
this.savedObjectsClient = savedObjectsClient;
|
||||
|
||||
this.errors = {
|
||||
MissingIndices: IndexPatternMissingIndices
|
||||
};
|
||||
|
||||
this.fieldsFetcher = new FieldsFetcher(apiClient, config.get('metaFields'));
|
||||
this.cache = createIndexPatternCache();
|
||||
this.getIds = getProvider('id');
|
||||
this.getTitles = getProvider('attributes.title');
|
||||
this.getFields = getProvider.multiple;
|
||||
this.fieldFormats = fieldFormats;
|
||||
}
|
||||
|
||||
|
||||
self.get = function (id) {
|
||||
if (!id) return self.make();
|
||||
get = (id) => {
|
||||
if (!id) return this.make();
|
||||
|
||||
const cache = patternCache.get(id);
|
||||
return cache || patternCache.set(id, self.make(id));
|
||||
const cache = this.cache.get(id);
|
||||
return cache || this.cache.set(id, this.make(id));
|
||||
};
|
||||
|
||||
self.getDefault = async () => {
|
||||
const defaultIndexPatternId = config.get('defaultIndex');
|
||||
getDefault = async () => {
|
||||
const defaultIndexPatternId = this.config.get('defaultIndex');
|
||||
if (defaultIndexPatternId) {
|
||||
return await self.get(defaultIndexPatternId);
|
||||
return await this.get(defaultIndexPatternId);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
self.make = function (id) {
|
||||
return (new IndexPattern(id)).init();
|
||||
make = (id) => {
|
||||
return (new IndexPattern(id,
|
||||
this.config,
|
||||
this.savedObjectsClient,
|
||||
this.cache,
|
||||
this.fieldsFetcher,
|
||||
this.getIds,
|
||||
)).init();
|
||||
};
|
||||
|
||||
self.delete = function (pattern) {
|
||||
self.getIds.clearCache();
|
||||
delete = (pattern) => {
|
||||
this.getIds.clearCache();
|
||||
return pattern.destroy();
|
||||
};
|
||||
|
||||
self.errors = {
|
||||
MissingIndices: IndexPatternMissingIndices
|
||||
};
|
||||
|
||||
self.cache = patternCache;
|
||||
self.getIds = getProvider('id');
|
||||
self.getTitles = getProvider('attributes.title');
|
||||
self.getFields = getProvider.multiple;
|
||||
self.fieldsFetcher = Private(FieldsFetcherProvider);
|
||||
self.fieldFormats = fieldFormats;
|
||||
}
|
||||
|
||||
module.service('indexPatterns', Private => Private(IndexPatternsProvider));
|
||||
// add angular service for backward compatibility
|
||||
import { uiModules } from '../modules';
|
||||
const module = uiModules.get('kibana/index_patterns');
|
||||
let _service;
|
||||
module.service('indexPatterns', function (chrome) {
|
||||
if (!_service) _service = new IndexPatterns(chrome.getUiSettingsClient(), chrome.getSavedObjectsClient());
|
||||
return _service;
|
||||
});
|
||||
|
||||
export const IndexPatternsProvider = (chrome) => {
|
||||
if (!_service) _service = new IndexPatterns(chrome.getUiSettingsClient(), chrome.getSavedObjectsClient());
|
||||
return _service;
|
||||
};
|
||||
|
|
|
@ -17,96 +17,85 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { resolve as resolveUrl, format as formatUrl } from 'url';
|
||||
import { kfetch } from '../kfetch';
|
||||
|
||||
import { pick, mapValues } from 'lodash';
|
||||
import { IndexPatternMissingIndices } from './errors';
|
||||
|
||||
import { IndexPatternMissingIndices } from '../errors';
|
||||
function join(...uriComponents) {
|
||||
return uriComponents.filter(Boolean).map(encodeURIComponent).join('/');
|
||||
}
|
||||
|
||||
export function createIndexPatternsApiClient($http, basePath) {
|
||||
const apiBaseUrl = `${basePath}/api/index_patterns/`;
|
||||
|
||||
function join(...uriComponents) {
|
||||
return uriComponents.filter(Boolean).map(encodeURIComponent).join('/');
|
||||
}
|
||||
|
||||
function getUrl(path, query) {
|
||||
const noNullsQuery = pick(query, value => value != null);
|
||||
const noArraysQuery = mapValues(noNullsQuery, value => (
|
||||
Array.isArray(value) ? JSON.stringify(value) : value
|
||||
));
|
||||
|
||||
return resolveUrl(apiBaseUrl, formatUrl({
|
||||
pathname: join(...path),
|
||||
query: noArraysQuery,
|
||||
}));
|
||||
}
|
||||
|
||||
function request(method, url, body) {
|
||||
return $http({
|
||||
method,
|
||||
url,
|
||||
data: body,
|
||||
})
|
||||
.then(resp => resp.data)
|
||||
.catch((resp) => {
|
||||
// convert $http errors into actual error objects
|
||||
const respBody = resp.data;
|
||||
|
||||
if (resp.status === 404 && respBody.code === 'no_matching_indices') {
|
||||
throw new IndexPatternMissingIndices(respBody.message);
|
||||
}
|
||||
|
||||
const err = new Error(respBody.message || respBody.error || `${resp.status} Response`);
|
||||
err.status = resp.status;
|
||||
err.body = respBody;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
class IndexPatternsApiClient {
|
||||
getFieldsForTimePattern(options = {}) {
|
||||
const {
|
||||
pattern,
|
||||
lookBack,
|
||||
metaFields,
|
||||
} = options;
|
||||
|
||||
const url = getUrl(['_fields_for_time_pattern'], {
|
||||
pattern,
|
||||
look_back: lookBack,
|
||||
meta_fields: metaFields,
|
||||
});
|
||||
|
||||
return request('GET', url).then(resp => resp.fields);
|
||||
}
|
||||
|
||||
getFieldsForWildcard(options = {}) {
|
||||
const {
|
||||
pattern,
|
||||
metaFields,
|
||||
type,
|
||||
params,
|
||||
} = options;
|
||||
|
||||
let url;
|
||||
|
||||
if(type) {
|
||||
url = getUrl([type, '_fields_for_wildcard'], {
|
||||
pattern,
|
||||
meta_fields: metaFields,
|
||||
params: JSON.stringify(params),
|
||||
});
|
||||
} else {
|
||||
url = getUrl(['_fields_for_wildcard'], {
|
||||
pattern,
|
||||
meta_fields: metaFields,
|
||||
});
|
||||
function request(method, url, query, body) {
|
||||
return kfetch({
|
||||
method,
|
||||
pathname: url,
|
||||
query,
|
||||
body,
|
||||
})
|
||||
.catch((resp) => {
|
||||
if (resp.body.statusCode === 404 && resp.body.statuscode === 'no_matching_indices') {
|
||||
throw new IndexPatternMissingIndices(resp.body.message);
|
||||
}
|
||||
|
||||
return request('GET', url).then(resp => resp.fields);
|
||||
}
|
||||
const err = new Error(resp.body.message || resp.body.error || `${resp.body.statusCode} Response`);
|
||||
err.status = resp.body.statusCode;
|
||||
err.body = resp.body.message;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
export class IndexPatternsApiClient {
|
||||
constructor() {
|
||||
this.apiBaseUrl = `/api/index_patterns/`;
|
||||
}
|
||||
|
||||
return new IndexPatternsApiClient();
|
||||
_getUrl(path) {
|
||||
return this.apiBaseUrl + join(path);
|
||||
}
|
||||
|
||||
|
||||
getFieldsForTimePattern(options = {}) {
|
||||
const {
|
||||
pattern,
|
||||
lookBack,
|
||||
metaFields,
|
||||
} = options;
|
||||
|
||||
const url = this._getUrl(['_fields_for_time_pattern']);
|
||||
|
||||
return request('GET', url, {
|
||||
pattern,
|
||||
look_back: lookBack,
|
||||
meta_fields: metaFields,
|
||||
}).then(resp => resp.fields);
|
||||
}
|
||||
|
||||
getFieldsForWildcard(options = {}) {
|
||||
const {
|
||||
pattern,
|
||||
metaFields,
|
||||
type,
|
||||
params,
|
||||
} = options;
|
||||
|
||||
let url;
|
||||
let query;
|
||||
|
||||
if(type) {
|
||||
url = this._getUrl([type, '_fields_for_wildcard']);
|
||||
query = {
|
||||
pattern,
|
||||
meta_fields: metaFields,
|
||||
params: JSON.stringify(params),
|
||||
};
|
||||
} else {
|
||||
url = this._getUrl(['_fields_for_wildcard']);
|
||||
query = {
|
||||
pattern,
|
||||
meta_fields: metaFields,
|
||||
};
|
||||
}
|
||||
|
||||
return request('GET', url, query).then(resp => resp.fields);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,11 @@ import {
|
|||
} from './validate_index';
|
||||
|
||||
jest.mock('ui/index_patterns/index_patterns.js', () => ({
|
||||
IndexPatternsProvider: jest.fn(),
|
||||
IndexPatterns: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('ui/index_patterns/index_patterns_api_client_provider.js', () => ({
|
||||
IndexPatternsApiClientProvider: jest.fn(),
|
||||
jest.mock('ui/index_patterns/index_patterns_api_client.js', () => ({
|
||||
IndexPatternsApiClient: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Index name validation', () => {
|
||||
|
|
|
@ -23,16 +23,18 @@ import sinon from 'sinon';
|
|||
import BluebirdPromise from 'bluebird';
|
||||
|
||||
import { SavedObjectProvider } from '../saved_object';
|
||||
import { IndexPatternProvider } from '../../index_patterns/_index_pattern';
|
||||
import { IndexPattern } 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 configMock = {
|
||||
get: cfg => cfg
|
||||
};
|
||||
|
||||
describe('Saved Object', function () {
|
||||
require('test_utils/no_digest_promises').activateForSuite();
|
||||
|
||||
let SavedObject;
|
||||
let IndexPattern;
|
||||
let esDataStub;
|
||||
let savedObjectsClientStub;
|
||||
let window;
|
||||
|
@ -87,7 +89,6 @@ describe('Saved Object', function () {
|
|||
|
||||
beforeEach(ngMock.module(
|
||||
'kibana',
|
||||
StubIndexPatternsApiClientModule,
|
||||
// Use the native window.confirm instead of our specialized version to make testing
|
||||
// this easier.
|
||||
function ($provide) {
|
||||
|
@ -98,7 +99,6 @@ describe('Saved Object', function () {
|
|||
|
||||
beforeEach(ngMock.inject(function (es, Private, $window) {
|
||||
SavedObject = Private(SavedObjectProvider);
|
||||
IndexPattern = Private(IndexPatternProvider);
|
||||
esDataStub = es;
|
||||
savedObjectsClientStub = Private(SavedObjectsClientProvider);
|
||||
window = $window;
|
||||
|
@ -339,7 +339,9 @@ describe('Saved Object', function () {
|
|||
type: 'dashboard',
|
||||
});
|
||||
});
|
||||
savedObject.searchSource.setField('index', new IndexPattern('my-index', null, []));
|
||||
const indexPattern = new IndexPattern('my-index', configMock, null, []);
|
||||
indexPattern.title = indexPattern.id;
|
||||
savedObject.searchSource.setField('index', indexPattern);
|
||||
return savedObject
|
||||
.save()
|
||||
.then(() => {
|
||||
|
@ -690,6 +692,12 @@ describe('Saved Object', function () {
|
|||
});
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
sinon.stub(savedObject, 'hydrateIndexPattern').callsFake(() => {
|
||||
const indexPattern = new IndexPattern(indexPatternId, configMock, null, []);
|
||||
indexPattern.title = indexPattern.id;
|
||||
savedObject.searchSource.setField('index', indexPattern);
|
||||
return Promise.resolve(indexPattern);
|
||||
});
|
||||
expect(!!savedObject.searchSource.getField('index')).to.be(false);
|
||||
|
||||
return savedObject.init().then(() => {
|
||||
|
|
|
@ -32,7 +32,7 @@ import angular from 'angular';
|
|||
import _ from 'lodash';
|
||||
|
||||
import { InvalidJSONProperty, SavedObjectNotFound } from '../errors';
|
||||
import MappingSetupProvider from '../utils/mapping_setup';
|
||||
import { expandShorthand } from '../utils/mapping_setup';
|
||||
|
||||
import { SearchSourceProvider } from '../courier/search_source';
|
||||
import { findObjectByTitle } from './find_object_by_title';
|
||||
|
@ -69,7 +69,6 @@ function isErrorNonFatal(error) {
|
|||
export function SavedObjectProvider(Promise, Private, confirmModalPromise, indexPatterns) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
const SearchSource = Private(SearchSourceProvider);
|
||||
const mappingSetup = Private(MappingSetupProvider);
|
||||
|
||||
/**
|
||||
* The SavedObject class is a base class for saved objects loaded from the server and
|
||||
|
@ -109,7 +108,7 @@ export function SavedObjectProvider(Promise, Private, confirmModalPromise, index
|
|||
this.defaults = config.defaults || {};
|
||||
|
||||
// mapping definition for the fields that this object will expose
|
||||
const mapping = mappingSetup.expandShorthand(config.mapping);
|
||||
const mapping = expandShorthand(config.mapping);
|
||||
|
||||
const afterESResp = config.afterESResp || _.noop;
|
||||
const customInit = config.init || _.noop;
|
||||
|
|
|
@ -20,40 +20,35 @@
|
|||
import _ from 'lodash';
|
||||
import ngMock from 'ng_mock';
|
||||
import expect from '@kbn/expect';
|
||||
import UtilsMappingSetupProvider from '../mapping_setup';
|
||||
|
||||
let mappingSetup;
|
||||
import { expandShorthand } from '../mapping_setup';
|
||||
|
||||
describe('ui/utils/mapping_setup', function () {
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
mappingSetup = Private(UtilsMappingSetupProvider);
|
||||
}));
|
||||
|
||||
describe('#expandShorthand()', function () {
|
||||
it('allows shortcuts for field types by just setting the value to the type name', function () {
|
||||
const mapping = mappingSetup.expandShorthand({ foo: 'boolean' });
|
||||
const mapping = expandShorthand({ foo: 'boolean' });
|
||||
expect(mapping.foo.type).to.be('boolean');
|
||||
});
|
||||
|
||||
it('can set type as an option', function () {
|
||||
const mapping = mappingSetup.expandShorthand({ foo: { type: 'integer' } });
|
||||
const mapping = expandShorthand({ foo: { type: 'integer' } });
|
||||
expect(mapping.foo.type).to.be('integer');
|
||||
});
|
||||
|
||||
describe('when type is json', function () {
|
||||
it('returned object is type text', function () {
|
||||
const mapping = mappingSetup.expandShorthand({ foo: 'json' });
|
||||
const mapping = expandShorthand({ foo: 'json' });
|
||||
expect(mapping.foo.type).to.be('text');
|
||||
});
|
||||
|
||||
it('returned object has _serialize function', function () {
|
||||
const mapping = mappingSetup.expandShorthand({ foo: 'json' });
|
||||
const mapping = expandShorthand({ foo: 'json' });
|
||||
expect(_.isFunction(mapping.foo._serialize)).to.be(true);
|
||||
});
|
||||
|
||||
it('returned object has _deserialize function', function () {
|
||||
const mapping = mappingSetup.expandShorthand({ foo: 'json' });
|
||||
const mapping = expandShorthand({ foo: 'json' });
|
||||
expect(_.isFunction(mapping.foo._serialize)).to.be(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,35 +17,29 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import { mapValues } from 'lodash';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function MappingSetupService() {
|
||||
const mappingSetup = this;
|
||||
const json = {
|
||||
_serialize: function (val) {
|
||||
if (val != null) return JSON.stringify(val);
|
||||
},
|
||||
_deserialize: function (val) {
|
||||
if (val != null) return JSON.parse(val);
|
||||
}
|
||||
};
|
||||
|
||||
const json = {
|
||||
_serialize: function (val) {
|
||||
if (val != null) return angular.toJson(val);
|
||||
},
|
||||
_deserialize: function (val) {
|
||||
if (val != null) return JSON.parse(val);
|
||||
export const expandShorthand = function (sh) {
|
||||
return mapValues(sh || {}, function (val) {
|
||||
// allow shortcuts for the field types, by just setting the value
|
||||
// to the type name
|
||||
if (typeof val === 'string') val = { type: val };
|
||||
|
||||
if (val.type === 'json') {
|
||||
val.type = 'text';
|
||||
val._serialize = json._serialize;
|
||||
val._deserialize = json._deserialize;
|
||||
}
|
||||
};
|
||||
|
||||
mappingSetup.expandShorthand = function (sh) {
|
||||
return _.mapValues(sh || {}, function (val) {
|
||||
// allow shortcuts for the field types, by just setting the value
|
||||
// to the type name
|
||||
if (typeof val === 'string') val = { type: val };
|
||||
|
||||
if (val.type === 'json') {
|
||||
val.type = 'text';
|
||||
val._serialize = json._serialize;
|
||||
val._deserialize = json._deserialize;
|
||||
}
|
||||
|
||||
return val;
|
||||
});
|
||||
};
|
||||
}
|
||||
return val;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -18,17 +18,15 @@
|
|||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { IndexPatternProvider, getRoutes } from 'ui/index_patterns/_index_pattern';
|
||||
import { formatHit } from 'ui/index_patterns/_format_hit';
|
||||
import { IndexPattern } from 'ui/index_patterns/_index_pattern';
|
||||
import { getRoutes } from 'ui/index_patterns/get_routes';
|
||||
import { formatHitProvider } from 'ui/index_patterns/_format_hit';
|
||||
import { getComputedFields } from 'ui/index_patterns/_get_computed_fields';
|
||||
import { fieldFormats } from 'ui/registry/field_formats';
|
||||
import { IndexPatternsFlattenHitProvider } from 'ui/index_patterns/_flatten_hit';
|
||||
import { flattenHitWrapper } from 'ui/index_patterns/_flatten_hit';
|
||||
import { FieldList } from 'ui/index_patterns/_field_list';
|
||||
|
||||
export default function (Private) {
|
||||
|
||||
const flattenHit = Private(IndexPatternsFlattenHitProvider);
|
||||
const IndexPattern = Private(IndexPatternProvider);
|
||||
export default function () {
|
||||
|
||||
function StubIndexPattern(pattern, timeField, fields) {
|
||||
this.id = pattern;
|
||||
|
@ -45,8 +43,9 @@ export default function (Private) {
|
|||
|
||||
this.getIndex = () => pattern;
|
||||
this.getComputedFields = getComputedFields.bind(this);
|
||||
this.flattenHit = flattenHit(this);
|
||||
this.formatHit = formatHit(this, fieldFormats.getDefaultInstance('string'));
|
||||
this.flattenHit = flattenHitWrapper(this, this.metaFields);
|
||||
this.formatHit = formatHitProvider(this, fieldFormats.getDefaultInstance('string'));
|
||||
this.fieldsFetcher = { apiClient: { baseUrl: '' } };
|
||||
this.formatField = this.formatHit.formatField;
|
||||
|
||||
this._reindexFields = function () {
|
||||
|
|
|
@ -12,11 +12,11 @@ jest.mock('../services/auto_follow_pattern_validators', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('ui/index_patterns/index_patterns.js', () => ({
|
||||
IndexPatternsProvider: jest.fn(),
|
||||
IndexPatterns: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('ui/index_patterns/index_patterns_api_client_provider.js', () => ({
|
||||
IndexPatternsApiClientProvider: jest.fn(),
|
||||
jest.mock('ui/index_patterns/index_patterns_api_client.js', () => ({
|
||||
IndexPatternsApiClient: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('<AutoFollowPatternForm state update', () => {
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
import { validateAutoFollowPattern } from './auto_follow_pattern_validators';
|
||||
|
||||
jest.mock('ui/index_patterns/index_patterns.js', () => ({
|
||||
IndexPatternsProvider: jest.fn(),
|
||||
IndexPatterns: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('ui/index_patterns/index_patterns_api_client_provider.js', () => ({
|
||||
IndexPatternsApiClientProvider: jest.fn(),
|
||||
jest.mock('ui/index_patterns/index_patterns_api_client.js', () => ({
|
||||
IndexPatternsApiClient: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Auto-follow pattern validators', () => {
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
convertMapExtentToPolygon,
|
||||
} from './elasticsearch_geo_utils';
|
||||
|
||||
import { IndexPatternsFlattenHitProvider } from 'ui/index_patterns/_flatten_hit';
|
||||
import { flattenHitWrapper } from 'ui/index_patterns/_flatten_hit';
|
||||
|
||||
const geoFieldName = 'location';
|
||||
const mapExtent = {
|
||||
|
@ -132,16 +132,6 @@ describe('hitsToGeoJson', () => {
|
|||
});
|
||||
|
||||
describe('dot in geoFieldName', () => {
|
||||
const configMock = {
|
||||
get: (key) => {
|
||||
if (key === 'metaFields') {
|
||||
return [];
|
||||
}
|
||||
throw new Error(`Unexpected config key: ${key}`);
|
||||
},
|
||||
watch: () => {}
|
||||
};
|
||||
const flattenHitWrapper = IndexPatternsFlattenHitProvider(configMock); // eslint-disable-line new-cap
|
||||
const indexPatternMock = {
|
||||
fields: {
|
||||
byName: {
|
||||
|
|
|
@ -467,7 +467,6 @@
|
|||
"common.ui.flotCharts.thuLabel": "木",
|
||||
"common.ui.flotCharts.tueLabel": "火",
|
||||
"common.ui.flotCharts.wedLabel": "水",
|
||||
"common.ui.indexPattern.bannerLabel": "Kibana でデータの可視化と閲覧を行うには、Elasticsearch からデータを取得するためのインデックスパターンの作成が必要です。",
|
||||
"common.ui.indexPattern.editIndexPattern": "インデックスパターンを編集",
|
||||
"common.ui.indexPattern.unableWriteLabel": "インデックスパターンを書き込めません!このインデックスパターンへの最新の変更を取得するにな、ページを更新してください。",
|
||||
"common.ui.indexPattern.unknownFieldErrorMessage": "インデックスパターン「{title}」のフィールド「{name}」が不明なフィールドタイプを使用しています。",
|
||||
|
|
|
@ -466,7 +466,6 @@
|
|||
"common.ui.flotCharts.thuLabel": "周四",
|
||||
"common.ui.flotCharts.tueLabel": "周二",
|
||||
"common.ui.flotCharts.wedLabel": "周三",
|
||||
"common.ui.indexPattern.bannerLabel": "若要在 Kibana 中可视化和浏览数据,您需要创建索引模式,以从 Elasticsearch 检索数据。",
|
||||
"common.ui.indexPattern.editIndexPattern": "编辑索引模式",
|
||||
"common.ui.indexPattern.unableWriteLabel": "无法写入索引模式!请刷新页面以获取此索引模式的最新更改。",
|
||||
"common.ui.indexPattern.unknownFieldErrorMessage": "indexPattern “{title}” 中的字段 “{name}” 使用未知字段类型。",
|
||||
|
@ -9980,4 +9979,4 @@
|
|||
"xpack.watcher.watchActionsTitle": "满足后将执行 {watchActionsCount, plural, one{# 个操作} other {# 个操作}}",
|
||||
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue