[indexPatterns] remove support for time patterns

This commit is contained in:
spalger 2017-06-07 15:41:11 -07:00
parent e2a5b27d5a
commit 4263e37c66
40 changed files with 171 additions and 1599 deletions

View file

@ -46,65 +46,6 @@ button.
NOTE: When you define an index pattern, indices that match that pattern must exist in Elasticsearch. Those indices must
contain data.
To use an event time in an index name, enclose the static text in the pattern and specify the date format using the
tokens described in the following table.
For example, `[logstash-]YYYY.MM.DD` matches all indices whose names have a timestamp of the form `YYYY.MM.DD` appended
to the prefix `logstash-`, such as `logstash-2015.01.31` and `logstash-2015-02-01`.
[float]
[[date-format-tokens]]
.Date Format Tokens
[horizontal]
`M`:: Month - cardinal: 1 2 3 ... 12
`Mo`:: Month - ordinal: 1st 2nd 3rd ... 12th
`MM`:: Month - two digit: 01 02 03 ... 12
`MMM`:: Month - abbreviation: Jan Feb Mar ... Dec
`MMMM`:: Month - full: January February March ... December
`Q`:: Quarter: 1 2 3 4
`D`:: Day of Month - cardinal: 1 2 3 ... 31
`Do`:: Day of Month - ordinal: 1st 2nd 3rd ... 31st
`DD`:: Day of Month - two digit: 01 02 03 ... 31
`DDD`:: Day of Year - cardinal: 1 2 3 ... 365
`DDDo`:: Day of Year - ordinal: 1st 2nd 3rd ... 365th
`DDDD`:: Day of Year - three digit: 001 002 ... 364 365
`d`:: Day of Week - cardinal: 0 1 3 ... 6
`do`:: Day of Week - ordinal: 0th 1st 2nd ... 6th
`dd`:: Day of Week - 2-letter abbreviation: Su Mo Tu ... Sa
`ddd`:: Day of Week - 3-letter abbreviation: Sun Mon Tue ... Sat
`dddd`:: Day of Week - full: Sunday Monday Tuesday ... Saturday
`e`:: Day of Week (locale): 0 1 2 ... 6
`E`:: Day of Week (ISO): 1 2 3 ... 7
`w`:: Week of Year - cardinal (locale): 1 2 3 ... 53
`wo`:: Week of Year - ordinal (locale): 1st 2nd 3rd ... 53rd
`ww`:: Week of Year - 2-digit (locale): 01 02 03 ... 53
`W`:: Week of Year - cardinal (ISO): 1 2 3 ... 53
`Wo`:: Week of Year - ordinal (ISO): 1st 2nd 3rd ... 53rd
`WW`:: Week of Year - two-digit (ISO): 01 02 03 ... 53
`YY`:: Year - two digit: 70 71 72 ... 30
`YYYY`:: Year - four digit: 1970 1971 1972 ... 2030
`gg`:: Week Year - two digit (locale): 70 71 72 ... 30
`gggg`:: Week Year - four digit (locale): 1970 1971 1972 ... 2030
`GG`:: Week Year - two digit (ISO): 70 71 72 ... 30
`GGGG`:: Week Year - four digit (ISO): 1970 1971 1972 ... 2030
`A`:: AM/PM: AM PM
`a`:: am/pm: am pm
`H`:: Hour: 0 1 2 ... 23
`HH`:: Hour - two digit: 00 01 02 ... 23
`h`:: Hour - 12-hour clock: 1 2 3 ... 12
`hh`:: Hour - 12-hour clock, 2 digit: 01 02 03 ... 12
`m`:: Minute: 0 1 2 ... 59
`mm`:: Minute - two-digit: 00 01 02 ... 59
`s`:: Second: 0 1 2 ... 59
`ss`:: Second - two-digit: 00 01 02 ... 59
`S`:: Fractional Second - 10ths: 0 1 2 ... 9
`SS`:: Fractional Second - 100ths: 0 1 ... 98 99
`SSS`:: Fractional Seconds - 1000ths: 0 1 ... 998 999
`Z`:: Timezone - zero UTC offset (hh:mm format): -07:00 -06:00 -05:00 .. +07:00
`ZZ`:: Timezone - zero UTC offset (hhmm format): -0700 -0600 -0500 ... +0700
`X`:: Unix Timestamp: 1360013296
`x`:: Unix Millisecond Timestamp: 1360013296123
[float]
[[set-default-pattern]]
== Setting the Default Index Pattern
@ -143,4 +84,4 @@ To delete an index pattern:
. Go to the *Settings > Indices* tab.
. Select the pattern you want to remove in the Index Patterns list.
. Click the pattern's *Delete* button.
. Confirm that you want to remove the index pattern.
. Confirm that you want to remove the index pattern.

View file

@ -49,7 +49,7 @@ describe('plugins/elasticsearch', function () {
'_score': 1,
'_source': {
'buildNum': 1.7976931348623157e+308,
'defaultIndex': '[logstash-]YYYY.MM.DD'
'defaultIndex': 'logstash-*'
}
};
@ -64,7 +64,7 @@ describe('plugins/elasticsearch', function () {
'_score': 1,
'_source': {
'buildNum': 1.7976931348623157e+308,
'defaultIndex': '[logstash-]YYYY.MM.DD'
'defaultIndex': 'logstash-*'
}
};
expect(isUpgradeable(server, doc)).to.be(false);

View file

@ -105,7 +105,7 @@ describe('plugins/elasticsearch', function () {
headers: {
'content-type': 'application/json'
},
payload: { docs: [{ _index: '.kibana', _type: 'index-pattern', _id: '[logstash-]YYYY.MM.DD' }] }
payload: { docs: [{ _index: '.kibana', _type: 'index-pattern', _id: 'logstash-*' }] }
});
testRoute({

View file

@ -7,9 +7,6 @@
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},

View file

@ -28,7 +28,6 @@ describe('createIndexPattern UI', () => {
beforeEach(ngMock.inject(($injector) => {
setup = function () {
const Private = $injector.get('Private');
const Promise = $injector.get('Promise');
const $compile = $injector.get('$compile');
const $rootScope = $injector.get('$rootScope');
@ -39,12 +38,6 @@ describe('createIndexPattern UI', () => {
trash.push(() => $scope.$destroy());
$scope.$apply();
// prevents errors when switching to time pattern
indexPatternsApiClient.testTimePattern = sinon.spy(() => Promise.resolve({
all: ['logstash-0', 'logstash-2017.01.01'],
matches: ['logstash-2017.01.01'],
}));
const setNameTo = (name) => {
$view.findTestSubject('createIndexPatternNameInput')
.val(name)
@ -122,13 +115,6 @@ describe('createIndexPattern UI', () => {
expect($enableExpand).to.have.length(1);
expect($enableExpand.is(':checked')).to.be(false);
});
it('displays the option (off) to use time patterns', () => {
const { $view } = setup();
const $enableTimePattern = $view.findTestSubject('createIndexPatternNameIsPatternCheckBox');
expect($enableTimePattern).to.have.length(1);
expect($enableTimePattern.is(':checked')).to.be(false);
});
});
describe('cross cluster pattern', () => {
@ -149,43 +135,5 @@ describe('createIndexPattern UI', () => {
const $enableExpand = $view.findTestSubject('createIndexPatternEnableExpand');
expect($enableExpand).to.have.length(0);
});
it('removes the option to use time patterns', () => {
const { $view, setNameTo } = setup();
setNameTo('cluster2:logstash-*');
const $enableTimePattern = $view.findTestSubject('createIndexPatternNameIsPatternCheckBox');
expect($enableTimePattern).to.have.length(0);
});
});
describe('expand selected', () => {
it('removes the option to use time patterns', () => {
const { $view } = setup();
const { controller } = $view.findTestSubject('createIndexPatternContainer').scope();
const $enableExpand = $view.findTestSubject('createIndexPatternEnableExpand');
expect($enableExpand).to.have.length(1);
$enableExpand.click();
expect(controller.isExpandWildcardEnabled()).to.be(true);
const $enableTimePattern = $view.findTestSubject('createIndexPatternNameIsPatternCheckBox');
expect($enableTimePattern).to.have.length(0);
});
});
describe('time pattern selected', () => {
it('removes the option to use wildcard expansion', () => {
const { $view } = setup();
const { controller } = $view.findTestSubject('createIndexPatternContainer').scope();
const $enableTimePattern = $view.findTestSubject('createIndexPatternNameIsPatternCheckBox');
expect($enableTimePattern).to.have.length(1);
$enableTimePattern.click();
expect(controller.formValues.nameIsPattern).to.be(true);
const $enableExpand = $view.findTestSubject('createIndexPatternEnableExpand');
expect($enableExpand).to.have.length(0);
});
});
});

View file

@ -61,37 +61,8 @@
<div class="kuiVerticalRhythm">
<p
class="kuiSubText kuiVerticalRhythmSmall"
ng-if="!controller.formValues.nameIsPattern"
translate="KIBANA-WILDCARD_DYNAMIC_INDEX_PATTERNS"
></p>
<p
class="kuiSubText kuiVerticalRhythmSmall"
ng-if="controller.formValues.nameIsPattern"
>
<span translate="KIBANA-STATIC_TEXT_IN_DYNAMIC_INDEX_PATTERNS"></span> &mdash;
<a
class="kuiLink"
href="http://momentjs.com/docs/#/displaying/format/"
target="_blank"
translate="KIBANA-DATE_FORMAT_DOCS"
></a>
</p>
<p
class="kuiSubText kuiVerticalRhythmSmall"
ng-show="controller.formValues.nameInterval.name == 'weeks'"
>
<strong translate="KIBANA-NOTE_COLON"></strong>&nbsp;
<span translate="KIBANA-WEEKLY_ISO_NOTICE"></span>
<span translate="KIBANA-SEE"></span>
<a
class="kuiLink"
href="https://en.wikipedia.org/wiki/ISO_week_date"
target="_blank"
translate="KIBANA-WIKI_ISO_WEEK_DATE"
></a>
</p>
</div>
</div>
@ -160,188 +131,6 @@
</div>
</div>
<!-- Use event times checkbox -->
<div
class="kuiVerticalRhythm"
ng-if="controller.canUseTimePattern()"
>
<label class="kuiCheckBoxLabel">
<input
class="kuiCheckBox"
type="checkbox"
data-test-subj="createIndexPatternNameIsPatternCheckBox"
ng-model="controller.formValues.nameIsPattern"
>
<span class="kuiCheckBoxLabel__text">
<span translate="KIBANA-INDEX_NAME_CREATED_BY_EVENT_TIMES"></span>
<span translate="KIBANA-DEPRECATED"></span>
</span>
</label>
</div>
<div
class="kuiVerticalRhythm"
ng-if="controller.canUseTimePattern() && controller.formValues.nameIsPattern"
>
<!-- Time-interval deprecation warning -->
<div class="kuiInfoPanel kuiInfoPanel--warning kuiVerticalRhythm">
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--warning fa-bolt"></span>
<span
class="kuiInfoPanelHeader__title"
translate="KIBANA-ALERT_INDEX_PATTERN_DEPRECATED"
></span>
</div>
<div class="kuiInfoPanelBody">
<div class="kuiInfoPanelBody__message">
<span translate="KIBANA-WE"></span>
<strong translate="KIBANA-STRONGLY_RECOMMEND"></strong>
<span translate="KIBANA-WILD_CARD_PATTERN"></span>
</div>
</div>
</div>
<!-- Index pattern interval select -->
<div class="kuiVerticalRhythm">
<label class="kuiLabel kuiVerticalRhythmSmall">
<span translate="KIBANA-INDEX_PATTERN_INTERVAL"></span>&nbsp;
<kbn-info info="{{ 'KIBANA-INTERVAL_INDEX_NAMES_ROTATE' | translate }}"></kbn-info>
</label>
<div class="kuiVerticalRhythmSmall">
<select
class="kuiSelect"
required
ng-options="opt.display for opt in controller.nameIntervalOptions"
ng-model="controller.formValues.nameInterval"
></select>
</div>
</div>
</div>
<!-- Errors -->
<div class="kuiVerticalRhythm">
<div
class="kuiInfoPanel kuiInfoPanel--error kuiVerticalRhythmSmall"
ng-repeat="err in controller.patternErrors"
>
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--error fa-warning"></span>
<span class="kuiInfoPanelHeader__title">
{{err}}
</span>
</div>
</div>
<div
class="kuiInfoPanel kuiInfoPanel--info kuiVerticalRhythmSmall"
ng-if="controller.samples"
>
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--info fa-info"></span>
<span
class="kuiInfoPanelHeader__title"
translate="KIBANA-SAMPLE_ALERT"
></span>
</div>
<div class="kuiInfoPanelBody">
<div
class="kuiInfoPanelBody__message"
ng-repeat="sample in controller.samples"
>
{{sample}}
</div>
<div class="kuiInfoPanelBody__message">
<button
type="button"
ng-click="controller.moreSamples(true)"
class="kuiButton kuiButton--basic"
>
<span translate="KIBANA-EXPAND_SEARCH"></span>
</button>
</div>
</div>
</div>
<!-- Percentage of indices which match the index pattern -->
<div
class="kuiInfoPanel kuiVerticalRhythmSmall"
ng-class="controller.existing.matches.length ? 'kuiInfoPanel--success' : 'kuiInfoPanel--error'"
ng-if="controller.existing"
>
<div class="kuiInfoPanelHeader">
<span
class="kuiInfoPanelHeader__icon kuiIcon"
ng-class="controller.existing.matches.length ? 'kuiIcon--success fa-check' : 'kuiIcon--error fa-warning'"
></span>
<span
class="kuiInfoPanelHeader__title"
translate="KIBANA-EXISTING_MATCH_PERCENT"
translate-values="{ indexExistingMatchPercent: '{{controller.existing.matchPercent}}' }"
></span>
</div>
<div
class="kuiInfoPanelBody"
ng-if="controller.existing.matches.length"
>
<div
class="kuiInfoPanelBody__message"
ng-repeat="match in controller.existing.matches | orderBy:'toString()'| limitTo: controller.sampleCount"
>
{{match}}
</div>
<div class="kuiInfoPanelBody__message">
<button
ng-if="controller.sampleCount < controller.existing.matches.length"
ng-click="controller.moreSamples()"
type="button"
class="kuiButton kuiButton--basic"
>
<span translate="KIBANA-EXPAND_SEARCH"></span>
</button>
</div>
</div>
</div>
<div
class="kuiInfoPanel kuiInfoPanel--info kuiVerticalRhythmSmall"
ng-if="controller.existing.failures.length"
>
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--info fa-info"></span>
<span
class="kuiInfoPanelHeader__title"
translate="KIBANA-NON_MATCHING_INDICES_AND_ALIASES"
></span>
</div>
<div class="kuiInfoPanelBody">
<div
class="kuiInfoPanelBody__message"
ng-repeat="match in controller.existing.failures | limitTo: controller.sampleCount"
>
{{match}}
</div>
</div>
<div class="kuiInfoPanelBody__message">
<a
class="kuiLink"
ng-if="controller.sampleCount < controller.existing.matches.length"
ng-click="controller.moreSamples()"
>
<span translate="KIBANA-MORE"></span>
</a>
</div>
</div>
</div>
<!-- Form actions -->
<button
data-test-subj="createIndexPatternCreateButton"

View file

@ -6,7 +6,6 @@ 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';
import { pickCreateButtonText } from './pick_create_button_text';
@ -19,41 +18,27 @@ uiModules.get('apps/management')
.controller('managementIndicesCreate', function ($scope, 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 = {
name: config.get('indexPattern:placeholder'),
nameIsPattern: false,
expandWildcard: false,
nameInterval: _.find(intervals, { name: 'daily' }),
timeFieldOption: null,
};
// UI state.
this.timeFieldOptions = [];
this.timeFieldOptionsError = null;
this.sampleCount = 5;
this.samples = null;
this.existing = null;
this.nameIntervalOptions = intervals;
this.patternErrors = [];
const getTimeFieldOptions = () => {
loadingCount += 1;
return Promise.resolve()
.then(() => {
const { nameIsPattern, name } = this.formValues;
const { name } = this.formValues;
if (!name) {
return [];
}
if (nameIsPattern) {
return indexPatterns.fieldsFetcher.fetchForTimePattern(name);
}
return indexPatterns.fieldsFetcher.fetchForWildcard(name);
})
.then(fields => {
@ -126,51 +111,6 @@ uiModules.get('apps/management')
return nonFieldOptions[0];
};
const resetIndex = () => {
this.patternErrors = [];
this.samples = null;
this.existing = null;
};
const updateSamples = () => {
const patternErrors = [];
if (!this.formValues.nameInterval || !this.formValues.name) {
return Promise.resolve();
}
loadingCount += 1;
return indexPatterns.fieldsFetcher.testTimePattern(this.formValues.name)
.then(existing => {
const all = _.get(existing, 'all', []);
const matches = _.get(existing, 'matches', []);
if (all.length) {
return this.existing = {
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.formValues.name, this.formValues.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;
})
.finally(() => {
loadingCount -= 1;
});
};
this.isTimeBased = () => {
if (!this.formValues.timeFieldOption) {
// if they haven't choosen a time field, assume they will
@ -187,7 +127,6 @@ uiModules.get('apps/management')
return (
this.isTimeBased() &&
!this.isCrossClusterName() &&
!this.formValues.nameIsPattern &&
_.includes(this.formValues.name, '*')
);
};
@ -199,14 +138,6 @@ uiModules.get('apps/management')
);
};
this.canUseTimePattern = () => {
return (
this.isTimeBased() &&
!this.isExpandWildcardEnabled() &&
!this.isCrossClusterName()
);
};
this.isCrossClusterName = () => {
return (
this.formValues.name &&
@ -264,8 +195,6 @@ uiModules.get('apps/management')
const {
name,
timeFieldOption,
nameIsPattern,
nameInterval,
} = this.formValues;
const id = name;
@ -278,16 +207,10 @@ uiModules.get('apps/management')
? undefined
: true;
// Only event-time-based index patterns set an intervalName.
const intervalName = (this.canUseTimePattern() && nameIsPattern && nameInterval)
? nameInterval.name
: undefined;
loadingCount += 1;
sendCreateIndexPatternRequest(indexPatterns, {
id,
timeFieldName,
intervalName,
notExpandable,
}).then(createdId => {
if (!createdId) {
@ -316,65 +239,7 @@ uiModules.get('apps/management')
});
};
$scope.$watchMulti([
'controller.formValues.nameIsPattern',
'controller.formValues.nameInterval.name',
], (newVal, oldVal) => {
const nameIsPattern = newVal[0];
const newDefault = getDefaultPatternForInterval(newVal[1]);
const oldDefault = getDefaultPatternForInterval(oldVal[1]);
if (this.formValues.name === oldDefault) {
this.formValues.name = newDefault;
}
if (!nameIsPattern) {
delete this.formValues.nameInterval;
} else {
this.formValues.nameInterval = this.formValues.nameInterval || intervals.byName.days;
this.formValues.name = this.formValues.name || getDefaultPatternForInterval(this.formValues.nameInterval);
}
});
this.moreSamples = andUpdate => {
this.sampleCount += 5;
if (andUpdate) updateSamples();
};
let latestUpdateSampleId = -1;
$scope.$watchMulti([
'controller.formValues.name',
'controller.formValues.nameInterval'
], () => {
resetIndex();
// track the latestUpdateSampleId at the time we started
// so that we can avoid mutating the controller if the
// watcher triggers again before we finish (which would
// cause latestUpdateSampleId to increment and the
// id === latestUpdateSampleId checks below to fail)
const id = (++latestUpdateSampleId);
updateSamples()
.then(() => {
if (latestUpdateSampleId === id) {
this.samples = null;
this.patternErrors = [];
}
})
.catch(errors => {
if (latestUpdateSampleId === id) {
this.existing = null;
this.patternErrors = errors;
}
})
.finally(() => {
this.refreshTimeFieldOptions();
});
});
$scope.$watchMulti([
'controller.sampleCount'
], () => {
$scope.$watch('controller.formValues.name', () => {
this.refreshTimeFieldOptions();
});

View file

@ -1,17 +0,0 @@
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

@ -1,7 +1,6 @@
export function sendCreateIndexPatternRequest(indexPatterns, {
id,
timeFieldName,
intervalName,
notExpandable,
}) {
// get an empty indexPattern to start
@ -12,7 +11,6 @@ export function sendCreateIndexPatternRequest(indexPatterns, {
Object.assign(indexPattern, {
timeFieldName,
intervalName,
notExpandable,
});

View file

@ -32,19 +32,22 @@
<!-- Alerts -->
<div
ng-if="indexPattern.timeFieldName && indexPattern.intervalName"
class="kuiInfoPanel kuiInfoPanel--warning kuiVerticalRhythm"
ng-if="indexPattern.isUnsupportedTimePattern()"
class="kuiInfoPanel kuiInfoPanel--error kuiVerticalRhythm"
>
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--warning fa-bolt"></span>
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--error fa-warning"></span>
<span class="kuiInfoPanelHeader__title">
Repeating index pattern
Support for repeating index patterns removed
</span>
</div>
<div class="kuiInfoPanelBody">
<div class="kuiInfoPanelBody__message">
This index uses a <strong>time-based index pattern</strong> which repeats <span ng-bind="::indexPattern.getInterval().display"></span>
Support for time-interval based index patterns has been removed! Migrate
saved objects that use this index pattern to a wildcard pattern and delete
this one. In the meantime requests made with this index pattern will be
sent as "{{::indexPattern.getUnsupportedTimePatternAsWildcard()}}".
</div>
</div>
</div>

View file

@ -5,17 +5,10 @@
"KIBANA-MUST_CONFIGURE_INDEX_PATTERN": "In order to use Kibana you must configure at least one index pattern. Index patterns are used to identify the Elasticsearch index to run search and analytics against. They are also used to configure fields.",
"KIBANA-INDEX_NAME_CREATED_BY_EVENT_TIMES": "Use event times to create index names ",
"KIBANA-DEPRECATED": "[DEPRECATED]",
"KIBANA-ALERT_INDEX_PATTERN_DEPRECATED": "Time-interval based index patterns are deprecated!",
"KIBANA-WE": " We",
"KIBANA-STRONGLY_RECOMMEND": " strongly recommend",
"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-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:",
"KIBANA-WEEKLY_ISO_NOTICE": "I noticed you are using weekly indices. Kibana requires ISO weeks be used in your index creation.",
"KIBANA-EXPAND_INDEX_PATTERN": "Expand index pattern when searching",
"KIBANA-WILDCARD_DEFAULT_EXPANDED_TO_CURRENT_TIME_RANGE": "With this option selected, searches against any time-based index pattern that contains a wildcard will automatically be expanded to query only the indices that contain data within the currently selected time range.",
"KIBANA-SEARCH_AGAINST_INDEX_PATTERN": "Searching against the index pattern ",
@ -26,21 +19,13 @@
"KIBANA-EXPAND_INDEX_PATTERN_DEPRECATION": "With recent changes to Elasticsearch, this option should no longer be necessary and will likely be removed in future versions of Kibana.",
"KIBANA-SAMPLE_ALERT": "Attempted to match the following indices and aliases:",
"KIBANA-EXPAND_SEARCH": "Expand Search",
"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-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",
"KIBANA-INDICES_DONT_CONTAIN_TIME_FIELDS": "The indices which match this index pattern don't contain any time fields.",
"KIBANA-INVALID_INDEX_PATTERN": "Invalid index name pattern.",
"KIBANA-DATE_FORMAT_DOCS": "Date Format Documentation",
"KIBANA-WIKI_ISO_WEEK_DATE": "Wikipedia: ISO Week Date",
"KIBANA-NO_INDICES_MATCHING_PATTERN": "Could not locate any indices matching that pattern. Please add the index to Elasticsearch",
"KIBANA-PATTERN_DOES_NOT_MATCH_EXIST_INDICES": "Pattern does not match any existing indices",
"KIBANA-INVALID_NON_UNIQUE_INDEX_NAME_CREATED": "Invalid pattern, interval does not create unique index names",
"KIBANA-FIELD_FILTER_EVENTS_GLOBAL_TIME" : "This field will be used to filter events with the global time filter",
"KIBANA-INTERVAL_INDEX_NAMES_ROTATE" : "The interval at which index names rotate.",
"KIBANA-WARNING" : "Warning",
"KIBANA-NO_DEFAULT_INDEX_PATTERN" : "No default index pattern. You must select or create one to continue.",
"KIBANA-LOADING": "Loading",

View file

@ -1,9 +1,7 @@
import { IndexPatternsService } from './service';
import {
createTestTimePatternRoute,
createFieldsForWildcardRoute,
createFieldsForTimePatternRoute,
} from './routes';
export function indexPatternsMixin(kbnServer, server) {
@ -25,7 +23,5 @@ export function indexPatternsMixin(kbnServer, server) {
}
};
server.route(createTestTimePatternRoute(pre));
server.route(createFieldsForWildcardRoute(pre));
server.route(createFieldsForTimePatternRoute(pre));
}

View file

@ -1,35 +0,0 @@
import Joi from 'joi';
export const createFieldsForTimePatternRoute = pre => ({
path: '/api/index_patterns/_fields_for_time_pattern',
method: 'GET',
config: {
pre: [pre.getIndexPatternsService],
validate: {
query: Joi.object().keys({
pattern: Joi.string().required(),
look_back: Joi.number().min(1).required(),
meta_fields: Joi.array().items(Joi.string()).default([]),
}).default()
},
handler(req, reply) {
const { indexPatterns } = req.pre;
const {
pattern,
interval,
look_back: lookBack,
meta_fields: metaFields,
} = req.query;
reply(
indexPatterns.getFieldsForTimePattern({
pattern,
interval,
lookBack,
metaFields
})
.then(fields => ({ fields }))
);
}
}
});

View file

@ -1,3 +1 @@
export { createTestTimePatternRoute } from './test_time_pattern_route';
export { createFieldsForWildcardRoute } from './fields_for_wildcard_route';
export { createFieldsForTimePatternRoute } from './fields_for_time_pattern_route';

View file

@ -1,22 +0,0 @@
import Joi from 'joi';
export const createTestTimePatternRoute = pre => ({
path: '/api/index_patterns/_test_time_pattern',
method: 'GET',
config: {
pre: [pre.getIndexPatternsService],
validate: {
query: Joi.object().keys({
pattern: Joi.string().required()
}).default()
},
handler(req, reply) {
const { indexPatterns } = req.pre;
const { pattern } = req.query;
reply(indexPatterns.testTimePattern({
pattern,
}));
}
}
});

View file

@ -1,8 +1,5 @@
import {
getFieldCapabilities,
resolveTimePattern,
createNoMatchingIndicesError,
isNoMatchingIndicesError,
} from './lib';
export class IndexPatternsService {
@ -23,52 +20,4 @@ export class IndexPatternsService {
const { pattern, metaFields } = options;
return await getFieldCapabilities(this._callDataCluster, pattern, metaFields);
}
/**
* Convert a time pattern into a list of indexes it could
* have matched and ones it did match.
*
* @param {Object} [options={}]
* @property {String} options.pattern The moment compatible time pattern
* @return {Promise<Object>} object that lists the indices that match based
* on a wildcard version of the time pattern (all)
* and the indices that actually match the time
* pattern (matches);
*/
async testTimePattern(options = {}) {
const { pattern } = options;
try {
return await resolveTimePattern(this._callDataCluster, pattern);
} catch (err) {
if (isNoMatchingIndicesError(err)) {
return {
all: [],
matches: []
};
}
throw err;
}
}
/**
* Get a list of field objects for a time pattern
*
* @param {Object} [options={}]
* @property {String} options.pattern The moment compatible time pattern
* @property {Number} options.lookBack The number of indices we will pull mappings for
* @property {Number} options.metaFields The list of underscore prefixed fields that should
* be left in the field list (all others are removed).
* @return {Promise<Array<Fields>>}
*/
async getFieldsForTimePattern(options = {}) {
const { pattern, lookBack, metaFields } = options;
const { matches } = await resolveTimePattern(this._callDataCluster, pattern);
const indices = matches.slice(0, lookBack);
if (indices.length === 0) {
throw createNoMatchingIndicesError(pattern);
}
return await getFieldCapabilities(this._callDataCluster, indices, metaFields);
}
}

View file

@ -1,99 +0,0 @@
import sinon from 'sinon';
import expect from 'expect.js';
import { noop } from 'lodash';
import { callIndexAliasApi } from '../es_api';
import * as callIndexAliasApiNS from '../es_api';
import { timePatternToWildcard } from '../time_pattern_to_wildcard';
import * as timePatternToWildcardNS from '../time_pattern_to_wildcard';
import { resolveTimePattern } from '../resolve_time_pattern';
const TIME_PATTERN = '[logs-]dddd-YYYY.w';
describe('server/index_patterns/service/lib/resolve_time_pattern', () => {
let sandbox;
beforeEach(() => sandbox = sinon.sandbox.create());
afterEach(() => sandbox.restore());
describe('resolveTimePattern()', () => {
describe('pre request', () => {
it('uses callIndexAliasApi() fn', async () => {
sandbox.stub(callIndexAliasApiNS, 'callIndexAliasApi').returns({});
await resolveTimePattern(noop, TIME_PATTERN);
sinon.assert.calledOnce(callIndexAliasApi);
});
it('converts the time pattern to a wildcard with timePatternToWildcard', async () => {
const timePattern = {};
const wildcard = {};
sandbox.stub(timePatternToWildcardNS, 'timePatternToWildcard')
.returns(wildcard);
await resolveTimePattern(noop, timePattern);
sinon.assert.calledOnce(timePatternToWildcard);
expect(timePatternToWildcard.firstCall.args).to.eql([timePattern]);
});
it('passes the converted wildcard as the index to callIndexAliasApi()', async () => {
const timePattern = {};
const wildcard = {};
sandbox.stub(callIndexAliasApiNS, 'callIndexAliasApi').returns({});
sandbox.stub(timePatternToWildcardNS, 'timePatternToWildcard')
.returns(wildcard);
await resolveTimePattern(noop, timePattern);
sinon.assert.calledOnce(callIndexAliasApi);
expect(callIndexAliasApi.firstCall.args[1]).to.be(wildcard);
});
});
describe('read response', () => {
it('returns all aliases names in result.all, ordered by time desc', async () => {
sandbox.stub(callIndexAliasApiNS, 'callIndexAliasApi').returns({
'logs-2016.2': {},
'logs-Saturday-2017.1': {},
'logs-2016.1': {},
'logs-Sunday-2017.1': {},
'logs-2015': {},
'logs-2016.3': {},
'logs-Friday-2017.1': {},
});
const resp = await resolveTimePattern(noop, TIME_PATTERN);
expect(resp).to.have.property('all');
expect(resp.all).to.eql([
'logs-Saturday-2017.1',
'logs-Friday-2017.1',
'logs-Sunday-2017.1',
'logs-2016.3',
'logs-2016.2',
'logs-2016.1',
'logs-2015',
]);
});
it('returns all indices matching the time pattern in matches, ordered by time desc', async () => {
sandbox.stub(callIndexAliasApiNS, 'callIndexAliasApi').returns({
'logs-2016.2': {},
'logs-Saturday-2017.1': {},
'logs-2016.1': {},
'logs-Sunday-2017.1': {},
'logs-2015': {},
'logs-2016.3': {},
'logs-Friday-2017.1': {},
});
const resp = await resolveTimePattern(noop, TIME_PATTERN);
expect(resp).to.have.property('matches');
expect(resp.matches).to.eql([
'logs-Saturday-2017.1',
'logs-Friday-2017.1',
'logs-Sunday-2017.1'
]);
});
});
});
});

View file

@ -1,3 +1 @@
export { getFieldCapabilities } from './field_capabilities';
export { resolveTimePattern } from './resolve_time_pattern';
export { createNoMatchingIndicesError, isNoMatchingIndicesError } from './errors';

View file

@ -1,57 +0,0 @@
import { chain } from 'lodash';
import moment from 'moment';
import { timePatternToWildcard } from './time_pattern_to_wildcard';
import { callIndexAliasApi } from './es_api';
/**
* Convert a time pattern into a list of indexes it could
* have matched and ones it did match.
*
* @param {Function} callCluster bound function for accessing an es client
* @param {String} timePattern
* @return {Promise<Object>} object that lists the indices that match based
* on a wildcard version of the time pattern (all)
* and the indices that actually match the time
* pattern (matches);
*/
export async function resolveTimePattern(callCluster, timePattern) {
const aliases = await callIndexAliasApi(callCluster, timePatternToWildcard(timePattern));
const allIndexDetails = chain(aliases)
.reduce((acc, index, indexName) => acc.concat(
indexName,
Object.keys(index.aliases || {})
), [])
.sort()
.uniq(true)
.map(indexName => {
const parsed = moment(indexName, timePattern, true);
if (!parsed.isValid()) {
return {
valid: false,
indexName,
order: indexName,
isMatch: false
};
}
return {
valid: true,
indexName,
order: parsed,
isMatch: indexName === parsed.format(timePattern)
};
})
.sortByOrder(['valid', 'order'], ['desc', 'desc'])
.value();
return {
all: allIndexDetails
.map(details => details.indexName),
matches: allIndexDetails
.filter(details => details.isMatch)
.map(details => details.indexName),
};
}

View file

@ -9,7 +9,6 @@ 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 UtilsMappingSetupProvider from 'ui/utils/mapping_setup';
import { IndexPatternsIntervalsProvider } from 'ui/index_patterns/_intervals';
import { IndexPatternProvider } from 'ui/index_patterns/_index_pattern';
import NoDigestPromises from 'test_utils/no_digest_promises';
@ -30,7 +29,6 @@ describe('index pattern', function () {
const indexPatternId = 'test-pattern';
let indexPattern;
let calculateIndices;
let intervals;
let indexPatternsApiClient;
let defaultTimeField;
@ -63,13 +61,6 @@ describe('index pattern', function () {
return Promise.resolve(true);
});
// spy on intervals
intervals = Private(IndexPatternsIntervalsProvider);
sinon.stub(intervals, 'toIndexList').returns([
{ index: 'foo', max: Infinity, min: -Infinity },
{ index: 'bar', max: Infinity, min: -Infinity }
]);
IndexPattern = Private(IndexPatternProvider);
fieldsFetcher = Private(FieldsFetcherProvider);
indexPatternsApiClient = Private(IndexPatternsApiClientProvider);
@ -103,7 +94,6 @@ describe('index pattern', function () {
expect(indexPattern).to.have.property('popularizeField');
expect(indexPattern).to.have.property('getScriptedFields');
expect(indexPattern).to.have.property('getNonScriptedFields');
expect(indexPattern).to.have.property('getInterval');
expect(indexPattern).to.have.property('addScriptedField');
expect(indexPattern).to.have.property('removeScriptedField');
expect(indexPattern).to.have.property('toString');
@ -275,39 +265,10 @@ describe('index pattern', function () {
});
describe('#toDetailedIndexList', function () {
describe('when index pattern is an interval', function () {
let interval;
beforeEach(function () {
interval = 'result:getInterval';
sinon.stub(indexPattern, 'getInterval').returns(interval);
sinon.stub(indexPattern, 'isTimeBasedInterval').returns(true);
});
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);
});
it('is fulfilled by the result of interval toDetailedIndexList', async function () {
const indexList = await indexPattern.toDetailedIndexList();
expect(indexList.map(i => i.index)).to.eql(['foo', 'bar']);
});
describe('with sort order', function () {
it('passes the sort order to the intervals module', async function () {
await indexPattern.toDetailedIndexList(1, 2, 'SORT_DIRECTION');
sinon.assert.calledOnce(intervals.toIndexList);
expect(intervals.toIndexList.getCall(0).args[4]).to.be('SORT_DIRECTION');
});
});
});
describe('when index pattern is a time-base wildcard', function () {
beforeEach(function () {
indexPattern.id = 'logstash-*';
indexPattern.timeFieldName = defaultTimeField.name;
indexPattern.intervalName = null;
indexPattern.notExpandable = false;
});
@ -329,7 +290,6 @@ describe('index pattern', function () {
beforeEach(function () {
indexPattern.id = 'logstash-*';
indexPattern.timeFieldName = defaultTimeField.name;
indexPattern.intervalName = null;
indexPattern.notExpandable = true;
});
@ -343,7 +303,6 @@ describe('index pattern', function () {
beforeEach(function () {
indexPattern.id = 'logstash-0';
indexPattern.timeFieldName = null;
indexPattern.intervalName = null;
indexPattern.notExpandable = true;
});
@ -355,45 +314,10 @@ describe('index pattern', function () {
});
describe('#toIndexList', function () {
describe('when index pattern is an interval', function () {
let interval;
beforeEach(function () {
indexPattern.id = '[logstash-]YYYY';
indexPattern.timeFieldName = defaultTimeField.name;
interval = intervals.byName.years;
indexPattern.intervalName = interval.name;
indexPattern.notExpandable = true;
});
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);
});
it('is fulfilled by the result of interval toIndexList', async function () {
const indexList = await indexPattern.toIndexList();
expect(indexList[0]).to.equal('foo');
expect(indexList[1]).to.equal('bar');
});
describe('with sort order', function () {
it('passes the sort order to the intervals module', function () {
return indexPattern.toIndexList(1, 2, 'SORT_DIRECTION')
.then(function () {
expect(intervals.toIndexList.callCount).to.be(1);
expect(intervals.toIndexList.getCall(0).args[4]).to.be('SORT_DIRECTION');
});
});
});
});
describe('when index pattern is a time-base wildcard', function () {
beforeEach(function () {
indexPattern.id = 'logstash-*';
indexPattern.timeFieldName = defaultTimeField.name;
indexPattern.intervalName = null;
indexPattern.notExpandable = false;
});
@ -415,7 +339,6 @@ describe('index pattern', function () {
beforeEach(function () {
indexPattern.id = 'logstash-*';
indexPattern.timeFieldName = defaultTimeField.name;
indexPattern.intervalName = null;
indexPattern.notExpandable = true;
});
@ -429,7 +352,6 @@ describe('index pattern', function () {
beforeEach(function () {
indexPattern.id = 'logstash-0';
indexPattern.timeFieldName = null;
indexPattern.intervalName = null;
indexPattern.notExpandable = true;
});

View file

@ -1,168 +0,0 @@
import moment from 'moment';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import { IndexPatternsIntervalsProvider } from 'ui/index_patterns/_intervals';
describe('Index Patterns', function () {
describe('interval.toIndexList()', function () {
let intervals;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
intervals = Private(IndexPatternsIntervalsProvider);
}));
it('should return correct indices for hourly [logstash-]YYYY.MM.DD.HH', function () {
const start = moment.utc('2014-01-01T07:00:00Z');
const end = moment.utc('2014-01-01T08:30:00Z');
const interval = { name: 'hours', startOf: 'hour', display: 'Hourly' };
const list = intervals.toIndexList('[logstash-]YYYY.MM.DD.HH', interval, start, end);
expect(list).to.eql([
{
index: 'logstash-2014.01.01.07',
min: moment.utc('2014-01-01T07:00:00').valueOf(),
max: moment.utc('2014-01-01T07:59:59.999').valueOf(),
},
{
index: 'logstash-2014.01.01.08',
min: moment.utc('2014-01-01T08:00:00').valueOf(),
max: moment.utc('2014-01-01T08:59:59.999').valueOf(),
}
]);
});
it('should return correct indices for daily [logstash-]YYYY.MM.DD', function () {
const start = moment(1418244231248);
const end = moment(1418849261281);
const interval = { name: 'days', startOf: 'day', display: 'Daily' };
const list = intervals.toIndexList('[logstash-]YYYY.MM.DD', interval, start, end);
expect(list).to.eql([
{
index: 'logstash-2014.12.10',
min: moment.utc('2014-12-10T00:00:00').valueOf(),
max: moment.utc('2014-12-10T23:59:59.999').valueOf(),
},
{
index: 'logstash-2014.12.11',
min: moment.utc('2014-12-11T00:00:00').valueOf(),
max: moment.utc('2014-12-11T23:59:59.999').valueOf(),
},
{
index: 'logstash-2014.12.12',
min: moment.utc('2014-12-12T00:00:00').valueOf(),
max: moment.utc('2014-12-12T23:59:59.999').valueOf(),
},
{
index: 'logstash-2014.12.13',
min: moment.utc('2014-12-13T00:00:00').valueOf(),
max: moment.utc('2014-12-13T23:59:59.999').valueOf(),
},
{
index: 'logstash-2014.12.14',
min: moment.utc('2014-12-14T00:00:00').valueOf(),
max: moment.utc('2014-12-14T23:59:59.999').valueOf(),
},
{
index: 'logstash-2014.12.15',
min: moment.utc('2014-12-15T00:00:00').valueOf(),
max: moment.utc('2014-12-15T23:59:59.999').valueOf(),
},
{
index: 'logstash-2014.12.16',
min: moment.utc('2014-12-16T00:00:00').valueOf(),
max: moment.utc('2014-12-16T23:59:59.999').valueOf(),
},
{
index: 'logstash-2014.12.17',
min: moment.utc('2014-12-17T00:00:00').valueOf(),
max: moment.utc('2014-12-17T23:59:59.999').valueOf(),
},
]);
});
it('should return correct indices for monthly [logstash-]YYYY.MM', function () {
const start = moment.utc('2014-12-01');
const end = moment.utc('2015-02-01');
const interval = { name: 'months', startOf: 'month', display: 'Monthly' };
const list = intervals.toIndexList('[logstash-]YYYY.MM', interval, start, end);
expect(list).to.eql([
{
index: 'logstash-2014.12',
min: moment.utc(0).year(2014).month(11).valueOf(),
max: moment.utc(0).year(2015).month(0).subtract(1, 'ms').valueOf(),
},
{
index: 'logstash-2015.01',
min: moment.utc(0).year(2015).month(0).valueOf(),
max: moment.utc(0).year(2015).month(1).subtract(1, 'ms').valueOf(),
},
{
index: 'logstash-2015.02',
min: moment.utc(0).year(2015).month(1).valueOf(),
max: moment.utc(0).year(2015).month(2).subtract(1, 'ms').valueOf(),
},
]);
});
it('should return correct indices for yearly [logstash-]YYYY', function () {
const start = moment.utc('2014-12-01');
const end = moment.utc('2015-02-01');
const interval = { name: 'years', startOf: 'year', display: 'Yearly' };
const list = intervals.toIndexList('[logstash-]YYYY', interval, start, end);
expect(list).to.eql([
{
index: 'logstash-2014',
min: moment.utc(0).year(2014).valueOf(),
max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(),
},
{
index: 'logstash-2015',
min: moment.utc(0).year(2015).valueOf(),
max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(),
},
]);
});
describe('with sortDirection=asc', function () {
it('returns values in ascending order', function () {
const start = moment.utc('2014-12-01');
const end = moment.utc('2015-02-01');
const interval = { name: 'years', startOf: 'year', display: 'Yearly' };
const list = intervals.toIndexList('[logstash-]YYYY', interval, start, end, 'asc');
expect(list).to.eql([
{
index: 'logstash-2014',
min: moment.utc(0).year(2014).valueOf(),
max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(),
},
{
index: 'logstash-2015',
min: moment.utc(0).year(2015).valueOf(),
max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(),
},
]);
});
});
describe('with sortDirection=desc', function () {
it('returns values in descending order', function () {
const start = moment.utc('2014-12-01');
const end = moment.utc('2015-02-01');
const interval = { name: 'years', startOf: 'year', display: 'Yearly' };
const list = intervals.toIndexList('[logstash-]YYYY', interval, start, end, 'desc');
expect(list).to.eql([
{
index: 'logstash-2015',
min: moment.utc(0).year(2015).valueOf(),
max: moment.utc(0).year(2016).subtract(1, 'ms').valueOf(),
},
{
index: 'logstash-2014',
min: moment.utc(0).year(2014).valueOf(),
max: moment.utc(0).year(2015).subtract(1, 'ms').valueOf(),
},
]);
});
});
});
});

View file

@ -13,12 +13,7 @@ export function StubIndexPatternsApiClientModule(PrivateProvider) {
));
class StubIndexPatternsApiClient {
getFieldsForTimePattern = sinon.spy(() => Promise.resolve(nonScriptedFields));
getFieldsForWildcard = sinon.spy(() => Promise.resolve(nonScriptedFields));
testTimePattern = sinon.spy(() => Promise.resolve({
all: [],
matches: []
}))
swapStubNonScriptedFields = (newNonScriptedFields) => {
nonScriptedFields = newNonScriptedFields;

View file

@ -5,11 +5,11 @@ 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';
import { timePatternToWildcard } from '../../../utils';
import { getComputedFields } from './_get_computed_fields';
import { formatHit } from './_format_hit';
import { IndexPatternsGetIdsProvider } from './_get_ids';
import { IndexPatternsIntervalsProvider } from './_intervals';
import { IndexPatternsFieldListProvider } from './_field_list';
import { IndexPatternsFlattenHitProvider } from './_flatten_hit';
import { IndexPatternsCalculateIndicesProvider } from './_calculate_indices';
@ -20,7 +20,6 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
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);
@ -43,7 +42,6 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
title: 'text',
timeFieldName: 'keyword',
notExpandable: 'boolean',
intervalName: 'keyword',
fields: 'json',
sourceFilters: 'json',
fieldFormatMap: {
@ -83,6 +81,16 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
);
});
if (response._source.intervalName) {
indexPattern._isUnsupportedTimePattern = true;
delete response._source.intervalName;
notify.error(
'Support for time-pattern based index patterns has been remove as of ' +
'Kibana 6.0.0. Please migrate saved objects using index pattern ' +
`"${indexPattern.id}" to a wildcard pattern instead.`
);
}
// give index pattern all of the values in _source
_.assign(indexPattern, response._source);
@ -268,10 +276,6 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
return _.where(this.fields, { scripted: true });
}
getInterval() {
return this.intervalName && _.find(intervals, { name: this.intervalName });
}
toIndexList(start, stop, sortDirection) {
return this
.toDetailedIndexList(start, stop, sortDirection)
@ -285,10 +289,14 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
toDetailedIndexList(start, stop, sortDirection) {
return Promise.resolve().then(() => {
if (this.isTimeBasedInterval()) {
return intervals.toIndexList(
this.id, this.getInterval(), start, stop, sortDirection
);
if (this.isUnsupportedTimePattern()) {
return [
{
index: this.getUnsupportedTimePatternAsWildcard(),
min: -Infinity,
max: Infinity
}
];
}
if (this.isTimeBasedWildcard() && this.isIndexExpansionEnabled()) {
@ -315,8 +323,8 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
return !!this.timeFieldName && (!this.fields || !!this.getTimeField());
}
isTimeBasedInterval() {
return this.isTimeBased() && !!this.getInterval();
isUnsupportedTimePattern() {
return !!this._isUnsupportedTimePattern;
}
isTimeBasedWildcard() {
@ -328,6 +336,10 @@ export function IndexPatternProvider(Private, $http, config, kbnIndex, Promise,
return this.fields.byName[this.timeFieldName];
}
getUnsupportedTimePatternAsWildcard() {
return timePatternToWildcard(this.id);
}
isWildcard() {
return _.includes(this.id, '*');
}

View file

@ -1,95 +0,0 @@
import _ from 'lodash';
import moment from 'moment';
import { IndexedArray } from 'ui/indexed_array';
export function IndexPatternsIntervalsProvider(timefilter) {
const intervals = new IndexedArray({
index: ['name'],
initialSet: [
{
name: 'hours',
startOf: 'hour',
display: 'Hourly'
},
{
name: 'days',
startOf: 'day',
display: 'Daily'
},
{
name: 'weeks',
startOf: 'isoWeek',
display: 'Weekly'
},
{
name: 'months',
startOf: 'month',
display: 'Monthly'
},
{
name: 'years',
startOf: 'year',
display: 'Yearly'
}
]
});
intervals.toIndexList = function (format, interval, a, b, sortDirection) {
let bounds;
// setup the range that the list will span, return two moment objects that
// are in proper order. a and b can be numbers to specify to go before or after now (respectively)
// a certain number of times, based on the interval
const range = [[a, 'min', 'startOf'], [b, 'max', 'startOf']].map(function (v) {
let val = v[0];
const bound = v[1];
const extend = v[2];
// grab a bound from the time filter
if (val == null) {
bounds = bounds || timefilter.getBounds();
val = bounds[bound];
}
if (_.isNumeric(val)) val = moment().add(val, interval.name);
else if (!moment.isMoment(val)) val = moment(val);
return val.clone().utc()[extend](interval.startOf);
}).sort(function (a, b) {
return a - b;
});
if (typeof interval === 'string') {
interval = _.find(intervals, { name: interval });
if (!interval) throw new Error('Interval must be one of ' + _.pluck(intervals, 'name'));
}
const indexList = [];
let start = range.shift();
// turn stop into milliseconds to that it's not constantly converted by the while condition
const stop = range.shift().valueOf();
const add = sortDirection === 'desc' ? 'unshift' : 'push';
while (start <= stop) {
const index = start.format(format);
const next = moment(start).add(1, interval.name);
const bound = moment(next).subtract(1, 'ms');
const min = start.valueOf();
const max = bound.valueOf();
indexList[add]({
index: index,
min: min,
max: max
});
start = next;
}
return indexList;
};
return intervals;
}

View file

@ -1,28 +1,9 @@
export function createFieldsFetcher(apiClient, config) {
class FieldsFetcher {
fetch(indexPattern) {
if (indexPattern.isTimeBasedInterval()) {
const interval = indexPattern.getInterval().name;
return this.fetchForTimePattern(indexPattern.id, interval);
}
return this.fetchForWildcard(indexPattern.id);
}
testTimePattern(indexPatternId) {
return apiClient.testTimePattern({
pattern: indexPatternId
});
}
fetchForTimePattern(indexPatternId) {
return apiClient.getFieldsForTimePattern({
pattern: indexPatternId,
lookBack: config.get('indexPattern:fieldMapping:lookBack'),
metaFields: config.get('metaFields'),
});
}
fetchForWildcard(indexPatternId) {
return apiClient.getFieldsForWildcard({
pattern: indexPatternId,

View file

@ -3,7 +3,6 @@ import { IndexPatternMissingIndices } from 'ui/errors';
import { IndexPatternProvider } from 'ui/index_patterns/_index_pattern';
import { IndexPatternsPatternCacheProvider } from 'ui/index_patterns/_pattern_cache';
import { IndexPatternsGetIdsProvider } from 'ui/index_patterns/_get_ids';
import { IndexPatternsIntervalsProvider } from 'ui/index_patterns/_intervals';
import { FieldsFetcherProvider } from './fields_fetcher_provider';
import { RegistryFieldFormatsProvider } from 'ui/registry/field_formats';
import { uiModules } from 'ui/modules';
@ -44,7 +43,6 @@ export function IndexPatternsProvider(esAdmin, Notifier, Private, Promise, kbnIn
self.cache = patternCache;
self.getIds = Private(IndexPatternsGetIdsProvider);
self.intervals = Private(IndexPatternsIntervalsProvider);
self.fieldsFetcher = Private(FieldsFetcherProvider);
self.fieldFormats = Private(RegistryFieldFormatsProvider);
self.IndexPattern = IndexPattern;

View file

@ -48,24 +48,6 @@ export function createIndexPatternsApiClient($http, basePath) {
}
class IndexPatternsApiClient {
getFieldsForTimePattern(options = {}) {
const {
pattern,
lookBack,
metaFields,
} = options;
const url = getUrl(['_fields_for_time_pattern'], {
pattern,
look_back: lookBack,
meta_fields: metaFields,
});
return notify.event(`getFieldsForTimePattern(${pattern})`, () => (
request('GET', url).then(resp => resp.fields)
));
}
getFieldsForWildcard(options = {}) {
const {
pattern,
@ -81,20 +63,6 @@ export function createIndexPatternsApiClient($http, basePath) {
request('GET', url).then(resp => resp.fields)
));
}
testTimePattern(options = {}) {
const {
pattern
} = options;
const url = getUrl(['_test_time_pattern'], {
pattern,
});
return notify.event(`testTimePattern(${pattern})`, () => (
request('GET', url)
));
}
}
return new IndexPatternsApiClient();

View file

@ -1,6 +1,6 @@
import { timePatternToWildcard } from '../time_pattern_to_wildcard';
describe('server/index_patterns/service/lib/time_pattern_to_wildcard', () => {
describe('utils/time_pattern_to_wildcard', () => {
const tests = [
['[logstash-]YYYY.MM.DD', 'logstash-*'],
['YYYY[-department-].w', '*-department-*'],

View file

@ -7,6 +7,7 @@ export { unset } from './unset';
export { encodeQueryComponent } from './encode_query_component';
export { modifyUrl } from './modify_url';
export { createToolingLog } from './tooling_log';
export { timePatternToWildcard } from './time_pattern_to_wildcard';
export {
getKbnTypeNames,

View file

@ -1,15 +0,0 @@
export default function ({ getService }) {
const supertest = getService('supertest');
describe('errors', () => {
it('returns 404 when no indices match', () => (
supertest
.get('/api/index_patterns/_fields_for_time_pattern')
.query({
pattern: '[not-really-an-index-]YYYY.MM.DD',
look_back: 1
})
.expect(404)
));
});
}

View file

@ -1,7 +0,0 @@
export default function ({ loadTestFile }) {
describe('index_patterns/_fields_for_time_pattern', () => {
loadTestFile(require.resolve('./errors'));
loadTestFile(require.resolve('./pattern'));
loadTestFile(require.resolve('./query_params'));
});
}

View file

@ -1,123 +0,0 @@
import expect from 'expect.js';
export default function ({ getService }) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('pattern', () => {
before(() => esArchiver.load('index_patterns/daily_index'));
after(() => esArchiver.unload('index_patterns/daily_index'));
it('matches indices with compatible patterns', () => (
supertest
.get('/api/index_patterns/_fields_for_time_pattern')
.query({
pattern: '[logs-]YYYY.MM.DD',
look_back: 2,
})
.expect(200)
.then(resp => {
expect(resp.body).to.eql({
fields: [
{
name: '@timestamp',
type: 'date',
aggregatable: true,
searchable: true,
readFromDocValues: true,
},
{
name: 'Jan01',
type: 'boolean',
aggregatable: true,
searchable: true,
readFromDocValues: true,
},
{
name: 'Jan02',
type: 'boolean',
aggregatable: true,
searchable: true,
readFromDocValues: true,
}
]
});
})
));
it('respects look_back parameter', () => (
supertest
.get('/api/index_patterns/_fields_for_time_pattern')
.query({
pattern: '[logs-]YYYY.MM.DD',
look_back: 1,
})
.expect(200)
.then(resp => {
expect(resp.body).to.eql({
fields: [
{
name: '@timestamp',
type: 'date',
aggregatable: true,
searchable: true,
readFromDocValues: true,
},
{
name: 'Jan02',
type: 'boolean',
aggregatable: true,
searchable: true,
readFromDocValues: true,
}
]
});
})
));
it('includes a field for each of `meta_fields` names', () => (
supertest
.get('/api/index_patterns/_fields_for_time_pattern')
.query({
pattern: '[logs-]YYYY.MM.DD',
look_back: 1,
meta_fields: JSON.stringify(['meta1', 'meta2'])
})
.expect(200)
.then(resp => {
expect(resp.body).to.eql({
fields: [
{
name: '@timestamp',
type: 'date',
aggregatable: true,
searchable: true,
readFromDocValues: true,
},
{
name: 'Jan02',
type: 'boolean',
aggregatable: true,
searchable: true,
readFromDocValues: true,
},
{
name: 'meta1',
type: 'string',
aggregatable: false,
searchable: false,
readFromDocValues: false,
},
{
name: 'meta2',
type: 'string',
aggregatable: false,
searchable: false,
readFromDocValues: false,
},
]
});
})
));
});
}

View file

@ -1,64 +0,0 @@
import expect from 'expect.js';
export default function ({ getService }) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('query params', () => {
before(() => esArchiver.load('index_patterns/daily_index'));
after(() => esArchiver.unload('index_patterns/daily_index'));
it('requires `pattern` and `look_back` query params', () => (
supertest
.get('/api/index_patterns/_fields_for_time_pattern')
.query({ pattern: null })
.expect(400)
.then(resp => {
expect(resp.body.validation).to.eql({
keys: [
'pattern',
'look_back'
],
source: 'query'
});
})
));
it('supports `meta_fields` query param', () => (
supertest
.get('/api/index_patterns/_fields_for_time_pattern')
.query({
pattern: '[logs-]YYYY.MM.DD',
look_back: 1,
meta_fields: JSON.stringify(['a'])
})
.expect(200)
));
it('requires `look_back` to be a number', () => (
supertest
.get('/api/index_patterns/_fields_for_time_pattern')
.query({
pattern: '[logs-]YYYY.MM.DD',
look_back: 'foo',
})
.expect(400)
.then(resp => {
expect(resp.body.message).to.contain('"look_back" must be a number');
})
));
it('requires `look_back` to be greater than one', () => (
supertest
.get('/api/index_patterns/_fields_for_time_pattern')
.query({
pattern: '[logs-]YYYY.MM.DD',
look_back: 0,
})
.expect(400)
.then(resp => {
expect(resp.body.message).to.contain('"look_back" must be larger than or equal to 1');
})
));
});
}

View file

@ -1,8 +1,6 @@
export default function ({ loadTestFile }) {
describe('index_patterns', () => {
loadTestFile(require.resolve('./es_errors'));
loadTestFile(require.resolve('./fields_for_time_pattern_route'));
loadTestFile(require.resolve('./fields_for_wildcard_route'));
loadTestFile(require.resolve('./test_time_pattern_route'));
});
}

View file

@ -1,53 +0,0 @@
export default function ({ getService }) {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
const chance = getService('chance');
describe('index_patterns/_test_time_pattern', () => {
before(() => esArchiver.load('index_patterns/time_based_indices'));
after(() => esArchiver.unload('index_patterns/time_based_indices'));
it('returns all and matching fields for tested pattern', () =>
supertest
.get('/api/index_patterns/_test_time_pattern')
.query({ pattern: '[yearly-]YYYY' })
.expect(200, {
all: [
'yearly-2018',
'yearly-2017',
'yearly-2016',
],
matches: [
'yearly-2018',
'yearly-2017',
'yearly-2016',
]
})
);
it('returns all and matching fields for tested pattern', () =>
supertest
.get('/api/index_patterns/_test_time_pattern')
.query({ pattern: '[monthly-]YYYY.MM' })
.expect(200, {
all: [
'monthly-2017.01',
'monthly-invalid',
],
matches: [
'monthly-2017.01',
]
})
);
it('returns an empty response if it does not match any indexes', () =>
supertest
.get('/api/index_patterns/_test_time_pattern')
.query({ pattern: `[${chance.word({ length: 12 })}]-YYYY` })
.expect(200, {
all: [],
matches: []
})
);
});
}

View file

@ -1,3 +1,31 @@
{
"type": "index",
"value": {
"index": "foo-1",
"settings": {
"index": {
"number_of_shards": "5",
"number_of_replicas": "1"
}
},
"mappings": {
"bar": {
"properties": {
"foo": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
{
"type": "index",
"value": {
@ -15,109 +43,26 @@
}
},
"mappings": {
"url": {
"index-pattern": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"fieldFormatMap": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"fields": {
"type": "text"
},
"title": {
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"_default_": {
"dynamic": "strict"
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
@ -151,29 +96,33 @@
}
}
},
"index-pattern": {
"visualization": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"description": {
"type": "text"
},
"fields": {
"type": "text"
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
@ -235,6 +184,71 @@
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"server": {
"dynamic": "strict",
"properties": {
@ -242,6 +256,17 @@
"type": "keyword"
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"_default_": {
"dynamic": "strict"
}
}
}
@ -273,32 +298,4 @@
}
}
}
}
{
"type": "index",
"value": {
"index": "foo-1",
"settings": {
"index": {
"number_of_shards": "5",
"number_of_replicas": "1"
}
},
"mappings": {
"bar": {
"properties": {
"foo": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}

View file

@ -17,13 +17,6 @@ export default function ({ getService, getPageObjects }) {
});
});
it('should load with name pattern unchecked', function () {
return PageObjects.settings.getTimeBasedIndexPatternCheckbox().isSelected()
.then(function (selected) {
expect(selected).to.not.be.ok();
});
});
it('should contain default index pattern', function () {
const defaultPattern = 'logstash-*';

View file

@ -62,11 +62,6 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
await PageObjects.common.navigateToApp('settings');
}
getTimeBasedIndexPatternCheckbox() {
// fail faster since we're sometimes checking that it doesn't exist
return testSubjects.find('createIndexPatternNameIsPatternCheckBox');
}
getIndexPatternField() {
return testSubjects.find('createIndexPatternNameInput');
}