Refactor Index Pattern Creation logic. (#11210)

* Reorder Create Index Pattern logic so local functions are before scope methods.
* Extract some Index Pattern Creation logic into sendCreateIndexPatternRequest and getDefaultPatternForInterval services.
* Use UI Framework typography components for Index Pattern Creation header.
* Separate user-specified variables for configuring the index pattern from controller variables, which just change the UI state.
* Rename index to newIndexPattern for clarity.
* Use controllerAs and fat arrow functions in Create Index Pattern.
This commit is contained in:
CJ Cenizal 2017-04-18 14:30:57 -07:00 committed by GitHub
parent 345b64924d
commit a605b7f478
7 changed files with 402 additions and 356 deletions

View file

@ -1,67 +1,80 @@
<kbn-management-app section="kibana">
<kbn-management-indices>
<div ng-controller="managementIndicesCreate" class="kbn-management-indices-create">
<div class="page-header">
<h1 translate="KIBANA-CONFIGURE_INDEX_PATTERN"></h1>
<p translate="KIBANA-MUST_CONFIGURE_INDEX_PATTERN"></p>
</div>
<div>
<form name="form" role="form" class="well" ng-submit="createIndexPattern()">
<div
ng-controller="managementIndicesCreate as controller"
class="kuiViewContent"
>
<h1
class="kuiTitle kuiVerticalRhythm"
translate="KIBANA-CONFIGURE_INDEX_PATTERN"
></h1>
<p
class="kuiText kuiVerticalRhythm"
translate="KIBANA-MUST_CONFIGURE_INDEX_PATTERN"
></p>
<div class="kuiVerticalRhythm">
<form name="form" role="form" class="well" ng-submit="controller.createIndexPattern()">
<div class="form-group">
<label translate="KIBANA-INDEX_NAME_OR_PATTERN"></label>
<p class="help-block" ng-if="!index.nameIsPattern" translate="KIBANA-WILDCARD_DYNAMIC_INDEX_PATTERNS"></p>
<p class="help-block" ng-if="index.isTimeBased && index.nameIsPattern"><span translate="KIBANA-STATIC_TEXT_IN_DYNAMIC_INDEX_PATTERNS"></span> &mdash;
<p class="help-block" ng-if="!controller.newIndexPattern.nameIsPattern" translate="KIBANA-WILDCARD_DYNAMIC_INDEX_PATTERNS"></p>
<p class="help-block" ng-if="controller.newIndexPattern.isTimeBased && controller.newIndexPattern.nameIsPattern"><span translate="KIBANA-STATIC_TEXT_IN_DYNAMIC_INDEX_PATTERNS"></span> &mdash;
<small><a target="_blank" href="http://momentjs.com/docs/#/displaying/format/" translate="KIBANA-DATE_FORMAT_DOCS"></a></small></p>
<input
ng-model="index.name"
ng-attr-placeholder="{{index.defaultName}}"
data-test-subj="createIndexPatternNameInput"
ng-model="controller.newIndexPattern.name"
ng-attr-placeholder="{{controller.newIndexPattern.defaultName}}"
ng-model-options="{ updateOn: 'default blur', debounce: {'default': 2500, 'blur': 0} }"
validate-index-name
allow-wildcard
name="name"
required
type="text"
class="form-control">
<small ng-show="index.nameInterval.name == 'weeks'">
<strong translate="KIBANA-NOTE_COLON"></strong>&nbsp;
<span translate="KIBANA-WEEKLY_ISO_NOTICE"></span>
<span translate="KIBANA-SEE"></span>
<a href="https://en.wikipedia.org/wiki/ISO_week_date" target="_blank" title="Wikipedia: ISO Week Date" translate="KIBANA-WIKI_ISO_WEEK_DATE"></a>
</small>
</div>
class="form-control"
>
<small ng-show="controller.newIndexPattern.nameInterval.name == 'weeks'">
<strong translate="KIBANA-NOTE_COLON"></strong>&nbsp;
<span translate="KIBANA-WEEKLY_ISO_NOTICE"></span>
<span translate="KIBANA-SEE"></span>
<a href="https://en.wikipedia.org/wiki/ISO_week_date" target="_blank" title="Wikipedia: ISO Week Date" translate="KIBANA-WIKI_ISO_WEEK_DATE"></a>
</small>
</div>
<div class="form-group time-and-pattern">
<label>
<input
ng-model="index.isTimeBased"
data-test-subj="createIndexPatternIsTimeBasedCheckBox"
ng-model="controller.newIndexPattern.isTimeBased"
type="checkbox">
<span translate="KIBANA-CONTAINS_TIME_BASED_EVENTS"></span>
</label>
</div>
<div class="form-group" ng-if="index.isTimeBased">
<div class="form-group" ng-if="controller.newIndexPattern.isTimeBased">
<label>
<span translate="KIBANA-TIME_FIELD_NAME"></span>
&nbsp;
<kbn-info info="{{ 'KIBANA-FIELD_FILTER_EVENTS_GLOBAL_TIME' | translate }}"></kbn-info>
&nbsp;
<small>
<a ng-click="refreshFieldList();" translate="KIBANA-REFRESH_FIELDS"></a>
<a ng-click="controller.refreshFieldList();" translate="KIBANA-REFRESH_FIELDS"></a>
</small>
</label>
<select
ng-disabled="index.fetchFieldsError"
ng-required="!index.fetchFieldsError"
ng-options="field.name for field in index.dateFields"
ng-model="index.timeField"
auto-select-if-only-one="index.dateFields"
class="form-control">
</select>
data-test-subj="createIndexPatternTimeFieldSelect"
ng-disabled="controller.fetchFieldsError"
ng-required="!controller.fetchFieldsError"
ng-options="field.name for field in controller.dateFields"
ng-model="controller.newIndexPattern.timeField"
auto-select-if-only-one="controller.dateFields"
class="form-control"
></select>
</div>
<div class="form-group" ng-if="canExpandIndices()">
<div class="form-group" ng-if="controller.canExpandIndices()">
<label>
<input ng-model="index.expandable" type="checkbox">
<input ng-model="controller.newIndexPattern.expandable" type="checkbox">
<span translate="KIBANA-EXPAND_INDEX_PATTERN"></span>
</label>
@ -79,14 +92,18 @@
</div>
<div class="form-group time-and-pattern">
<label ng-if="index.isTimeBased">
<input ng-model="index.nameIsPattern" type="checkbox">
<label ng-if="controller.newIndexPattern.isTimeBased">
<input
data-test-subj="createIndexPatternNameIsPatternCheckBox"
ng-model="controller.newIndexPattern.nameIsPattern"
type="checkbox"
>
<span translate="KIBANA-INDEX_NAME_CREATED_BY_EVENT_TIMES"></span>
<small translate="KIBANA-DEPRECATED"></small>
</label>
</div>
<div class="form-group" ng-if="index.isTimeBased && index.nameIsPattern">
<div class="form-group" ng-if="controller.newIndexPattern.isTimeBased && controller.newIndexPattern.nameIsPattern">
<div class="alert alert-warning">
</p>
<h4 translate="KIBANA-ALERT_INDEX_PATTERN_DEPRECATED"></h4>
@ -102,49 +119,49 @@
</label>
<select
required
ng-options="opt.display for opt in index.nameIntervalOptions"
ng-model="index.nameInterval"
ng-options="opt.display for opt in controller.nameIntervalOptions"
ng-model="controller.newIndexPattern.nameInterval"
class="form-control">
</select>
</div>
<section>
<div class="alert alert-danger" ng-repeat="err in index.patternErrors">
<div class="alert alert-danger" ng-repeat="err in controller.patternErrors">
{{err}}
</div>
<div class="alert alert-info" ng-if="index.samples">
<div class="alert alert-info" ng-if="controller.samples">
<span translate="KIBANA-SAMPLE_ALERT"></span>
<ul>
<li ng-repeat="sample in index.samples">{{sample}}</li>
<li ng-repeat="sample in controller.samples">{{sample}}</li>
</ul>
<button type="button" ng-click="moreSamples(true)" class="btn btn-default">
<button type="button" ng-click="controller.moreSamples(true)" class="btn btn-default">
<span translate="KIBANA-EXPAND_SEARCH"></span>
</button>
</div>
<div class="alert alert-{{index.existing.class}}" ng-if="index.existing">
<span translate="KIBANA-EXISTING_MATCH_PERCENT" translate-values="{ indexExistingMatchPercent: '{{index.existing.matchPercent}}' }"></span>
<div class="alert alert-{{controller.existing.class}}" ng-if="controller.existing">
<span translate="KIBANA-EXISTING_MATCH_PERCENT" translate-values="{ indexExistingMatchPercent: '{{controller.existing.matchPercent}}' }"></span>
<ul>
<li ng-repeat="match in index.existing.matches | orderBy:'toString()'| limitTo: index.sampleCount">{{match}}</li>
<li ng-repeat="match in controller.existing.matches | orderBy:'toString()'| limitTo: controller.sampleCount">{{match}}</li>
</ul>
<button
ng-if="index.sampleCount < index.existing.matches.length"
ng-click="moreSamples()"
ng-if="controller.sampleCount < controller.existing.matches.length"
ng-click="controller.moreSamples()"
type="button"
class="btn btn-default">
<span translate="KIBANA-EXPAND_SEARCH"></span>
</button>
</div>
<div class="alert alert-info" ng-if="index.existing.failures.length">
<div class="alert alert-info" ng-if="controller.existing.failures.length">
<span translate="KIBANA-NON_MATCHING_INDICES_AND_ALIASES"></span>
<ul>
<li ng-repeat="match in index.existing.failures | limitTo: index.sampleCount">{{match}}</li>
<li ng-repeat="match in controller.existing.failures | limitTo: controller.sampleCount">{{match}}</li>
</ul>
<a
ng-if="index.sampleCount < index.existing.matches.length"
ng-click="moreSamples()"
ng-if="controller.sampleCount < controller.existing.matches.length"
ng-click="controller.moreSamples()"
class="alert-link">
<span translate="KIBANA-MORE"></span>
</a>
@ -152,13 +169,14 @@
</section>
<button
data-test-subj="submitCreateIndexPatternFromExistingForm"
ng-disabled="form.$invalid || index.fetchFieldsError"
ng-class="index.fetchFieldsError ? 'btn-default' : 'btn-success'"
data-test-subj="createIndexPatternCreateButton"
ng-disabled="form.$invalid || controller.fetchFieldsError"
ng-class="controller.fetchFieldsError ? 'btn-default' : 'btn-success'"
type="submit"
class="btn">
<span ng-hide="form.name.$error.indexNameInput" ng-if="index.fetchFieldsError">{{index.fetchFieldsError}}</span>
<span ng-hide="form.name.$error.indexNameInput" ng-if="!index.fetchFieldsError" translate="KIBANA-CREATE"></span>
class="btn"
>
<span ng-hide="form.name.$error.indexNameInput" ng-if="controller.fetchFieldsError">{{controller.fetchFieldsError}}</span>
<span ng-hide="form.name.$error.indexNameInput" ng-if="!controller.fetchFieldsError" translate="KIBANA-CREATE"></span>
<span ng-show="form.name.$error.indexNameInput" translate="KIBANA-INVALID_INDEX_PATTERN"></span>
</button>
</form>

View file

@ -6,6 +6,8 @@ import { RefreshKibanaIndex } from '../refresh_kibana_index';
import uiRoutes from 'ui/routes';
import uiModules from 'ui/modules';
import template from './create_index_pattern.html';
import { getDefaultPatternForInterval } from './get_default_pattern_for_interval';
import { sendCreateIndexPatternRequest } from './send_create_index_pattern_request';
uiRoutes
.when('/management/kibana/index', {
@ -19,217 +21,50 @@ uiModules.get('apps/management')
const intervals = indexPatterns.intervals;
let samplePromise;
// this and child scopes will write pattern vars here
const index = $scope.index = {
// Configure the new index pattern we're going to create.
this.newIndexPattern = {
name: config.get('indexPattern:placeholder'),
isTimeBased: true,
nameIsPattern: false,
expandable: false,
sampleCount: 5,
nameIntervalOptions: intervals,
fetchFieldsError: $translate.instant('KIBANA-LOADING')
nameInterval: _.find(intervals, { name: 'daily' }),
timeField: null,
};
index.nameInterval = _.find(index.nameIntervalOptions, { name: 'daily' });
index.timeField = null;
// UI state.
this.dateFields = null;
this.sampleCount = 5;
this.samples = null;
this.existing = null;
this.nameIntervalOptions = intervals;
this.patternErrors = [];
this.fetchFieldsError = $translate.instant('KIBANA-LOADING');
$scope.canExpandIndices = function () {
// to maximize performance in the digest cycle, move from the least
// expensive operation to most
return index.isTimeBased && !index.nameIsPattern && _.includes(index.name, '*');
};
$scope.refreshFieldList = function () {
const timeField = index.timeField;
fetchFieldList().then(function (results) {
if (timeField) {
updateFieldListAndSetTimeField(results, timeField.name);
} else {
updateFieldList(results);
}
});
};
$scope.createIndexPattern = function () {
// get an empty indexPattern to start
indexPatterns.get()
.then(function (indexPattern) {
// set both the id and title to the index index
indexPattern.id = indexPattern.title = index.name;
if (index.isTimeBased) {
indexPattern.timeFieldName = index.timeField.name;
if (index.nameIsPattern) {
indexPattern.intervalName = index.nameInterval.name;
}
}
if (!index.expandable && $scope.canExpandIndices()) {
indexPattern.notExpandable = true;
}
// fetch the fields
return indexPattern.create()
.then(function (id) {
if (id) {
refreshKibanaIndex().then(function () {
if (!config.get('defaultIndex')) {
config.set('defaultIndex', indexPattern.id);
}
indexPatterns.cache.clear(indexPattern.id);
kbnUrl.change('/management/kibana/indices/' + indexPattern.id);
});
}
});
// refreshFields calls save() after a successfull fetch, no need to save again
// .then(function () { indexPattern.save(); })
})
.catch(function (err) {
if (err instanceof IndexPatternMissingIndices) {
notify.error($translate.instant('KIBANA-NO_INDICES_MATCHING_PATTERN'));
}
else notify.fatal(err);
});
};
$scope.$watchMulti([
'index.isTimeBased',
'index.nameIsPattern',
'index.nameInterval.name'
], function (newVal, oldVal) {
const isTimeBased = newVal[0];
const nameIsPattern = newVal[1];
const newDefault = getPatternDefault(newVal[2]);
const oldDefault = getPatternDefault(oldVal[2]);
if (index.name === oldDefault) {
index.name = newDefault;
}
if (!isTimeBased) {
index.nameIsPattern = false;
}
if (!nameIsPattern) {
delete index.nameInterval;
delete index.timeField;
} else {
index.nameInterval = index.nameInterval || intervals.byName.days;
index.name = index.name || getPatternDefault(index.nameInterval);
}
});
$scope.moreSamples = function (andUpdate) {
index.sampleCount += 5;
if (andUpdate) updateSamples();
};
$scope.$watchMulti([
'index.name',
'index.nameInterval'
], function (newVal, oldVal) {
let lastPromise;
resetIndex();
samplePromise = lastPromise = updateSamples()
.then(function () {
promiseMatch(lastPromise, function () {
index.samples = null;
index.patternErrors = [];
});
})
.catch(function (errors) {
promiseMatch(lastPromise, function () {
index.existing = null;
index.patternErrors = errors;
});
})
.finally(function () {
// prevent running when no change happened (ie, first watcher call)
if (!_.isEqual(newVal, oldVal)) {
fetchFieldList().then(function (results) {
if (lastPromise === samplePromise) {
updateFieldList(results);
samplePromise = null;
}
});
}
});
});
$scope.$watchMulti([
'index.isTimeBased',
'index.sampleCount'
], $scope.refreshFieldList);
function updateSamples() {
const patternErrors = [];
if (!index.nameInterval || !index.name) {
return Promise.resolve();
}
const pattern = mockIndexPattern(index);
return indexPatterns.mapper.getIndicesForIndexPattern(pattern)
.catch(function (err) {
if (err instanceof IndexPatternMissingIndices) return;
notify.error(err);
})
.then(function (existing) {
const all = _.get(existing, 'all', []);
const matches = _.get(existing, 'matches', []);
if (all.length) {
index.existing = {
class: 'success',
all: all,
matches: matches,
matchPercent: Math.round((matches.length / all.length) * 100) + '%',
failures: _.difference(all, matches)
};
return;
}
patternErrors.push($translate.instant('KIBANA-PATTERN_DOES_NOT_MATCH_EXIST_INDICES'));
const radius = Math.round(index.sampleCount / 2);
const samples = intervals.toIndexList(index.name, index.nameInterval, -radius, radius);
if (_.uniq(samples).length !== samples.length) {
patternErrors.push($translate.instant('KIBANA-INVALID_NON_UNIQUE_INDEX_NAME_CREATED'));
} else {
index.samples = samples;
}
throw patternErrors;
});
}
function fetchFieldList() {
index.dateFields = index.timeField = index.listUsed = null;
const useIndexList = index.isTimeBased && index.nameIsPattern;
const fetchFieldList = () => {
this.dateFields = this.newIndexPattern.timeField = null;
const useIndexList = this.newIndexPattern.isTimeBased && this.newIndexPattern.nameIsPattern;
let fetchFieldsError;
let dateFields;
// we don't have enough info to continue
if (!index.name) {
if (!this.newIndexPattern.name) {
fetchFieldsError = $translate.instant('KIBANA-SET_INDEX_NAME_FIRST');
return;
}
if (useIndexList && !index.nameInterval) {
if (useIndexList && !this.newIndexPattern.nameInterval) {
fetchFieldsError = $translate.instant('KIBANA-INTERVAL_INDICES_POPULATED');
return;
}
return indexPatterns.mapper.clearCache(index.name)
.then(function () {
const pattern = mockIndexPattern(index);
return indexPatterns.mapper.clearCache(this.newIndexPattern.name)
.then(() => {
const pattern = mockIndexPattern(this.newIndexPattern);
return indexPatterns.mapper.getFieldsForIndexPattern(pattern, {
skipIndexPatternCache: true,
})
.catch(function (err) {
.catch((err) => {
// TODO: we should probably display a message of some kind
if (err instanceof IndexPatternMissingIndices) {
fetchFieldsError = $translate.instant('KIBANA-INDICES_MATCH_PATTERN');
@ -239,22 +74,25 @@ uiModules.get('apps/management')
throw err;
});
})
.then(function (fields) {
.then(fields => {
if (fields.length > 0) {
fetchFieldsError = null;
dateFields = fields.filter(function (field) {
return field.type === 'date';
});
dateFields = fields.filter(field => field.type === 'date');
}
return {
fetchFieldsError: fetchFieldsError,
dateFields: dateFields
fetchFieldsError,
dateFields,
};
}, notify.fatal);
}
};
function updateFieldListAndSetTimeField(results, timeFieldName) {
const updateFieldList = results => {
this.fetchFieldsError = results.fetchFieldsError;
this.dateFields = results.dateFields;
};
const updateFieldListAndSetTimeField = (results, timeFieldName) => {
updateFieldList(results);
if (!results.dateFields.length) {
@ -267,46 +105,15 @@ uiModules.get('apps/management')
//assign the field from the results-list
//angular recreates a new timefield instance, each time the list is refreshed.
//This ensures the selected field matches one of the instances in the list.
index.timeField = matchingTimeField ? matchingTimeField : defaultTimeField;
}
this.newIndexPattern.timeField = matchingTimeField ? matchingTimeField : defaultTimeField;
};
function updateFieldList(results) {
index.fetchFieldsError = results.fetchFieldsError;
index.dateFields = results.dateFields;
}
function promiseMatch(lastPromise, cb) {
if (lastPromise === samplePromise) {
cb();
} else if (samplePromise != null) {
// haven't hit the last promise yet, reset index params
resetIndex();
}
}
function resetIndex() {
index.patternErrors = [];
index.samples = null;
index.existing = null;
index.fetchFieldsError = $translate.instant('KIBANA-LOADING');
}
function getPatternDefault(interval) {
switch (interval) {
case 'hours':
return '[logstash-]YYYY.MM.DD.HH';
case 'days':
return '[logstash-]YYYY.MM.DD';
case 'weeks':
return '[logstash-]GGGG.WW';
case 'months':
return '[logstash-]YYYY.MM';
case 'years':
return '[logstash-]YYYY';
default:
return 'logstash-*';
}
}
const resetIndex = () => {
this.patternErrors = [];
this.samples = null;
this.existing = null;
this.fetchFieldsError = $translate.instant('KIBANA-LOADING');
};
function mockIndexPattern(index) {
// trick the mapper into thinking this is an indexPattern
@ -315,4 +122,188 @@ uiModules.get('apps/management')
intervalName: index.nameInterval
};
}
const updateSamples = () => {
const patternErrors = [];
if (!this.newIndexPattern.nameInterval || !this.newIndexPattern.name) {
return Promise.resolve();
}
const pattern = mockIndexPattern(this.newIndexPattern);
return indexPatterns.mapper.getIndicesForIndexPattern(pattern)
.catch(err => {
if (err instanceof IndexPatternMissingIndices) return;
notify.error(err);
})
.then(existing => {
const all = _.get(existing, 'all', []);
const matches = _.get(existing, 'matches', []);
if (all.length) {
return this.existing = {
class: 'success',
all,
matches,
matchPercent: Math.round((matches.length / all.length) * 100) + '%',
failures: _.difference(all, matches)
};
}
patternErrors.push($translate.instant('KIBANA-PATTERN_DOES_NOT_MATCH_EXIST_INDICES'));
const radius = Math.round(this.sampleCount / 2);
const samples = intervals.toIndexList(this.newIndexPattern.name, this.newIndexPattern.nameInterval, -radius, radius);
if (_.uniq(samples).length !== samples.length) {
patternErrors.push($translate.instant('KIBANA-INVALID_NON_UNIQUE_INDEX_NAME_CREATED'));
} else {
this.samples = samples;
}
throw patternErrors;
});
};
this.canExpandIndices = () => {
// to maximize performance in the digest cycle, move from the least
// expensive operation to most
return this.newIndexPattern.isTimeBased && !this.newIndexPattern.nameIsPattern && _.includes(this.newIndexPattern.name, '*');
};
this.refreshFieldList = () => {
const timeField = this.newIndexPattern.timeField;
fetchFieldList().then(results => {
if (timeField) {
updateFieldListAndSetTimeField(results, timeField.name);
} else {
updateFieldList(results);
}
});
};
this.createIndexPattern = () => {
const id = this.newIndexPattern.name;
const timeFieldName =
this.newIndexPattern.isTimeBased
? this.newIndexPattern.timeField.name
: undefined;
// Only event-time-based index patterns set an intervalName.
const intervalName =
this.newIndexPattern.isTimeBased && this.newIndexPattern.nameIsPattern
? this.newIndexPattern.nameInterval.name
: undefined;
const notExpandable =
!this.newIndexPattern.expandable && this.canExpandIndices()
? true
: undefined;
sendCreateIndexPatternRequest(indexPatterns, {
id,
timeFieldName,
intervalName,
notExpandable,
}).then(createdId => {
if (!createdId) {
return;
}
refreshKibanaIndex().then(() => {
if (!config.get('defaultIndex')) {
config.set('defaultIndex', id);
}
indexPatterns.cache.clear(id);
kbnUrl.change(`/management/kibana/indices/${id}`);
});
}).catch(err => {
if (err instanceof IndexPatternMissingIndices) {
return notify.error($translate.instant('KIBANA-NO_INDICES_MATCHING_PATTERN'));
}
notify.fatal(err);
});
};
$scope.$watchMulti([
'controller.newIndexPattern.isTimeBased',
'controller.newIndexPattern.nameIsPattern',
'controller.newIndexPattern.nameInterval.name'
], (newVal, oldVal) => {
const isTimeBased = newVal[0];
const nameIsPattern = newVal[1];
const newDefault = getDefaultPatternForInterval(newVal[2]);
const oldDefault = getDefaultPatternForInterval(oldVal[2]);
if (this.newIndexPattern.name === oldDefault) {
this.newIndexPattern.name = newDefault;
}
if (!isTimeBased) {
this.newIndexPattern.nameIsPattern = false;
}
if (!nameIsPattern) {
delete this.newIndexPattern.nameInterval;
delete this.newIndexPattern.timeField;
} else {
this.newIndexPattern.nameInterval = this.newIndexPattern.nameInterval || intervals.byName.days;
this.newIndexPattern.name = this.newIndexPattern.name || getDefaultPatternForInterval(this.newIndexPattern.nameInterval);
}
});
this.moreSamples = andUpdate => {
this.sampleCount += 5;
if (andUpdate) updateSamples();
};
$scope.$watchMulti([
'controller.newIndexPattern.name',
'controller.newIndexPattern.nameInterval'
], (newVal, oldVal) => {
function promiseMatch(lastPromise, cb) {
if (lastPromise === samplePromise) {
cb();
} else if (samplePromise != null) {
// haven't hit the last promise yet, reset index params
resetIndex();
}
}
let lastPromise;
resetIndex();
samplePromise = lastPromise = updateSamples()
.then(() => {
promiseMatch(lastPromise, () => {
this.samples = null;
this.patternErrors = [];
});
})
.catch(errors => {
promiseMatch(lastPromise, () => {
this.existing = null;
this.patternErrors = errors;
});
})
.finally(() => {
// prevent running when no change happened (ie, first watcher call)
if (!_.isEqual(newVal, oldVal)) {
fetchFieldList().then(results => {
if (lastPromise === samplePromise) {
updateFieldList(results);
samplePromise = null;
}
});
}
});
});
$scope.$watchMulti([
'controller.newIndexPattern.isTimeBased',
'controller.sampleCount'
], () => {
this.refreshFieldList();
});
});

View file

@ -0,0 +1,17 @@
const intervalToDefaultPatternMap = {
hours: '[logstash-]YYYY.MM.DD.HH',
days: '[logstash-]YYYY.MM.DD',
weeks: '[logstash-]GGGG.WW',
months: '[logstash-]YYYY.MM',
years: '[logstash-]YYYY',
};
export function getDefaultPatternForInterval(interval) {
const defaultPattern = intervalToDefaultPatternMap[interval];
if (defaultPattern) {
return defaultPattern;
}
return 'logstash-*';
}

View file

@ -0,0 +1,22 @@
export function sendCreateIndexPatternRequest(indexPatterns, {
id,
timeFieldName,
intervalName,
notExpandable,
}) {
// 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, {
timeFieldName,
intervalName,
notExpandable,
});
// fetch the fields
return indexPattern.create();
});
}

View file

@ -25,8 +25,7 @@ export default function ({ getService, getPageObjects }) {
})
// try to find the checkbox (this shouldn fail)
.then(function () {
const waitTime = 10000;
return PageObjects.settings.getTimeBasedIndexPatternCheckbox(waitTime);
return PageObjects.settings.getTimeBasedIndexPatternCheckbox();
})
.then(function () {
PageObjects.common.saveScreenshot('Settings-indices-hide-time-based-index-pattern');

View file

@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }) {
});
it('should not be enable creation', function () {
return PageObjects.settings.getCreateButton().isEnabled()
return PageObjects.settings.getCreateIndexPatternButton().isEnabled()
.then(function (enabled) {
expect(enabled).to.not.be.ok();
});

View file

@ -32,18 +32,18 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
getAdvancedSettings(propertyName) {
log.debug('in setAdvancedSettings');
return testSubjects.find('advancedSetting-' + propertyName + '-currentValue')
return testSubjects.find(`advancedSetting-${propertyName}-currentValue`)
.getVisibleText();
}
async setAdvancedSettings(propertyName, propertyValue) {
await testSubjects.click('advancedSetting-' + propertyName + '-editButton');
await testSubjects.click(`advancedSetting-${propertyName}-editButton`);
await PageObjects.header.waitUntilLoadingHasFinished();
await PageObjects.common.sleep(1000);
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('option[label="' + propertyValue + '"]').click();
.findByCssSelector(`option[label="${propertyValue}"]`).click();
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.click('advancedSetting-' + propertyName + '-saveButton');
await testSubjects.click(`advancedSetting-${propertyName}-saveButton`);
await PageObjects.header.waitUntilLoadingHasFinished();
}
@ -52,25 +52,20 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
}
getTimeBasedEventsCheckbox() {
return remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model="index.isTimeBased"]');
return testSubjects.find('createIndexPatternIsTimeBasedCheckBox');
}
getTimeBasedIndexPatternCheckbox(timeout) {
timeout = timeout || defaultFindTimeout;
getTimeBasedIndexPatternCheckbox() {
// fail faster since we're sometimes checking that it doesn't exist
return remote.setFindTimeout(timeout)
.findByCssSelector('input[ng-model="index.nameIsPattern"]');
return testSubjects.find('createIndexPatternNameIsPatternCheckBox');
}
getIndexPatternField() {
return remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('[ng-model="index.name"]');
return testSubjects.find('createIndexPatternNameInput');
}
getTimeFieldNameField() {
return remote.setFindTimeout(defaultFindTimeout)
.findDisplayedByCssSelector('select[ng-model="index.timeField"]');
return testSubjects.find('createIndexPatternTimeFieldSelect');
}
async selectTimeFieldOption(selection) {
@ -91,6 +86,10 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
.findDisplayedByCssSelector('option[label="' + selection + '"]');
}
getCreateIndexPatternButton() {
return testSubjects.find('createIndexPatternCreateButton');
}
getCreateButton() {
return remote.setFindTimeout(defaultFindTimeout)
.findDisplayedByCssSelector('[type="submit"]');
@ -111,12 +110,12 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
getConfigureHeader() {
return remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('h1');
.findByCssSelector('h1');
}
getTableHeader() {
return remote.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('table.table.table-condensed thead tr th');
.findAllByCssSelector('table.table.table-condensed thead tr th');
}
sortBy(columnName) {
@ -142,11 +141,11 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
getTableRow(rowNumber, colNumber) {
return remote.setFindTimeout(defaultFindTimeout)
// passing in zero-based index, but adding 1 for css 1-based indexes
.findByCssSelector('div.agg-table-paginated table.table.table-condensed tbody tr:nth-child(' +
(rowNumber + 1) + ') td.ng-scope:nth-child(' +
(colNumber + 1) + ') span.ng-binding'
);
// passing in zero-based index, but adding 1 for css 1-based indexes
.findByCssSelector('div.agg-table-paginated table.table.table-condensed tbody tr:nth-child(' +
(rowNumber + 1) + ') td.ng-scope:nth-child(' +
(colNumber + 1) + ') span.ng-binding'
);
}
getFieldsTabCount() {
@ -172,27 +171,27 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
getPageSize() {
let selectedItemLabel = '';
return remote.setFindTimeout(defaultFindTimeout)
.findAllByCssSelector('select.ng-pristine.ng-valid.ng-untouched option')
.then(function (chartTypes) {
function getChartType(chart) {
const thisChart = chart;
return chart.isSelected()
.then(function (isSelected) {
if (isSelected === true) {
return thisChart.getProperty('label')
.then(function (theLabel) {
selectedItemLabel = theLabel;
});
}
});
}
.findAllByCssSelector('select.ng-pristine.ng-valid.ng-untouched option')
.then(function (chartTypes) {
function getChartType(chart) {
const thisChart = chart;
return chart.isSelected()
.then(function (isSelected) {
if (isSelected === true) {
return thisChart.getProperty('label')
.then(function (theLabel) {
selectedItemLabel = theLabel;
});
}
});
}
const getChartTypesPromises = chartTypes.map(getChartType);
return Promise.all(getChartTypesPromises);
})
.then(() => {
return selectedItemLabel;
});
const getChartTypesPromises = chartTypes.map(getChartType);
return Promise.all(getChartTypesPromises);
})
.then(() => {
return selectedItemLabel;
});
}
async getFieldNames() {
@ -218,68 +217,68 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
async setFieldTypeFilter(type) {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]')
.click();
.findByCssSelector('select[data-test-subj="indexedFieldTypeFilterDropdown"] > option[label="' + type + '"]')
.click();
}
async setScriptedFieldLanguageFilter(language) {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' + language + '"]')
.click();
.findByCssSelector('select[data-test-subj="scriptedFieldLanguageFilterDropdown"] > option[label="' + language + '"]')
.click();
}
async goToPage(pageNum) {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('ul.pagination-other-pages-list.pagination-sm.ng-scope li.ng-scope:nth-child(' +
(pageNum + 1) + ') a.ng-binding')
.click();
.findByCssSelector('ul.pagination-other-pages-list.pagination-sm.ng-scope li.ng-scope:nth-child(' +
(pageNum + 1) + ') a.ng-binding')
.click();
await PageObjects.header.waitUntilLoadingHasFinished();
}
async openControlsRow(row) {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('table.table.table-condensed tbody tr:nth-child(' +
(row + 1) + ') td.ng-scope div.actions a.btn.btn-xs.btn-default i.fa.fa-pencil')
.click();
.findByCssSelector('table.table.table-condensed tbody tr:nth-child(' +
(row + 1) + ') td.ng-scope div.actions a.btn.btn-xs.btn-default i.fa.fa-pencil')
.click();
}
async openControlsByName(name) {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('div.actions a.btn.btn-xs.btn-default[href$="/' + name + '"]')
.click();
.findByCssSelector('div.actions a.btn.btn-xs.btn-default[href$="/' + name + '"]')
.click();
}
async increasePopularity() {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('button.btn.btn-default[aria-label="Plus"]')
.click();
.findByCssSelector('button.btn.btn-default[aria-label="Plus"]')
.click();
await PageObjects.header.waitUntilLoadingHasFinished();
}
getPopularity() {
return remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('input[ng-model="editor.field.count"]')
.getProperty('value');
.findByCssSelector('input[ng-model="editor.field.count"]')
.getProperty('value');
}
async controlChangeCancel() {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('button.btn.btn-primary[aria-label="Cancel"]')
.click();
.findByCssSelector('button.btn.btn-primary[aria-label="Cancel"]')
.click();
await PageObjects.header.waitUntilLoadingHasFinished();
}
async controlChangeSave() {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector('button.btn.btn-success.ng-binding[aria-label="Update Field"]')
.click();
.findByCssSelector('button.btn.btn-success.ng-binding[aria-label="Update Field"]')
.click();
await PageObjects.header.waitUntilLoadingHasFinished();
}
async setPageSize(size) {
await remote.setFindTimeout(defaultFindTimeout)
.findByCssSelector(`[data-test-subj="paginateControlsPageSizeSelect"] option[label="${size}"]`)
.click();
.findByCssSelector(`[data-test-subj="paginateControlsPageSizeSelect"] option[label="${size}"]`)
.click();
await PageObjects.header.waitUntilLoadingHasFinished();
}