mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Index pattern is created, by default, with a random ID by Elasticsearch * Updated all references requiring the pattern itself to use indexPattern.title * Advanced options toggle added to index pattern creation page to provide a specified ID * If an index pattern does not exist, the user is given a link to create a pattern with the referenced ID.
This commit is contained in:
parent
f0b6e7d85f
commit
3c64283dd1
46 changed files with 445 additions and 364 deletions
|
@ -73,7 +73,7 @@
|
|||
|
||||
<div ng-if="error" class="load-error">
|
||||
<span aria-hidden="true" class="kuiIcon fa-exclamation-triangle"></span>
|
||||
<span ng-bind="error"></span>
|
||||
<span ng-bind-html="error | markdown"></span>
|
||||
</div>
|
||||
|
||||
<visualize
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'ui/private';
|
|||
import 'plugins/kibana/discover/components/field_chooser/field_chooser';
|
||||
import FixturesHitsProvider from 'fixtures/hits';
|
||||
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import { SavedObject } from 'ui/saved_objects';
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
|
||||
|
@ -70,7 +71,11 @@ describe('discover field chooser directives', function () {
|
|||
beforeEach(ngMock.inject(function (Private) {
|
||||
hits = Private(FixturesHitsProvider);
|
||||
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
|
||||
indexPatternList = [ 'b', 'a', 'c' ];
|
||||
indexPatternList = [
|
||||
new SavedObject(undefined, { id: '0', attributes: { title: 'b' } }),
|
||||
new SavedObject(undefined, { id: '1', attributes: { title: 'a' } }),
|
||||
new SavedObject(undefined, { id: '2', attributes: { title: 'c' } })
|
||||
];
|
||||
|
||||
const fieldCounts = _.transform(hits, function (counts, hit) {
|
||||
_.keys(indexPattern.flattenHit(hit)).forEach(function (key) {
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
class="index-pattern-selection"
|
||||
ng-model="selectedIndexPattern"
|
||||
on-select="setIndexPattern($item)"
|
||||
ng-init="selectedIndexPattern = indexPattern.id"
|
||||
ng-init="selectedIndexPattern = indexPattern"
|
||||
>
|
||||
<ui-select-match>
|
||||
{{$select.selected}}
|
||||
{{$select.selected.title}}
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="id in indexPatternList | filter:$select.search | orderBy">
|
||||
<div ng-bind-html="id | highlight: $select.search"></div>
|
||||
<ui-select-choices repeat="pattern in indexPatternList | filter:$select.search">
|
||||
<div ng-bind-html="pattern.get('title') | highlight: $select.search"></div>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
</div>
|
||||
|
@ -20,8 +20,9 @@
|
|||
class="index-pattern-label"
|
||||
id="index_pattern_id"
|
||||
tabindex="0"
|
||||
css-truncate
|
||||
>{{ indexPattern.id }}</h2>
|
||||
css-truncate>
|
||||
|
||||
{{ indexPattern.title }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@ import { uiModules } from 'ui/modules';
|
|||
import fieldChooserTemplate from 'plugins/kibana/discover/components/field_chooser/field_chooser.html';
|
||||
const app = uiModules.get('apps/discover');
|
||||
|
||||
|
||||
|
||||
app.directive('discFieldChooser', function ($location, globalState, config, $route, Private) {
|
||||
const FieldList = Private(IndexPatternsFieldListProvider);
|
||||
|
||||
|
@ -32,8 +30,9 @@ app.directive('discFieldChooser', function ($location, globalState, config, $rou
|
|||
},
|
||||
template: fieldChooserTemplate,
|
||||
link: function ($scope) {
|
||||
$scope.setIndexPattern = function (id) {
|
||||
$scope.state.index = id;
|
||||
$scope.indexPatternList = _.sortBy($scope.indexPatternList, o => o.get('title'));
|
||||
$scope.setIndexPattern = function (pattern) {
|
||||
$scope.state.index = pattern.id;
|
||||
$scope.state.save();
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import { uiModules } from 'ui/modules';
|
|||
import indexTemplate from 'plugins/kibana/discover/index.html';
|
||||
import { StateProvider } from 'ui/state_management/state';
|
||||
import { documentationLinks } from 'ui/documentation_links/documentation_links';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
const app = uiModules.get('apps/discover', [
|
||||
'kibana/notify',
|
||||
|
@ -45,8 +46,14 @@ uiRoutes
|
|||
resolve: {
|
||||
ip: function (Promise, courier, config, $location, Private) {
|
||||
const State = Private(StateProvider);
|
||||
return courier.indexPatterns.getIds()
|
||||
.then(function (list) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
return savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
perPage: 10000
|
||||
})
|
||||
.then(({ savedObjects }) => {
|
||||
/**
|
||||
* In making the indexPattern modifiable it was placed in appState. Unfortunately,
|
||||
* the load order of AppState conflicts with the load order of many other things
|
||||
|
@ -59,12 +66,12 @@ uiRoutes
|
|||
const state = new State('_a', {});
|
||||
|
||||
const specified = !!state.index;
|
||||
const exists = _.contains(list, state.index);
|
||||
const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1;
|
||||
const id = exists ? state.index : config.get('defaultIndex');
|
||||
state.destroy();
|
||||
|
||||
return Promise.props({
|
||||
list: list,
|
||||
list: savedObjects,
|
||||
loaded: courier.indexPatterns.get(id),
|
||||
stateVal: state.index,
|
||||
stateValFound: specified && exists
|
||||
|
@ -156,6 +163,7 @@ function discoverController($scope, config, courier, $route, $window, Notifier,
|
|||
dirty: !savedSearch.id
|
||||
};
|
||||
const $state = $scope.state = new AppState(getStateDefaults());
|
||||
|
||||
$scope.uiState = $state.makeStateful('uiState');
|
||||
|
||||
function getStateDefaults() {
|
||||
|
@ -179,8 +187,6 @@ function discoverController($scope, config, courier, $route, $window, Notifier,
|
|||
$scope.opts = {
|
||||
// number of records to fetch, then paginate through
|
||||
sampleSize: config.get('discover:sampleSize'),
|
||||
// Index to match
|
||||
index: $scope.indexPattern.id,
|
||||
timefield: $scope.indexPattern.timeFieldName,
|
||||
savedSearch: savedSearch,
|
||||
indexPatternList: $route.current.locals.ip.list,
|
||||
|
@ -586,7 +592,7 @@ function discoverController($scope, config, courier, $route, $window, Notifier,
|
|||
|
||||
if (own && !stateVal) return own;
|
||||
if (stateVal && !stateValFound) {
|
||||
const err = '"' + stateVal + '" is not a configured pattern. ';
|
||||
const err = '"' + stateVal + '" is not a configured pattern ID. ';
|
||||
if (own) {
|
||||
notify.warning(err + ' Using the saved index pattern: "' + own.id + '"');
|
||||
return own;
|
||||
|
|
|
@ -19,7 +19,7 @@ describe('createIndexPattern UI', () => {
|
|||
current: {
|
||||
params: {},
|
||||
locals: {
|
||||
indexPatternIds: []
|
||||
indexPatterns: []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,12 +23,20 @@
|
|||
class="kuiVerticalRhythm"
|
||||
ng-submit="controller.createIndexPattern()"
|
||||
>
|
||||
|
||||
<!-- Index pattern input -->
|
||||
<div class="kuiVerticalRhythm">
|
||||
<label
|
||||
class="kuiLabel kuiVerticalRhythmSmall"
|
||||
translate="KIBANA-INDEX_NAME_OR_PATTERN"
|
||||
></label>
|
||||
<label class="kuiLabel kuiVerticalRhythmSmall">
|
||||
<span translate="KIBANA-INDEX_PATTERN"></span>
|
||||
|
||||
<small>
|
||||
<a
|
||||
class="kuiLink"
|
||||
ng-click="controller.toggleAdvancedIndexOptions();"
|
||||
translate="KIBANA-ADVANCED_OPTIONS"
|
||||
></a>
|
||||
</small>
|
||||
</label>
|
||||
|
||||
<div class="kuiVerticalRhythm kuiVerticalRhythmSmall">
|
||||
<input
|
||||
|
@ -95,6 +103,35 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Index pattern id input -->
|
||||
<div class="kuiVerticalRhythm" ng-if="controller.showAdvancedOptions">
|
||||
<label
|
||||
class="kuiLabel kuiVerticalRhythmSmall"
|
||||
translate="KIBANA-INDEX_PATTERN_ID">
|
||||
</label>
|
||||
|
||||
<div class="kuiVerticalRhythm kuiVerticalRhythmSmall">
|
||||
<input
|
||||
class="kuiTextInput kuiTextInput--large"
|
||||
data-test-subj="createIndexPatternIdInput"
|
||||
ng-model="controller.formValues.id"
|
||||
ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"
|
||||
validate-index-name
|
||||
allow-wildcard
|
||||
name="id"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- ID help text -->
|
||||
<div class="kuiVerticalRhythm">
|
||||
<p
|
||||
class="kuiSubText kuiVerticalRhythmSmall"
|
||||
translate="KIBANA-INDEX_PATTERN_SPECIFY_ID"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Time field select -->
|
||||
<div class="kuiVerticalRhythm">
|
||||
<label class="kuiLabel kuiVerticalRhythmSmall">
|
||||
|
|
|
@ -2,7 +2,6 @@ import _ from 'lodash';
|
|||
import { IndexPatternMissingIndices } from 'ui/errors';
|
||||
import 'ui/directives/validate_index_name';
|
||||
import 'ui/directives/auto_select_if_only_one';
|
||||
import { RefreshKibanaIndex } from '../refresh_kibana_index';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import template from './create_index_pattern.html';
|
||||
|
@ -16,14 +15,25 @@ uiRoutes
|
|||
});
|
||||
|
||||
uiModules.get('apps/management')
|
||||
.controller('managementIndicesCreate', function ($scope, kbnUrl, Private, Notifier, indexPatterns, es, config, Promise, $translate) {
|
||||
.controller('managementIndicesCreate', function (
|
||||
$scope,
|
||||
$routeParams,
|
||||
kbnUrl,
|
||||
Private,
|
||||
Notifier,
|
||||
indexPatterns,
|
||||
es,
|
||||
config,
|
||||
Promise,
|
||||
$translate
|
||||
) {
|
||||
const notify = new Notifier();
|
||||
const refreshKibanaIndex = Private(RefreshKibanaIndex);
|
||||
const intervals = indexPatterns.intervals;
|
||||
let loadingCount = 0;
|
||||
|
||||
// Configure the new index pattern we're going to create.
|
||||
this.formValues = {
|
||||
id: $routeParams.id ? decodeURIComponent($routeParams.id) : undefined,
|
||||
name: config.get('indexPattern:placeholder'),
|
||||
nameIsPattern: false,
|
||||
expandWildcard: false,
|
||||
|
@ -39,6 +49,7 @@ uiModules.get('apps/management')
|
|||
this.existing = null;
|
||||
this.nameIntervalOptions = intervals;
|
||||
this.patternErrors = [];
|
||||
this.showAdvancedOptions = $routeParams.id || false;
|
||||
|
||||
const getTimeFieldOptions = () => {
|
||||
loadingCount += 1;
|
||||
|
@ -260,16 +271,19 @@ uiModules.get('apps/management')
|
|||
});
|
||||
};
|
||||
|
||||
this.toggleAdvancedIndexOptions = () => {
|
||||
this.showAdvancedOptions = !!!this.showAdvancedOptions;
|
||||
};
|
||||
|
||||
this.createIndexPattern = () => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
timeFieldOption,
|
||||
nameIsPattern,
|
||||
nameInterval,
|
||||
} = this.formValues;
|
||||
|
||||
const id = name;
|
||||
|
||||
const timeFieldName = timeFieldOption
|
||||
? timeFieldOption.fieldName
|
||||
: undefined;
|
||||
|
@ -286,6 +300,7 @@ uiModules.get('apps/management')
|
|||
loadingCount += 1;
|
||||
sendCreateIndexPatternRequest(indexPatterns, {
|
||||
id,
|
||||
name,
|
||||
timeFieldName,
|
||||
intervalName,
|
||||
notExpandable,
|
||||
|
@ -294,17 +309,15 @@ uiModules.get('apps/management')
|
|||
return;
|
||||
}
|
||||
|
||||
refreshKibanaIndex().then(() => {
|
||||
if (!config.get('defaultIndex')) {
|
||||
config.set('defaultIndex', id);
|
||||
}
|
||||
if (!config.get('defaultIndex')) {
|
||||
config.set('defaultIndex', createdId);
|
||||
}
|
||||
|
||||
indexPatterns.cache.clear(id);
|
||||
kbnUrl.change(`/management/kibana/indices/${id}`);
|
||||
indexPatterns.cache.clear(createdId);
|
||||
kbnUrl.change(`/management/kibana/indices/${createdId}`);
|
||||
|
||||
// force loading while kbnUrl.change takes effect
|
||||
loadingCount = Infinity;
|
||||
});
|
||||
// force loading while kbnUrl.change takes effect
|
||||
loadingCount = Infinity;
|
||||
}).catch(err => {
|
||||
if (err instanceof IndexPatternMissingIndices) {
|
||||
return notify.error($translate.instant('KIBANA-NO_INDICES_MATCHING_PATTERN'));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export function sendCreateIndexPatternRequest(indexPatterns, {
|
||||
id,
|
||||
name,
|
||||
timeFieldName,
|
||||
intervalName,
|
||||
notExpandable,
|
||||
|
@ -7,10 +8,9 @@ export function sendCreateIndexPatternRequest(indexPatterns, {
|
|||
// get an empty indexPattern to start
|
||||
return indexPatterns.get()
|
||||
.then(indexPattern => {
|
||||
// set both the id and title to the same value
|
||||
indexPattern.id = indexPattern.title = id;
|
||||
|
||||
Object.assign(indexPattern, {
|
||||
id,
|
||||
title: name,
|
||||
timeFieldName,
|
||||
intervalName,
|
||||
notExpandable,
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</p>
|
||||
|
||||
<p class="kuiText kuiVerticalRhythm">
|
||||
This page lists every field in the <strong>{{::indexPattern.id}}</strong>
|
||||
This page lists every field in the <strong>{{::indexPattern.title}}</strong>
|
||||
index and the field's associated core type as recorded by Elasticsearch.
|
||||
While this list allows you to view the core type of each field, changing
|
||||
field types must be done using Elasticsearch's
|
||||
|
|
|
@ -4,7 +4,6 @@ import './indexed_fields_table';
|
|||
import './scripted_fields_table';
|
||||
import './scripted_field_editor';
|
||||
import './source_filters_table';
|
||||
import { RefreshKibanaIndex } from '../refresh_kibana_index';
|
||||
import UrlProvider from 'ui/url';
|
||||
import { IndicesEditSectionsProvider } from './edit_sections';
|
||||
import uiRoutes from 'ui/routes';
|
||||
|
@ -44,12 +43,14 @@ uiModules.get('apps/management')
|
|||
$scope, $location, $route, config, courier, Notifier, Private, AppState, docTitle, confirmModal) {
|
||||
const notify = new Notifier();
|
||||
const $state = $scope.state = new AppState();
|
||||
const refreshKibanaIndex = Private(RefreshKibanaIndex);
|
||||
|
||||
$scope.kbnUrl = Private(UrlProvider);
|
||||
$scope.indexPattern = $route.current.locals.indexPattern;
|
||||
docTitle.change($scope.indexPattern.id);
|
||||
const otherIds = _.without($route.current.locals.indexPatternIds, $scope.indexPattern.id);
|
||||
|
||||
const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => {
|
||||
return pattern.id !== $scope.indexPattern.id;
|
||||
});
|
||||
|
||||
$scope.$watch('indexPattern.fields', function () {
|
||||
$scope.editSections = Private(IndicesEditSectionsProvider)($scope.indexPattern);
|
||||
|
@ -104,13 +105,13 @@ uiModules.get('apps/management')
|
|||
function doRemove() {
|
||||
if ($scope.indexPattern.id === config.get('defaultIndex')) {
|
||||
config.remove('defaultIndex');
|
||||
if (otherIds.length) {
|
||||
config.set('defaultIndex', otherIds[0]);
|
||||
|
||||
if (otherPatterns.length) {
|
||||
config.set('defaultIndex', otherPatterns[0].id);
|
||||
}
|
||||
}
|
||||
|
||||
courier.indexPatterns.delete($scope.indexPattern)
|
||||
.then(refreshKibanaIndex)
|
||||
.then(function () {
|
||||
$location.url('/management/kibana/index');
|
||||
})
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
ng-if="defaultIndex === indexPattern.id"
|
||||
class="kuiIcon fa-star"
|
||||
></span>
|
||||
{{indexPattern.id}}
|
||||
{{indexPattern.title}}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -25,13 +25,13 @@
|
|||
</li>
|
||||
|
||||
<li
|
||||
ng-repeat="pattern in indexPatternList | orderBy:['-default','id'] track by pattern.id "
|
||||
ng-repeat="pattern in indexPatternList | orderBy:['-default','title'] track by pattern.id"
|
||||
class="sidebar-item"
|
||||
>
|
||||
<a href="{{::pattern.url}}">
|
||||
<div class="{{::pattern.class}}">
|
||||
<i aria-hidden="true" ng-if="pattern.default" class="fa fa-star"></i>
|
||||
<span ng-bind="::pattern.id"></span>
|
||||
<span ng-bind="::pattern.title"></span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -4,10 +4,17 @@ import './edit_index_pattern';
|
|||
import uiRoutes from 'ui/routes';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import indexTemplate from 'plugins/kibana/management/sections/indices/index.html';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
const indexPatternsResolutions = {
|
||||
indexPatternIds: function (courier) {
|
||||
return courier.indexPatterns.getIds();
|
||||
indexPatterns: function (Private) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
return savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
perPage: 10000
|
||||
}).then(response => response.savedObjects);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -34,10 +41,12 @@ uiModules.get('apps/management')
|
|||
config.bindToScope($scope, 'defaultIndex');
|
||||
|
||||
$scope.$watch('defaultIndex', function () {
|
||||
const ids = $route.current.locals.indexPatternIds;
|
||||
$scope.indexPatternList = ids.map(function (id) {
|
||||
$scope.indexPatternList = $route.current.locals.indexPatterns.map(pattern => {
|
||||
const id = pattern.id;
|
||||
|
||||
return {
|
||||
id: id,
|
||||
title: pattern.get('title'),
|
||||
url: kbnUrl.eval('#/management/kibana/indices/{{id}}', { id: id }),
|
||||
class: 'sidebar-item-title ' + ($scope.editingId === id ? 'active' : ''),
|
||||
default: $scope.defaultIndex === id
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export function RefreshKibanaIndex(esAdmin, kbnIndex) {
|
||||
return function () {
|
||||
return esAdmin.indices.refresh({
|
||||
index: kbnIndex
|
||||
});
|
||||
};
|
||||
}
|
|
@ -8,11 +8,11 @@
|
|||
|
||||
<div
|
||||
css-truncate
|
||||
aria-label="{{:: 'Index pattern: ' + indexPattern.id}}"
|
||||
aria-label="{{:: 'Index pattern: ' + indexPattern.title}}"
|
||||
ng-if="vis.type.requiresSearch"
|
||||
class="index-pattern"
|
||||
>
|
||||
{{ indexPattern.id }}
|
||||
{{ indexPattern.title }}
|
||||
</div>
|
||||
|
||||
<nav class="navbar navbar-default subnav">
|
||||
|
|
|
@ -192,7 +192,6 @@
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<paginated-selectable-list
|
||||
per-page="20"
|
||||
list="indexPattern.list"
|
||||
list-property="attributes.title"
|
||||
user-make-url="makeUrl"
|
||||
class="wizard-row visualizeWizardPaginatedSelectableList kuiVerticalRhythm"
|
||||
></paginated-selectable-list>
|
||||
|
|
|
@ -13,6 +13,7 @@ import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import visualizeWizardStep1Template from './step_1.html';
|
||||
import visualizeWizardStep2Template from './step_2.html';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
const module = uiModules.get('app/visualize', ['kibana/courier']);
|
||||
|
||||
|
@ -166,8 +167,14 @@ routes.when(VisualizeConstants.WIZARD_STEP_2_PAGE_PATH, {
|
|||
template: visualizeWizardStep2Template,
|
||||
controller: 'VisualizeWizardStep2',
|
||||
resolve: {
|
||||
indexPatternIds: function (courier) {
|
||||
return courier.indexPatterns.getIds();
|
||||
indexPatterns: function (Private) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
return savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
perPage: 10000
|
||||
}).then(response => response.savedObjects);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -197,7 +204,7 @@ module.controller('VisualizeWizardStep2', function ($route, $scope, timefilter,
|
|||
|
||||
$scope.indexPattern = {
|
||||
selection: null,
|
||||
list: $route.current.locals.indexPatternIds
|
||||
list: $route.current.locals.indexPatterns
|
||||
};
|
||||
|
||||
$scope.makeUrl = function (pattern) {
|
||||
|
@ -206,9 +213,9 @@ module.controller('VisualizeWizardStep2', function ($route, $scope, timefilter,
|
|||
if (addToDashMode) {
|
||||
return `#${VisualizeConstants.CREATE_PATH}` +
|
||||
`?${DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM}` +
|
||||
`&type=${type}&indexPattern=${pattern}`;
|
||||
`&type=${type}&indexPattern=${pattern.id}`;
|
||||
}
|
||||
|
||||
return `#${VisualizeConstants.CREATE_PATH}?type=${type}&indexPattern=${pattern}`;
|
||||
return `#${VisualizeConstants.CREATE_PATH}?type=${type}&indexPattern=${pattern.id}`;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
"KIBANA-WILD_CARD_PATTERN": " using wildcard pattern names instead of time-interval based index patterns.",
|
||||
"KIBANA-RECOMMEND_WILD_CARD_PATTERN_DETAILS": "Kibana is now smart enough to automatically determine which indices to search against within the current time range for wildcard index patterns. This means that wildcard index patterns now get the same performance optimizations when searching within a time range as time-interval patterns.",
|
||||
"KIBANA-INDEX_PATTERN_INTERVAL": "Index pattern interval",
|
||||
"KIBANA-INDEX_NAME_OR_PATTERN": "Index name or pattern",
|
||||
"KIBANA-INDEX_PATTERN": "Index pattern",
|
||||
"KIBANA-INDEX_PATTERN_ID": "Index pattern ID",
|
||||
"KIBANA-INDEX_PATTERN_SPECIFY_ID": "Creates the index pattern with the specified ID.",
|
||||
"KIBANA-WILDCARD_DYNAMIC_INDEX_PATTERNS": "Patterns allow you to define dynamic index names using * as a wildcard. Example: logstash-*",
|
||||
"KIBANA-STATIC_TEXT_IN_DYNAMIC_INDEX_PATTERNS": "Patterns allow you to define dynamic index names. Static text in an index name is denoted using brackets. Example: [logstash-]YYYY.MM.DD. Please note that weeks are setup to use ISO weeks which start on Monday.",
|
||||
"KIBANA-NOTE_COLON": "Note:",
|
||||
|
@ -29,6 +31,7 @@
|
|||
"KIBANA-EXISTING_MATCH_PERCENT": "Pattern matches {{indexExistingMatchPercent}} of existing indices and aliases",
|
||||
"KIBANA-NON_MATCHING_INDICES_AND_ALIASES": "Indices and aliases that were found, but did not match the pattern:",
|
||||
"KIBANA-MORE": "more",
|
||||
"KIBANA-ADVANCED_OPTIONS": "advanced options",
|
||||
"KIBANA-TIME_FILTER_FIELD_NAME": "Time Filter field name",
|
||||
"KIBANA-NO_DATE_FIELD_DESIRED": "I don't want to use the Time Filter",
|
||||
"KIBANA-REFRESH_FIELDS": "refresh fields",
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||
|
||||
function stubbedDocSourceResponse(Private) {
|
||||
const mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||
|
||||
return function (id, index) {
|
||||
index = index || '.kibana';
|
||||
return {
|
||||
_id: id,
|
||||
_index: index,
|
||||
_type: 'index-pattern',
|
||||
_version: 2,
|
||||
found: true,
|
||||
_source: {
|
||||
customFormats: '{}',
|
||||
fields: JSON.stringify(mockLogstashFields)
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default stubbedDocSourceResponse;
|
18
src/fixtures/stubbed_saved_object_index_pattern.js
Normal file
18
src/fixtures/stubbed_saved_object_index_pattern.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||
import { SavedObject } from 'ui/saved_objects';
|
||||
|
||||
export function FixturesStubbedSavedObjectIndexPatternProvider(Private) {
|
||||
const mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||
|
||||
return function (id) {
|
||||
return new SavedObject(undefined, {
|
||||
id,
|
||||
type: 'index-pattern',
|
||||
attributes: {
|
||||
customFormats: '{}',
|
||||
fields: JSON.stringify(mockLogstashFields)
|
||||
},
|
||||
version: 2
|
||||
});
|
||||
};
|
||||
}
|
|
@ -36,7 +36,6 @@ describe('Saved Object', function () {
|
|||
|
||||
// Necessary to avoid a timeout condition.
|
||||
sinon.stub(esAdminStub.indices, 'putMapping').returns(BluebirdPromise.resolve());
|
||||
sinon.stub(esAdminStub.indices, 'refresh').returns(BluebirdPromise.resolve());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,11 +61,9 @@ describe('Saved Object', function () {
|
|||
* @param {Object} mockDocResponse
|
||||
*/
|
||||
function stubESResponse(mockDocResponse) {
|
||||
sinon.stub(esAdminStub, 'mget').returns(BluebirdPromise.resolve({ docs: [mockDocResponse] }));
|
||||
sinon.stub(esAdminStub, 'index').returns(BluebirdPromise.resolve(mockDocResponse));
|
||||
|
||||
// Stub out search for duplicate title:
|
||||
sinon.stub(savedObjectsClientStub, 'get').returns(BluebirdPromise.resolve(mockDocResponse));
|
||||
sinon.stub(savedObjectsClientStub, 'update').returns(BluebirdPromise.resolve(mockDocResponse));
|
||||
|
||||
sinon.stub(savedObjectsClientStub, 'find').returns(BluebirdPromise.resolve({ savedObjects: [], total: 0 }));
|
||||
sinon.stub(savedObjectsClientStub, 'bulkGet').returns(BluebirdPromise.resolve({ savedObjects: [mockDocResponse] }));
|
||||
|
@ -112,8 +109,6 @@ describe('Saved Object', function () {
|
|||
describe('with confirmOverwrite', function () {
|
||||
function stubConfirmOverwrite() {
|
||||
window.confirm = sinon.stub().returns(true);
|
||||
|
||||
sinon.stub(esAdminStub, 'create').returns(BluebirdPromise.reject({ status : 409 }));
|
||||
sinon.stub(esDataStub, 'create').returns(BluebirdPromise.reject({ status : 409 }));
|
||||
}
|
||||
|
||||
|
@ -421,7 +416,6 @@ describe('Saved Object', function () {
|
|||
});
|
||||
|
||||
describe('searchSource', function () {
|
||||
|
||||
it('when true, creates index', function () {
|
||||
const indexPatternId = 'testIndexPattern';
|
||||
const afterESRespCallback = sinon.spy();
|
||||
|
@ -434,10 +428,12 @@ describe('Saved Object', function () {
|
|||
};
|
||||
|
||||
stubESResponse({
|
||||
_id: indexPatternId,
|
||||
_type: 'dashboard',
|
||||
_source: {},
|
||||
found: true
|
||||
id: indexPatternId,
|
||||
type: 'dashboard',
|
||||
attributes: {
|
||||
title: 'testIndexPattern'
|
||||
},
|
||||
_version: 2
|
||||
});
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
|
|
|
@ -12,7 +12,6 @@ import { SearchStrategyProvider } from './fetch/strategy/search';
|
|||
import { RequestQueueProvider } from './_request_queue';
|
||||
import { FetchProvider } from './fetch';
|
||||
import { DocDataLooperProvider } from './looper/doc_data';
|
||||
import { DocAdminLooperProvider } from './looper/doc_admin';
|
||||
import { SearchLooperProvider } from './looper/search';
|
||||
import { RootSearchSourceProvider } from './data_source/_root_search_source';
|
||||
import { SavedObjectProvider } from './saved_object';
|
||||
|
@ -32,7 +31,6 @@ uiModules.get('kibana/courier')
|
|||
|
||||
const fetch = Private(FetchProvider);
|
||||
const docDataLooper = self.docLooper = Private(DocDataLooperProvider);
|
||||
const docAdminLooper = self.docLooper = Private(DocAdminLooperProvider);
|
||||
const searchLooper = self.searchLooper = Private(SearchLooperProvider);
|
||||
|
||||
// expose some internal modules
|
||||
|
@ -62,7 +60,6 @@ uiModules.get('kibana/courier')
|
|||
self.start = function () {
|
||||
searchLooper.start();
|
||||
docDataLooper.start();
|
||||
docAdminLooper.start();
|
||||
return this;
|
||||
};
|
||||
|
||||
|
@ -121,7 +118,6 @@ uiModules.get('kibana/courier')
|
|||
*/
|
||||
self.close = function () {
|
||||
searchLooper.stop();
|
||||
docAdminLooper.stop();
|
||||
docDataLooper.stop();
|
||||
|
||||
_.invoke(requestQueue, 'abort');
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { AbstractDocSourceProvider } from './_abstract_doc_source';
|
||||
import { DocAdminStrategyProvider } from '../fetch/strategy/doc_admin';
|
||||
import { AdminDocRequestProvider } from '../fetch/request/doc_admin';
|
||||
|
||||
export function AdminDocSourceProvider(Private) {
|
||||
const AbstractDocSource = Private(AbstractDocSourceProvider);
|
||||
const docStrategy = Private(DocAdminStrategyProvider);
|
||||
const AdminDocRequest = Private(AdminDocRequestProvider);
|
||||
|
||||
class AdminDocSource extends AbstractDocSource {
|
||||
constructor(initialState) {
|
||||
super(initialState, docStrategy);
|
||||
}
|
||||
|
||||
_createRequest(defer) {
|
||||
return new AdminDocRequest(this, defer);
|
||||
}
|
||||
}
|
||||
|
||||
return AdminDocSource;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { DocAdminStrategyProvider } from '../strategy/doc_admin';
|
||||
import { AbstractDocRequestProvider } from './_abstract_doc';
|
||||
|
||||
export function AdminDocRequestProvider(Private) {
|
||||
|
||||
const docStrategy = Private(DocAdminStrategyProvider);
|
||||
const AbstractDocRequest = Private(AbstractDocRequestProvider);
|
||||
|
||||
class AdminDocRequest extends AbstractDocRequest {
|
||||
strategy = docStrategy;
|
||||
}
|
||||
|
||||
return AdminDocRequest;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
export function DocAdminStrategyProvider(Promise) {
|
||||
return {
|
||||
id: 'doc_admin',
|
||||
clientMethod: 'mget',
|
||||
|
||||
/**
|
||||
* Flatten a series of requests into as ES request body
|
||||
* @param {array} requests - an array of flattened requests
|
||||
* @return {Promise} - a promise that is fulfilled by the request body
|
||||
*/
|
||||
reqsFetchParamsToBody: function (reqsFetchParams) {
|
||||
return Promise.resolve({
|
||||
docs: reqsFetchParams
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch the multiple responses from the ES Response
|
||||
* @param {object} resp - The response sent from Elasticsearch
|
||||
* @return {array} - the list of responses
|
||||
*/
|
||||
getResponses: function (resp) {
|
||||
return resp.docs;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import { FetchProvider } from '../fetch';
|
||||
import { LooperProvider } from './_looper';
|
||||
import { DocAdminStrategyProvider } from '../fetch/strategy/doc_admin';
|
||||
|
||||
export function DocAdminLooperProvider(Private) {
|
||||
const fetch = Private(FetchProvider);
|
||||
const Looper = Private(LooperProvider);
|
||||
const DocStrategy = Private(DocAdminStrategyProvider);
|
||||
|
||||
/**
|
||||
* The Looper which will manage the doc fetch interval
|
||||
* @type {Looper}
|
||||
*/
|
||||
const docLooper = new Looper(1500, function () {
|
||||
fetch.fetchQueued(DocStrategy);
|
||||
});
|
||||
|
||||
return docLooper;
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import { find } from 'lodash';
|
||||
/**
|
||||
* Returns true if the given saved object has a title that already exists, false otherwise. Search is case
|
||||
* insensitive.
|
||||
* @param savedObject {SavedObject} The object with the title to check.
|
||||
* @param esAdmin {Object} Used to query es
|
||||
* @returns {Promise<string|undefined>} Returns the title that matches. Because this search is not case
|
||||
* sensitive, it may not exactly match the title of the object.
|
||||
*/
|
||||
export function getTitleAlreadyExists(savedObject, savedObjectsClient) {
|
||||
const { title, id } = savedObject;
|
||||
const type = savedObject.getEsType();
|
||||
if (!title) {
|
||||
throw new Error('Title must be supplied');
|
||||
}
|
||||
|
||||
// Elastic search will return the most relevant results first, which means exact matches should come
|
||||
// first, and so we shouldn't need to request everything. Using 10 just to be on the safe side.
|
||||
const perPage = 10;
|
||||
return savedObjectsClient.find({
|
||||
type,
|
||||
perPage,
|
||||
search: title,
|
||||
searchFields: 'title',
|
||||
fields: ['title']
|
||||
}).then(response => {
|
||||
const match = find(response.savedObjects, (obj) => {
|
||||
return obj.id !== id && obj.get('title').toLowerCase() === title.toLowerCase();
|
||||
});
|
||||
|
||||
return match ? match.get('title') : undefined;
|
||||
});
|
||||
}
|
|
@ -16,8 +16,7 @@ import { SavedObjectNotFound } from 'ui/errors';
|
|||
import MappingSetupProvider from 'ui/utils/mapping_setup';
|
||||
|
||||
import { SearchSourceProvider } from '../data_source/search_source';
|
||||
import { getTitleAlreadyExists } from './get_title_already_exists';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
import { SavedObjectsClientProvider, findObjectByTitle } from 'ui/saved_objects';
|
||||
|
||||
/**
|
||||
* An error message to be used when the user rejects a confirm overwrite.
|
||||
|
@ -312,12 +311,13 @@ export function SavedObjectProvider(esAdmin, kbnIndex, Promise, Private, Notifie
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return getTitleAlreadyExists(this, savedObjectsClient)
|
||||
.then(duplicateTitle => {
|
||||
if (!duplicateTitle) return true;
|
||||
return findObjectByTitle(savedObjectsClient, this.getEsType(), this.title)
|
||||
.then(duplicate => {
|
||||
if (!duplicate) return true;
|
||||
if (duplicate.id === this.id) return true;
|
||||
|
||||
const confirmMessage =
|
||||
`A ${this.getDisplayName()} with the title '${duplicateTitle}' already exists. Would you like to save anyway?`;
|
||||
`A ${this.getDisplayName()} with the title '${this.title}' already exists. Would you like to save anyway?`;
|
||||
|
||||
return confirmModalPromise(confirmMessage, { confirmButtonText: `Save ${this.getDisplayName()}` })
|
||||
.catch(() => Promise.reject(new Error(SAVE_DUPLICATE_REJECTED)));
|
||||
|
|
|
@ -8,6 +8,7 @@ describe('Validate index name directive', function () {
|
|||
let $compile;
|
||||
let $rootScope;
|
||||
const noWildcardHtml = '<input type="text" ng-model="indexName" validate-index-name />';
|
||||
const requiredHtml = '<input type="text" ng-model="indexName" validate-index-name required />';
|
||||
const allowWildcardHtml = '<input type="text" ng-model="indexName" allow-wildcard validate-index-name />';
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
|
@ -24,10 +25,13 @@ describe('Validate index name directive', function () {
|
|||
return element;
|
||||
}
|
||||
|
||||
const badPatterns = [
|
||||
null,
|
||||
const emptyPatterns = [
|
||||
undefined,
|
||||
'',
|
||||
null,
|
||||
''
|
||||
];
|
||||
|
||||
const badPatterns = [
|
||||
'.',
|
||||
'..',
|
||||
'foo\\bar',
|
||||
|
@ -71,6 +75,14 @@ describe('Validate index name directive', function () {
|
|||
});
|
||||
});
|
||||
|
||||
emptyPatterns.forEach(function (pattern) {
|
||||
it('should not accept index pattern: ' + pattern, function () {
|
||||
const element = checkPattern(pattern, requiredHtml);
|
||||
expect(element.hasClass('ng-invalid')).to.be(true);
|
||||
expect(element.hasClass('ng-valid')).to.not.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should disallow wildcards by default', function () {
|
||||
wildcardPatterns.forEach(function (pattern) {
|
||||
const element = checkPattern(pattern, noWildcardHtml);
|
||||
|
|
|
@ -15,7 +15,7 @@ module.directive('paginatedSelectableList', function () {
|
|||
scope: {
|
||||
perPage: '=?',
|
||||
list: '=',
|
||||
listProperty: '=',
|
||||
listProperty: '@',
|
||||
userMakeUrl: '=?',
|
||||
userOnSelect: '=?'
|
||||
},
|
||||
|
@ -32,7 +32,7 @@ module.directive('paginatedSelectableList', function () {
|
|||
}
|
||||
|
||||
$scope.perPage = $scope.perPage || 10;
|
||||
$scope.hits = $scope.list = _.sortBy($scope.list, accessor);
|
||||
$scope.hits = $scope.list = _.sortBy($scope.list, $scope.accessor);
|
||||
$scope.hitCount = $scope.hits.length;
|
||||
|
||||
/**
|
||||
|
@ -48,7 +48,7 @@ module.directive('paginatedSelectableList', function () {
|
|||
* @return {Array} Array sorted either ascending or descending
|
||||
*/
|
||||
$scope.sortHits = function (hits) {
|
||||
const sortedList = _.sortBy(hits, accessor);
|
||||
const sortedList = _.sortBy(hits, $scope.accessor);
|
||||
|
||||
$scope.isAscending = !$scope.isAscending;
|
||||
$scope.hits = $scope.isAscending ? sortedList : sortedList.reverse();
|
||||
|
@ -62,10 +62,10 @@ module.directive('paginatedSelectableList', function () {
|
|||
return $scope.userOnSelect(hit, $event);
|
||||
};
|
||||
|
||||
function accessor(val) {
|
||||
$scope.accessor = function (val) {
|
||||
const prop = $scope.listProperty;
|
||||
return prop ? val[prop] : val;
|
||||
}
|
||||
return prop ? _.get(val, prop) : val;
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -16,11 +16,13 @@ uiModules
|
|||
}
|
||||
|
||||
const isValid = function (input) {
|
||||
if (input == null || input === '' || input === '.' || input === '..') return false;
|
||||
if (input == null || input === '') return !attr.required === true;
|
||||
if (input === '.' || input === '..') return false;
|
||||
|
||||
const match = _.find(illegalCharacters, function (character) {
|
||||
return input.indexOf(character) >= 0;
|
||||
});
|
||||
|
||||
return !match;
|
||||
};
|
||||
|
||||
|
|
|
@ -161,15 +161,31 @@ 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
|
||||
*/
|
||||
export class SavedObjectNotFound extends KbnError {
|
||||
constructor(type, id) {
|
||||
constructor(type, id, link) {
|
||||
const idMsg = id ? ` (id: ${id})` : '';
|
||||
super(
|
||||
`Could not locate that ${type}${idMsg}`,
|
||||
SavedObjectNotFound);
|
||||
let message = `Could not locate that ${type}${idMsg}`;
|
||||
|
||||
if (link) {
|
||||
message += `, [click here to re-create it](${link})`;
|
||||
}
|
||||
|
||||
super(message, SavedObjectNotFound);
|
||||
|
||||
this.savedObjectType = type;
|
||||
this.savedObjectId = id;
|
||||
|
|
|
@ -6,8 +6,7 @@ import Promise from 'bluebird';
|
|||
import { DuplicateField } from 'ui/errors';
|
||||
import { IndexedArray } from 'ui/indexed_array';
|
||||
import FixturesLogstashFieldsProvider from 'fixtures/logstash_fields';
|
||||
import FixturesStubbedDocSourceResponseProvider from 'fixtures/stubbed_doc_source_response';
|
||||
import { AdminDocSourceProvider } from 'ui/courier/data_source/admin_doc_source';
|
||||
import { FixturesStubbedSavedObjectIndexPatternProvider } from 'fixtures/stubbed_saved_object_index_pattern';
|
||||
import UtilsMappingSetupProvider from 'ui/utils/mapping_setup';
|
||||
import { IndexPatternsIntervalsProvider } from 'ui/index_patterns/_intervals';
|
||||
import { IndexPatternProvider } from 'ui/index_patterns/_index_pattern';
|
||||
|
@ -17,6 +16,7 @@ import { FieldsFetcherProvider } from '../fields_fetcher_provider';
|
|||
import { StubIndexPatternsApiClientModule } from './stub_index_patterns_api_client';
|
||||
import { IndexPatternsApiClientProvider } from '../index_patterns_api_client_provider';
|
||||
import { IndexPatternsCalculateIndicesProvider } from '../_calculate_indices';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
describe('index pattern', function () {
|
||||
NoDigestPromises.activateForSuite();
|
||||
|
@ -25,8 +25,8 @@ describe('index pattern', function () {
|
|||
let fieldsFetcher;
|
||||
let mappingSetup;
|
||||
let mockLogstashFields;
|
||||
let DocSource;
|
||||
let docSourceResponse;
|
||||
let savedObjectsClient;
|
||||
let savedObjectsResponse;
|
||||
const indexPatternId = 'test-pattern';
|
||||
let indexPattern;
|
||||
let calculateIndices;
|
||||
|
@ -51,11 +51,12 @@ describe('index pattern', function () {
|
|||
beforeEach(ngMock.inject(function (Private) {
|
||||
mockLogstashFields = Private(FixturesLogstashFieldsProvider);
|
||||
defaultTimeField = mockLogstashFields.find(f => f.type === 'date');
|
||||
docSourceResponse = Private(FixturesStubbedDocSourceResponseProvider);
|
||||
savedObjectsResponse = Private(FixturesStubbedSavedObjectIndexPatternProvider);
|
||||
|
||||
DocSource = Private(AdminDocSourceProvider);
|
||||
sinon.stub(DocSource.prototype, 'doIndex');
|
||||
sinon.stub(DocSource.prototype, 'fetch');
|
||||
savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
sinon.stub(savedObjectsClient, 'create');
|
||||
sinon.stub(savedObjectsClient, 'get');
|
||||
sinon.stub(savedObjectsClient, 'update');
|
||||
|
||||
// stub mappingSetup
|
||||
mappingSetup = Private(UtilsMappingSetupProvider);
|
||||
|
@ -85,14 +86,17 @@ describe('index pattern', function () {
|
|||
// helper function to create index patterns
|
||||
function create(id, payload) {
|
||||
const indexPattern = new IndexPattern(id);
|
||||
DocSource.prototype.doIndex.returns(Promise.resolve(id));
|
||||
payload = _.defaults(payload || {}, docSourceResponse(id));
|
||||
payload = _.defaults(payload || {}, savedObjectsResponse(id));
|
||||
|
||||
savedObjectsClient.create.returns(Promise.resolve(payload));
|
||||
setDocsourcePayload(payload);
|
||||
|
||||
return indexPattern.init();
|
||||
}
|
||||
|
||||
function setDocsourcePayload(payload) {
|
||||
DocSource.prototype.fetch.returns(Promise.resolve(payload));
|
||||
savedObjectsClient.get.returns(Promise.resolve(payload));
|
||||
savedObjectsClient.update.returns(Promise.resolve(payload));
|
||||
}
|
||||
|
||||
describe('api', function () {
|
||||
|
@ -118,7 +122,7 @@ describe('index pattern', function () {
|
|||
|
||||
describe('init', function () {
|
||||
it('should append the found fields', function () {
|
||||
expect(DocSource.prototype.fetch.callCount).to.be(1);
|
||||
expect(savedObjectsClient.get.callCount).to.be(1);
|
||||
expect(indexPattern.fields).to.have.length(mockLogstashFields.length);
|
||||
expect(indexPattern.fields).to.be.an(IndexedArray);
|
||||
});
|
||||
|
@ -285,8 +289,7 @@ describe('index pattern', function () {
|
|||
|
||||
it('invokes interval toDetailedIndexList with given start/stop times', async function () {
|
||||
await indexPattern.toDetailedIndexList(1, 2);
|
||||
const id = indexPattern.id;
|
||||
sinon.assert.calledWith(intervals.toIndexList, id, interval, 1, 2);
|
||||
sinon.assert.calledWith(intervals.toIndexList, indexPattern.title, interval, 1, 2);
|
||||
});
|
||||
|
||||
it('is fulfilled by the result of interval toDetailedIndexList', async function () {
|
||||
|
@ -305,7 +308,8 @@ describe('index pattern', function () {
|
|||
|
||||
describe('when index pattern is a time-base wildcard', function () {
|
||||
beforeEach(function () {
|
||||
indexPattern.id = 'logstash-*';
|
||||
indexPattern.id = 'randomID';
|
||||
indexPattern.title = 'logstash-*';
|
||||
indexPattern.timeFieldName = defaultTimeField.name;
|
||||
indexPattern.intervalName = null;
|
||||
indexPattern.notExpandable = false;
|
||||
|
@ -313,9 +317,13 @@ describe('index pattern', function () {
|
|||
|
||||
it('invokes calculateIndices with given start/stop times and sortOrder', async function () {
|
||||
await indexPattern.toDetailedIndexList(1, 2, 'sortOrder');
|
||||
const id = indexPattern.id;
|
||||
const field = indexPattern.timeFieldName;
|
||||
expect(calculateIndices.calledWith(id, field, 1, 2, 'sortOrder')).to.be(true);
|
||||
|
||||
const { title, timeFieldName } = indexPattern;
|
||||
|
||||
sinon.assert.calledOnce(calculateIndices);
|
||||
expect(calculateIndices.getCall(0).args).to.eql([
|
||||
title, timeFieldName, 1, 2, 'sortOrder'
|
||||
]);
|
||||
});
|
||||
|
||||
it('is fulfilled by the result of calculateIndices', async function () {
|
||||
|
@ -327,29 +335,31 @@ describe('index pattern', function () {
|
|||
|
||||
describe('when index pattern is a time-base wildcard that is configured not to expand', function () {
|
||||
beforeEach(function () {
|
||||
indexPattern.id = 'logstash-*';
|
||||
indexPattern.id = 'randomID';
|
||||
indexPattern.title = 'logstash-*';
|
||||
indexPattern.timeFieldName = defaultTimeField.name;
|
||||
indexPattern.intervalName = null;
|
||||
indexPattern.notExpandable = true;
|
||||
});
|
||||
|
||||
it('is fulfilled by id', async function () {
|
||||
it('is fulfilled by title', async function () {
|
||||
const indexList = await indexPattern.toDetailedIndexList();
|
||||
expect(indexList.map(i => i.index)).to.eql([indexPattern.id]);
|
||||
expect(indexList.map(i => i.index)).to.eql([indexPattern.title]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when index pattern is neither an interval nor a time-based wildcard', function () {
|
||||
beforeEach(function () {
|
||||
indexPattern.id = 'logstash-0';
|
||||
indexPattern.id = 'randomID';
|
||||
indexPattern.title = 'logstash-0';
|
||||
indexPattern.timeFieldName = null;
|
||||
indexPattern.intervalName = null;
|
||||
indexPattern.notExpandable = true;
|
||||
});
|
||||
|
||||
it('is fulfilled by id', async function () {
|
||||
it('is fulfilled by title', async function () {
|
||||
const indexList = await indexPattern.toDetailedIndexList();
|
||||
expect(indexList.map(i => i.index)).to.eql([indexPattern.id]);
|
||||
expect(indexList.map(i => i.index)).to.eql([indexPattern.title]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -359,7 +369,8 @@ describe('index pattern', function () {
|
|||
|
||||
let interval;
|
||||
beforeEach(function () {
|
||||
indexPattern.id = '[logstash-]YYYY';
|
||||
indexPattern.id = 'randomID';
|
||||
indexPattern.title = '[logstash-]YYYY';
|
||||
indexPattern.timeFieldName = defaultTimeField.name;
|
||||
interval = intervals.byName.years;
|
||||
indexPattern.intervalName = interval.name;
|
||||
|
@ -368,8 +379,8 @@ describe('index pattern', function () {
|
|||
|
||||
it('invokes interval toIndexList with given start/stop times', async function () {
|
||||
await indexPattern.toIndexList(1, 2);
|
||||
const id = indexPattern.id;
|
||||
sinon.assert.calledWith(intervals.toIndexList, id, interval, 1, 2);
|
||||
const { title } = indexPattern;
|
||||
sinon.assert.calledWith(intervals.toIndexList, title, interval, 1, 2);
|
||||
});
|
||||
|
||||
it('is fulfilled by the result of interval toIndexList', async function () {
|
||||
|
@ -391,7 +402,8 @@ describe('index pattern', function () {
|
|||
|
||||
describe('when index pattern is a time-base wildcard', function () {
|
||||
beforeEach(function () {
|
||||
indexPattern.id = 'logstash-*';
|
||||
indexPattern.id = 'randomID';
|
||||
indexPattern.title = 'logstash-*';
|
||||
indexPattern.timeFieldName = defaultTimeField.name;
|
||||
indexPattern.intervalName = null;
|
||||
indexPattern.notExpandable = false;
|
||||
|
@ -399,9 +411,8 @@ describe('index pattern', function () {
|
|||
|
||||
it('invokes calculateIndices with given start/stop times and sortOrder', async function () {
|
||||
await indexPattern.toIndexList(1, 2, 'sortOrder');
|
||||
const id = indexPattern.id;
|
||||
const field = indexPattern.timeFieldName;
|
||||
expect(calculateIndices.calledWith(id, field, 1, 2, 'sortOrder')).to.be(true);
|
||||
const { title, timeFieldName } = indexPattern;
|
||||
expect(calculateIndices.calledWith(title, timeFieldName, 1, 2, 'sortOrder')).to.be(true);
|
||||
});
|
||||
|
||||
it('is fulfilled by the result of calculateIndices', async function () {
|
||||
|
@ -413,7 +424,8 @@ describe('index pattern', function () {
|
|||
|
||||
describe('when index pattern is a time-base wildcard that is configured not to expand', function () {
|
||||
beforeEach(function () {
|
||||
indexPattern.id = 'logstash-*';
|
||||
indexPattern.id = 'randomID';
|
||||
indexPattern.title = 'logstash-*';
|
||||
indexPattern.timeFieldName = defaultTimeField.name;
|
||||
indexPattern.intervalName = null;
|
||||
indexPattern.notExpandable = true;
|
||||
|
@ -421,13 +433,14 @@ describe('index pattern', function () {
|
|||
|
||||
it('is fulfilled using the id', async function () {
|
||||
const indexList = await indexPattern.toIndexList();
|
||||
expect(indexList).to.eql([indexPattern.id]);
|
||||
expect(indexList).to.eql([indexPattern.title]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when index pattern is neither an interval nor a time-based wildcard', function () {
|
||||
beforeEach(function () {
|
||||
indexPattern.id = 'logstash-0';
|
||||
indexPattern.id = 'randomID';
|
||||
indexPattern.title = 'logstash-0';
|
||||
indexPattern.timeFieldName = null;
|
||||
indexPattern.intervalName = null;
|
||||
indexPattern.notExpandable = true;
|
||||
|
@ -435,7 +448,7 @@ describe('index pattern', function () {
|
|||
|
||||
it('is fulfilled by id', async function () {
|
||||
const indexList = await indexPattern.toIndexList();
|
||||
expect(indexList).to.eql([indexPattern.id]);
|
||||
expect(indexList).to.eql([indexPattern.title]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -480,11 +493,11 @@ describe('index pattern', function () {
|
|||
|
||||
describe('#isWildcard()', function () {
|
||||
it('returns true if id has an *', function () {
|
||||
indexPattern.id = 'foo*';
|
||||
indexPattern.title = 'foo*';
|
||||
expect(indexPattern.isWildcard()).to.be(true);
|
||||
});
|
||||
it('returns false if id has no *', function () {
|
||||
indexPattern.id = 'foo';
|
||||
indexPattern.title = 'foo';
|
||||
expect(indexPattern.isWildcard()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import _ from 'lodash';
|
||||
import { SavedObjectsClientProvider } from 'ui/saved_objects';
|
||||
|
||||
export function IndexPatternsGetIdsProvider(esAdmin, kbnIndex) {
|
||||
export function IndexPatternsGetIdsProvider(Private) {
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
// many places may require the id list, so we will cache it separately
|
||||
// didn't incorporate with the indexPattern cache to prevent id collisions.
|
||||
|
@ -14,17 +16,12 @@ export function IndexPatternsGetIdsProvider(esAdmin, kbnIndex) {
|
|||
});
|
||||
}
|
||||
|
||||
cachedPromise = esAdmin.search({
|
||||
index: kbnIndex,
|
||||
cachedPromise = savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
storedFields: [],
|
||||
body: {
|
||||
query: { match_all: {} },
|
||||
size: 10000
|
||||
}
|
||||
})
|
||||
.then(function (resp) {
|
||||
return _.pluck(resp.hits.hits, '_id');
|
||||
fields: [],
|
||||
perPage: 10000
|
||||
}).then(resp => {
|
||||
return resp.savedObjects.map(obj => obj.id);
|
||||
});
|
||||
|
||||
// ensure that the response stays pristine by cloning it here too
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import _ from 'lodash';
|
||||
import { SavedObjectNotFound, DuplicateField, IndexPatternMissingIndices } from 'ui/errors';
|
||||
import { SavedObjectNotFound, DuplicateField, IndexPatternAlreadyExists, IndexPatternMissingIndices } from 'ui/errors';
|
||||
import angular from 'angular';
|
||||
import { RegistryFieldFormatsProvider } from 'ui/registry/field_formats';
|
||||
import { AdminDocSourceProvider } from 'ui/courier/data_source/admin_doc_source';
|
||||
import UtilsMappingSetupProvider from 'ui/utils/mapping_setup';
|
||||
import { Notifier } from 'ui/notify';
|
||||
|
||||
|
@ -15,13 +14,13 @@ import { IndexPatternsFlattenHitProvider } from './_flatten_hit';
|
|||
import { IndexPatternsCalculateIndicesProvider } from './_calculate_indices';
|
||||
import { IndexPatternsPatternCacheProvider } from './_pattern_cache';
|
||||
import { FieldsFetcherProvider } from './fields_fetcher_provider';
|
||||
import { SavedObjectsClientProvider, findObjectByTitle } from 'ui/saved_objects';
|
||||
|
||||
export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise, confirmModalPromise) {
|
||||
export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise, confirmModalPromise, kbnUrl) {
|
||||
const fieldformats = Private(RegistryFieldFormatsProvider);
|
||||
const getIds = Private(IndexPatternsGetIdsProvider);
|
||||
const fieldsFetcher = Private(FieldsFetcherProvider);
|
||||
const intervals = Private(IndexPatternsIntervalsProvider);
|
||||
const DocSource = Private(AdminDocSourceProvider);
|
||||
const mappingSetup = Private(UtilsMappingSetupProvider);
|
||||
const FieldList = Private(IndexPatternsFieldListProvider);
|
||||
const flattenHit = Private(IndexPatternsFlattenHitProvider);
|
||||
|
@ -30,7 +29,6 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
const type = 'index-pattern';
|
||||
const notify = new Notifier();
|
||||
const configWatchers = new WeakMap();
|
||||
const docSources = new WeakMap();
|
||||
const getRoutes = () => ({
|
||||
edit: '/management/kibana/indices/{{id}}',
|
||||
addField: '/management/kibana/indices/{{id}}/create-field',
|
||||
|
@ -38,6 +36,7 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
scriptedFields: '/management/kibana/indices/{{id}}?_a=(tab:scriptedFields)',
|
||||
sourceFilters: '/management/kibana/indices/{{id}}?_a=(tab:sourceFilters)'
|
||||
});
|
||||
const savedObjectsClient = Private(SavedObjectsClientProvider);
|
||||
|
||||
const mapping = mappingSetup.expandShorthand({
|
||||
title: 'string',
|
||||
|
@ -71,7 +70,13 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
|
||||
function updateFromElasticSearch(indexPattern, response) {
|
||||
if (!response.found) {
|
||||
throw new SavedObjectNotFound(type, indexPattern.id);
|
||||
const markdownSaveId = indexPattern.id.replace('*', '%2A');
|
||||
|
||||
throw new SavedObjectNotFound(
|
||||
type,
|
||||
indexPattern.id,
|
||||
kbnUrl.eval('#/management/kibana/index?id={{id}}&name=', { id: markdownSaveId })
|
||||
);
|
||||
}
|
||||
|
||||
_.forOwn(mapping, (fieldMapping, name) => {
|
||||
|
@ -84,15 +89,7 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
// give index pattern all of the values in _source
|
||||
_.assign(indexPattern, response._source);
|
||||
|
||||
const promise = indexFields(indexPattern);
|
||||
|
||||
// any time index pattern in ES is updated, update index pattern object
|
||||
docSources
|
||||
.get(indexPattern)
|
||||
.onUpdate()
|
||||
.then(response => updateFromElasticSearch(indexPattern, response), notify.fatal);
|
||||
|
||||
return promise;
|
||||
return indexFields(indexPattern);
|
||||
}
|
||||
|
||||
function isFieldRefreshRequired(indexPattern) {
|
||||
|
@ -171,8 +168,6 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
class IndexPattern {
|
||||
constructor(id) {
|
||||
setId(this, id);
|
||||
docSources.set(this, new DocSource());
|
||||
|
||||
this.metaFields = config.get('metaFields');
|
||||
this.getComputedFields = getComputedFields.bind(this);
|
||||
|
||||
|
@ -186,12 +181,6 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
}
|
||||
|
||||
init() {
|
||||
docSources
|
||||
.get(this)
|
||||
.index(kbnIndex)
|
||||
.type(type)
|
||||
.id(this.id);
|
||||
|
||||
watch(this);
|
||||
|
||||
return mappingSetup
|
||||
|
@ -206,8 +195,19 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
if (!this.id) {
|
||||
return; // no id === no elasticsearch document
|
||||
}
|
||||
return docSources.get(this).fetch()
|
||||
.then(response => updateFromElasticSearch(this, response));
|
||||
|
||||
return savedObjectsClient.get(type, this.id)
|
||||
.then(resp => {
|
||||
// temporary compatability for savedObjectsClient
|
||||
|
||||
return {
|
||||
_id: resp.id,
|
||||
_type: resp.type,
|
||||
_source: _.cloneDeep(resp.attributes),
|
||||
found: resp._version ? true : false
|
||||
};
|
||||
})
|
||||
.then(response => updateFromElasticSearch(this, response));
|
||||
})
|
||||
.then(() => this);
|
||||
}
|
||||
|
@ -287,19 +287,19 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
return Promise.resolve().then(() => {
|
||||
if (this.isTimeBasedInterval()) {
|
||||
return intervals.toIndexList(
|
||||
this.id, this.getInterval(), start, stop, sortDirection
|
||||
this.title, this.getInterval(), start, stop, sortDirection
|
||||
);
|
||||
}
|
||||
|
||||
if (this.isTimeBasedWildcard() && this.isIndexExpansionEnabled()) {
|
||||
return calculateIndices(
|
||||
this.id, this.timeFieldName, start, stop, sortDirection
|
||||
this.title, this.timeFieldName, start, stop, sortDirection
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
index: this.id,
|
||||
index: this.title,
|
||||
min: -Infinity,
|
||||
max: Infinity
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
}
|
||||
|
||||
isWildcard() {
|
||||
return _.includes(this.id, '*');
|
||||
return _.includes(this.title, '*');
|
||||
}
|
||||
|
||||
prepBody() {
|
||||
|
@ -344,45 +344,68 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
}
|
||||
});
|
||||
|
||||
// ensure that the docSource has the current this.id
|
||||
docSources.get(this).id(this.id);
|
||||
|
||||
// clear the indexPattern list cache
|
||||
getIds.clearCache();
|
||||
return body;
|
||||
}
|
||||
|
||||
create() {
|
||||
const body = this.prepBody();
|
||||
return docSources.get(this)
|
||||
.doCreate(body)
|
||||
.then(id => setId(this, id))
|
||||
.catch(err => {
|
||||
if (_.get(err, 'origError.status') !== 409) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
const confirmMessage = 'Are you sure you want to overwrite this?';
|
||||
/**
|
||||
* Returns a promise that resolves to true if either the title is unique, or if the user confirmed they
|
||||
* wished to save the duplicate title. Promise is rejected if the user rejects the confirmation.
|
||||
*/
|
||||
warnIfDuplicateTitle() {
|
||||
return findObjectByTitle(savedObjectsClient, type, this.title)
|
||||
.then(duplicate => {
|
||||
if (!duplicate) return false;
|
||||
if (duplicate.id === this.id) return false;
|
||||
|
||||
return confirmModalPromise(confirmMessage, { confirmButtonText: 'Overwrite' })
|
||||
.then(() => Promise
|
||||
.try(() => {
|
||||
const cached = patternCache.get(this.id);
|
||||
if (cached) {
|
||||
return cached.then(pattern => pattern.destroy());
|
||||
const confirmMessage =
|
||||
`An index pattern with the title '${this.title}' already exists.`;
|
||||
|
||||
return confirmModalPromise(confirmMessage, { confirmButtonText: 'Edit existing pattern' })
|
||||
.then(() => {
|
||||
kbnUrl.change('/management/kibana/indices/{{id}}', { id: duplicate.id });
|
||||
return true;
|
||||
})
|
||||
.catch(() => {
|
||||
throw new IndexPatternAlreadyExists(this.title);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
create() {
|
||||
return this.warnIfDuplicateTitle().then((duplicate) => {
|
||||
if (duplicate) return;
|
||||
|
||||
const body = this.prepBody();
|
||||
|
||||
return savedObjectsClient.create(type, body, { id: this.id })
|
||||
.then(response => setId(this, response.id))
|
||||
.catch(err => {
|
||||
if (err.statusCode !== 409) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
})
|
||||
.then(() => docSources.get(this).doIndex(body))
|
||||
.then(id => setId(this, id)),
|
||||
_.constant(false) // if the user doesn't overwrite, resolve with false
|
||||
);
|
||||
const confirmMessage = 'Are you sure you want to overwrite this?';
|
||||
|
||||
return confirmModalPromise(confirmMessage, { confirmButtonText: 'Overwrite' })
|
||||
.then(() => Promise
|
||||
.try(() => {
|
||||
const cached = patternCache.get(this.id);
|
||||
if (cached) {
|
||||
return cached.then(pattern => pattern.destroy());
|
||||
}
|
||||
})
|
||||
.then(() => savedObjectsClient.create(type, body, { id: this.id, overwrite: true }))
|
||||
.then(response => setId(this, response.id)),
|
||||
_.constant(false) // if the user doesn't overwrite, resolve with false
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
const body = this.prepBody();
|
||||
return docSources.get(this)
|
||||
.doIndex(body)
|
||||
.then(id => setId(this, id));
|
||||
async save() {
|
||||
return savedObjectsClient.update(type, this.id, this.prepBody())
|
||||
.then(({ id }) => setId(this, id));
|
||||
}
|
||||
|
||||
refreshFields() {
|
||||
|
@ -415,8 +438,7 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
|
|||
destroy() {
|
||||
unwatch(this);
|
||||
patternCache.clear(this.id);
|
||||
docSources.get(this).destroy();
|
||||
docSources.delete(this);
|
||||
return savedObjectsClient.delete(type, this.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ export function createFieldsFetcher(apiClient, config) {
|
|||
fetch(indexPattern) {
|
||||
if (indexPattern.isTimeBasedInterval()) {
|
||||
const interval = indexPattern.getInterval().name;
|
||||
return this.fetchForTimePattern(indexPattern.id, interval);
|
||||
return this.fetchForTimePattern(indexPattern.title, interval);
|
||||
}
|
||||
|
||||
return this.fetchForWildcard(indexPattern.id);
|
||||
return this.fetchForWildcard(indexPattern.title);
|
||||
}
|
||||
|
||||
testTimePattern(indexPatternId) {
|
||||
|
|
|
@ -10,7 +10,8 @@ import { uiModules } from 'ui/modules';
|
|||
const module = uiModules.get('kibana/index_patterns');
|
||||
|
||||
export { IndexPatternsApiClientProvider } from './index_patterns_api_client_provider';
|
||||
export function IndexPatternsProvider(esAdmin, Notifier, Private, Promise, kbnIndex) {
|
||||
|
||||
export function IndexPatternsProvider(Notifier, Private) {
|
||||
const self = this;
|
||||
|
||||
const IndexPattern = Private(IndexPatternProvider);
|
||||
|
@ -29,13 +30,7 @@ export function IndexPatternsProvider(esAdmin, Notifier, Private, Promise, kbnIn
|
|||
|
||||
self.delete = function (pattern) {
|
||||
self.getIds.clearCache();
|
||||
pattern.destroy();
|
||||
|
||||
return esAdmin.delete({
|
||||
index: kbnIndex,
|
||||
type: 'index-pattern',
|
||||
id: pattern.id
|
||||
});
|
||||
return pattern.destroy();
|
||||
};
|
||||
|
||||
self.errors = {
|
||||
|
|
|
@ -42,11 +42,11 @@
|
|||
|
||||
<li class="list-group-item list-group-menu-item" ng-repeat="hit in page">
|
||||
<a ng-show="userMakeUrl" kbn-href="{{ makeUrl(hit) }}">
|
||||
<span>{{ hit }}</span>
|
||||
<span>{{ accessor(hit) }}</span>
|
||||
</a>
|
||||
|
||||
<div ng-show="userOnSelect" ng-click="onSelect(hit, $event)">
|
||||
<span>{{ hit }}</span>
|
||||
<span>{{ accessor(hit) }}</span>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
import { findObjectByTitle } from '../find_object_by_title';
|
||||
import { SavedObject } from '../saved_object';
|
||||
|
||||
describe('findObjectByTitle', () => {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
const savedObjectsClient = {};
|
||||
|
||||
beforeEach(() => {
|
||||
savedObjectsClient.find = sandbox.stub();
|
||||
});
|
||||
|
||||
afterEach(() => sandbox.restore());
|
||||
|
||||
it('returns undefined if title is not provided', async () => {
|
||||
const match = await findObjectByTitle(savedObjectsClient, 'index-pattern');
|
||||
expect(match).to.be(undefined);
|
||||
});
|
||||
|
||||
it('matches any case', async () => {
|
||||
const indexPattern = new SavedObject(savedObjectsClient, { attributes: { title: 'foo' } });
|
||||
savedObjectsClient.find.returns(Promise.resolve({
|
||||
savedObjects: [indexPattern]
|
||||
}));
|
||||
|
||||
const match = await findObjectByTitle(savedObjectsClient, 'index-pattern', 'FOO');
|
||||
expect(match).to.eql(indexPattern);
|
||||
});
|
||||
});
|
29
src/ui/public/saved_objects/find_object_by_title.js
Normal file
29
src/ui/public/saved_objects/find_object_by_title.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { find } from 'lodash';
|
||||
|
||||
/**
|
||||
* Returns an object matching a given title
|
||||
*
|
||||
* @param savedObjectsClient {SavedObjectsClient}
|
||||
* @param type {string}
|
||||
* @param title {string}
|
||||
* @returns {Promise<SavedObject|undefined>}
|
||||
*/
|
||||
export function findObjectByTitle(savedObjectsClient, type, title) {
|
||||
if (!title) return Promise.resolve();
|
||||
|
||||
// Elastic search will return the most relevant results first, which means exact matches should come
|
||||
// first, and so we shouldn't need to request everything. Using 10 just to be on the safe side.
|
||||
return savedObjectsClient.find({
|
||||
type,
|
||||
perPage: 10,
|
||||
search: `"${title}"`,
|
||||
searchFields: 'title',
|
||||
fields: ['title']
|
||||
}).then(response => {
|
||||
const match = find(response.savedObjects, (obj) => {
|
||||
return obj.get('title').toLowerCase() === title.toLowerCase();
|
||||
});
|
||||
|
||||
return match;
|
||||
});
|
||||
}
|
|
@ -2,3 +2,4 @@ export { SavedObjectsClient } from './saved_objects_client';
|
|||
export { SavedObjectRegistryProvider } from './saved_object_registry';
|
||||
export { SavedObjectsClientProvider } from './saved_objects_client_provider';
|
||||
export { SavedObject } from './saved_object';
|
||||
export { findObjectByTitle } from './find_object_by_title';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import marked from 'marked';
|
||||
import { NoResults } from 'ui/errors';
|
||||
import { Binder } from 'ui/binder';
|
||||
import { VislibLibLayoutLayoutProvider } from './layout/layout';
|
||||
|
@ -203,7 +204,7 @@ export function VisHandlerProvider(Private) {
|
|||
|
||||
div.append('div').attr('class', 'item bottom');
|
||||
} else {
|
||||
div.append('h4').text(message);
|
||||
div.append('h4').text(marked.inlineLexer(message, []));
|
||||
}
|
||||
|
||||
$(this.el).trigger('renderComplete');
|
||||
|
|
|
@ -21,8 +21,11 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
|
||||
describe('index pattern creation', function indexPatternCreation() {
|
||||
let indexPatternId;
|
||||
|
||||
before(function () {
|
||||
return PageObjects.settings.createIndexPattern();
|
||||
return PageObjects.settings.createIndexPattern()
|
||||
.then(id => indexPatternId = id);
|
||||
});
|
||||
|
||||
it('should have index pattern in page header', function () {
|
||||
|
@ -37,7 +40,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
return retry.try(function tryingForTime() {
|
||||
return remote.getCurrentUrl()
|
||||
.then(function (currentUrl) {
|
||||
expect(currentUrl).to.contain('logstash-*');
|
||||
expect(currentUrl).to.contain(indexPatternId);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -295,6 +295,17 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
|
|||
log.debug('Index pattern created: ' + currentUrl);
|
||||
}
|
||||
});
|
||||
|
||||
return await this.getIndexPatternIdFromUrl();
|
||||
}
|
||||
|
||||
async getIndexPatternIdFromUrl() {
|
||||
const currentUrl = await remote.getCurrentUrl();
|
||||
const indexPatternId = currentUrl.match(/.*\/(.*)/)[1];
|
||||
|
||||
log.debug('index pattern ID: ', indexPatternId);
|
||||
|
||||
return indexPatternId;
|
||||
}
|
||||
|
||||
async setIndexPatternField(pattern) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue