From 16f040b9bebfae338826cc90989a52331492f903 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Thu, 1 Aug 2019 17:38:04 -0600 Subject: [PATCH] Merge changes with updates from #35262. --- .../__tests__/_index_pattern.test.js | 35 ++++++++ .../{_index_pattern.ts => _index_pattern.tsx} | 85 ++++++++++++++++++- .../public/index_patterns/fields_fetcher.ts | 2 +- 3 files changed, 120 insertions(+), 2 deletions(-) rename src/legacy/ui/public/index_patterns/{_index_pattern.ts => _index_pattern.tsx} (82%) diff --git a/src/legacy/ui/public/index_patterns/__tests__/_index_pattern.test.js b/src/legacy/ui/public/index_patterns/__tests__/_index_pattern.test.js index 976a6ab86b9f2d..abfe1ffdd8b52c 100644 --- a/src/legacy/ui/public/index_patterns/__tests__/_index_pattern.test.js +++ b/src/legacy/ui/public/index_patterns/__tests__/_index_pattern.test.js @@ -309,6 +309,41 @@ describe('IndexPattern', () => { }); }); + describe('getIndex', () => { + it('should return the title when there is no intervalName', () => { + expect(indexPattern.getIndex()).toBe(indexPattern.title); + }); + + it('should convert time-based intervals to wildcards', () => { + const oldTitle = indexPattern.title; + indexPattern.intervalName = 'daily'; + + indexPattern.title = '[logstash-]YYYY.MM.DD'; + expect(indexPattern.getIndex()).toBe('logstash-*'); + + indexPattern.title = 'YYYY.MM.DD[-logstash]'; + expect(indexPattern.getIndex()).toBe('*-logstash'); + + indexPattern.title = 'YYYY[-logstash-]YYYY.MM.DD'; + expect(indexPattern.getIndex()).toBe('*-logstash-*'); + + indexPattern.title = 'YYYY[-logstash-]YYYY[-foo-]MM.DD'; + expect(indexPattern.getIndex()).toBe('*-logstash-*-foo-*'); + + indexPattern.title = 'YYYY[-logstash-]YYYY[-foo-]MM.DD[-bar]'; + expect(indexPattern.getIndex()).toBe('*-logstash-*-foo-*-bar'); + + indexPattern.title = '[logstash-]YYYY[-foo-]MM.DD[-bar]'; + expect(indexPattern.getIndex()).toBe('logstash-*-foo-*-bar'); + + indexPattern.title = '[logstash]'; + expect(indexPattern.getIndex()).toBe('logstash'); + + indexPattern.title = oldTitle; + delete indexPattern.intervalName; + }); + }); + it('should handle version conflicts', async () => { setDocsourcePayload(null, { _version: 'foo', diff --git a/src/legacy/ui/public/index_patterns/_index_pattern.ts b/src/legacy/ui/public/index_patterns/_index_pattern.tsx similarity index 82% rename from src/legacy/ui/public/index_patterns/_index_pattern.ts rename to src/legacy/ui/public/index_patterns/_index_pattern.tsx index 09623f75442ed6..e9eb849e9320af 100644 --- a/src/legacy/ui/public/index_patterns/_index_pattern.ts +++ b/src/legacy/ui/public/index_patterns/_index_pattern.tsx @@ -19,6 +19,11 @@ import _, { each, reject } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React from 'react'; +import chrome from 'ui/chrome'; + // @ts-ignore import { SavedObjectNotFound, DuplicateField } from 'ui/errors'; // @ts-ignore @@ -57,6 +62,7 @@ export interface StaticIndexPattern { id?: string; type?: string; timeFieldName?: string; + intervalName?: string | null; } export class IndexPattern implements StaticIndexPattern { @@ -69,6 +75,7 @@ export class IndexPattern implements StaticIndexPattern { public typeMeta: any; public fields: FieldList; public timeFieldName: string | undefined; + public intervalName: string | undefined | null; public formatHit: any; public formatField: any; public flattenHit: any; @@ -173,7 +180,7 @@ export class IndexPattern implements StaticIndexPattern { this.initFields(); } - private updateFromElasticSearch(response: any, forceFieldRefresh: boolean = false) { + private async updateFromElasticSearch(response: any, forceFieldRefresh: boolean = false) { if (!response.found) { throw new SavedObjectNotFound(type, this.id, '#/management/kibana/index_pattern'); } @@ -192,6 +199,46 @@ export class IndexPattern implements StaticIndexPattern { this.title = this.id; } + if (this.isUnsupportedTimePattern()) { + const warningTitle = i18n.translate('common.ui.indexPattern.warningTitle', { + defaultMessage: 'Support for time interval index patterns removed', + }); + + const warningText = i18n.translate('common.ui.indexPattern.warningText', { + defaultMessage: + 'Currently querying all indices matching {index}. {title} should be migrated to a wildcard-based index pattern.', + values: { + title: this.title, + index: this.getIndex(), + }, + }); + + // kbnUrl was added to this service in #35262 before it was de-angularized, and merged in a PR + // directly against the 7.x branch. Index patterns were de-angularized in #39247, and in order + // to preserve the functionality from #35262 we need to get the injector here just for kbnUrl. + // This has all been removed as of 8.0. + const $injector = await chrome.dangerouslyGetActiveInjector(); + const kbnUrl = $injector.get('kbnUrl') as any; // `any` because KbnUrl interface doesn't have `getRouteHref` + toastNotifications.addWarning({ + title: warningTitle, + text: ( +
+

{warningText}

+ + + + + + + +
+ ), + }); + } + return this.indexFields(forceFieldRefresh); } @@ -261,6 +308,19 @@ export class IndexPattern implements StaticIndexPattern { return this; } + migrate(newTitle: string) { + return this.savedObjectsClient + .update(type, this.id!, { + title: newTitle, + intervalName: null, + }) + .then(({ attributes: { title, intervalName } }) => { + this.title = title; + this.intervalName = intervalName; + }) + .then(() => this); + } + // Get the source filtering configuration for that index. getSourceFiltering() { return { @@ -326,10 +386,33 @@ export class IndexPattern implements StaticIndexPattern { return _.where(this.fields, { scripted: true }); } + getIndex() { + if (!this.isUnsupportedTimePattern()) { + return this.title; + } + + // Take a time-based interval index pattern title (like [foo-]YYYY.MM.DD[-bar]) and turn it + // into the actual index (like foo-*-bar) by replacing anything not inside square brackets + // with a *. + const regex = /\[[^\]]*]/g; // Matches text inside brackets + const splits = this.title.split(regex); // e.g. ['', 'YYYY.MM.DD', ''] from the above example + const matches = this.title.match(regex) || []; // e.g. ['[foo-]', '[-bar]'] from the above example + return splits + .map((split, i) => { + const match = i >= matches.length ? '' : matches[i].replace(/[\[\]]/g, ''); + return `${split.length ? '*' : ''}${match}`; + }) + .join(''); + } + isTimeBased(): boolean { return !!this.timeFieldName && (!this.fields || !!this.getTimeField()); } + isUnsupportedTimePattern(): boolean { + return !!this.intervalName; + } + isTimeNanosBased(): boolean { const timeField: any = this.getTimeField(); return timeField && timeField.esTypes && timeField.esTypes.indexOf('date_nanos') !== -1; diff --git a/src/legacy/ui/public/index_patterns/fields_fetcher.ts b/src/legacy/ui/public/index_patterns/fields_fetcher.ts index d320bbd718d47f..c7705d0e2ce124 100644 --- a/src/legacy/ui/public/index_patterns/fields_fetcher.ts +++ b/src/legacy/ui/public/index_patterns/fields_fetcher.ts @@ -30,7 +30,7 @@ export const createFieldsFetcher = ( ) => { const fieldFetcher = { fetch: (options: GetFieldsOptions) => { - return fieldFetcher.fetchForWildcard(indexPattern.title, { + return fieldFetcher.fetchForWildcard(indexPattern.getIndex(), { ...options, type: indexPattern.type, params: indexPattern.typeMeta && indexPattern.typeMeta.params,