diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/__tests__/create_index_pattern.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/__tests__/create_index_pattern.js
deleted file mode 100644
index d5fe732f1604e..0000000000000
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/__tests__/create_index_pattern.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import angular from 'angular';
-import ngMock from 'ng_mock';
-import jQuery from 'jquery';
-import expect from 'expect.js';
-import sinon from 'sinon';
-
-import createIndexPatternTemplate from '../create_index_pattern.html';
-import { StubIndexPatternsApiClientModule } from 'ui/index_patterns/__tests__/stub_index_patterns_api_client';
-import { IndexPatternsApiClientProvider } from 'ui/index_patterns';
-import MockLogstashFieldsProvider from 'fixtures/logstash_fields';
-
-describe('createIndexPattern UI', () => {
- let setup;
- const trash = [];
-
- beforeEach(ngMock.module('kibana', StubIndexPatternsApiClientModule, ($provide) => {
- $provide.constant('buildSha', 'abc1234');
- $provide.constant('$route', {
- current: {
- params: {},
- locals: {
- indexPatterns: []
- }
- }
- });
- }));
-
- beforeEach(ngMock.inject(($injector) => {
- setup = function () {
- const Private = $injector.get('Private');
- const $compile = $injector.get('$compile');
- const $rootScope = $injector.get('$rootScope');
-
- const fields = Private(MockLogstashFieldsProvider);
- const indexPatternsApiClient = Private(IndexPatternsApiClientProvider);
- const $scope = $rootScope.$new();
- const $view = jQuery($compile(angular.element('
').html(createIndexPatternTemplate))($scope));
- trash.push(() => $scope.$destroy());
- $scope.$apply();
-
- const setNameTo = (name) => {
- $view.findTestSubject('createIndexPatternNameInput')
- .val(name)
- .change()
- .blur();
-
- // ensure that name successfully applied
- const form = $view.find('form').scope().form;
- expect(form.name).to.have.property('$viewValue', name);
- };
-
- return {
- $view,
- $scope,
- setNameTo,
- indexPatternsApiClient,
- fields
- };
- };
- }));
-
- afterEach(() => {
- trash.forEach(fn => fn());
- trash.length = 0;
- });
-
- describe('defaults', () => {
- it('renders `logstash-*` into the name input', () => {
- const { $view } = setup();
-
- const $name = $view.findTestSubject('createIndexPatternNameInput');
- expect($name).to.have.length(1);
- expect($name.val()).to.be('logstash-*');
- });
-
- it('attempts to getFieldsForWildcard for `logstash-*`', () => {
- const { indexPatternsApiClient } = setup();
- const { getFieldsForWildcard } = indexPatternsApiClient;
-
- sinon.assert.called(getFieldsForWildcard);
- const calledWithPattern = getFieldsForWildcard.getCalls().some(call => {
- const [params] = call.args;
- return (
- params &&
- params.pattern &&
- params.pattern === 'logstash-*'
- );
- });
-
- if (!calledWithPattern) {
- throw new Error('expected indexPatternsApiClient.getFieldsForWildcard to be called with pattern = logstash-*');
- }
- });
-
- it('loads the time fields into the select box', () => {
- const { $view, fields } = setup();
-
- const timeFieldOptions = $view.findTestSubject('createIndexPatternTimeFieldSelect')
- .find('option')
- .toArray()
- .map(option => option.innerText);
-
- fields.forEach((field) => {
- if (!field.scripted && field.type === 'date') {
- expect(timeFieldOptions).to.contain(field.name);
- } else {
- expect(timeFieldOptions).to.not.contain(field.name);
- }
- });
- });
- });
-
- describe('cross cluster pattern', () => {
- it('name input accepts `cluster2:logstash-*` pattern', () => {
- const { $view, setNameTo } = setup();
- setNameTo('cluster2:logstash-*');
-
- const $name = $view.findTestSubject('createIndexPatternNameInput');
- const classes = [...$name.get(0).classList];
- expect(classes).to.contain('ng-valid');
- expect(classes).to.not.contain('ng-invalid');
- });
- });
-});
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/create_index_pattern.html b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/create_index_pattern.html
deleted file mode 100644
index b3a65d8db7992..0000000000000
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/create_index_pattern.html
+++ /dev/null
@@ -1,144 +0,0 @@
-
-
-
-
- Configure an 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.
-
-
-
-
-
-
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/create_index_pattern.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/create_index_pattern.js
deleted file mode 100644
index 44cb9cdaa6a3f..0000000000000
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/create_index_pattern.js
+++ /dev/null
@@ -1,252 +0,0 @@
-import { IndexPatternMissingIndices } from 'ui/errors';
-import 'ui/directives/validate_index_name';
-import 'ui/directives/auto_select_if_only_one';
-import uiRoutes from 'ui/routes';
-import { uiModules } from 'ui/modules';
-import template from './create_index_pattern.html';
-import { sendCreateIndexPatternRequest } from './send_create_index_pattern_request';
-import { pickCreateButtonText } from './pick_create_button_text';
-
-uiRoutes
-.when('/management/kibana/index', {
- template,
-});
-
-uiModules.get('apps/management')
-.controller('managementIndicesCreate', function (
- $scope,
- $routeParams,
- kbnUrl,
- Private,
- Notifier,
- indexPatterns,
- es,
- config,
- Promise,
- $translate
-) {
- const notify = new Notifier();
- let loadingCount = 0;
-
- // Configure the new index pattern we're going to create.
- this.formValues = {
- name: config.get('indexPattern:placeholder'),
- timeFieldOption: null,
- };
-
- // UI state.
- this.timeFieldOptions = [];
- this.timeFieldOptionsError = null;
- this.showAdvancedOptions = false;
-
- // fills index-pattern ID based on query param.
- if ($routeParams.id) {
- this.formValues.id = decodeURIComponent($routeParams.id);
- this.formValues.name = '';
-
- this.showAdvancedOptions = true;
- }
-
- const getTimeFieldOptions = () => {
- loadingCount += 1;
- return Promise.resolve()
- .then(() => {
- const { name } = this.formValues;
- if (!name) {
- return [];
- }
- return indexPatterns.fieldsFetcher.fetchForWildcard(name);
- })
- .then(fields => {
- const dateFields = fields.filter(field => field.type === 'date');
-
- if (dateFields.length === 0) {
- return {
- options: [
- {
- display: `The indices which match this index pattern don't contain any time fields.`
- }
- ]
- };
- }
-
- return {
- options: [
- {
- display: `I don't want to use the Time Filter`
- },
- ...dateFields.map(field => ({
- display: field.name,
- fieldName: field.name
- })),
- ]
- };
- })
- .catch(err => {
- if (err instanceof IndexPatternMissingIndices) {
- return {
- error: 'Unable to fetch mapping. Do you have indices matching the pattern?'
- };
- }
-
- throw err;
- })
- .finally(() => {
- loadingCount -= 1;
- });
- };
-
- const findTimeFieldOption = match => {
- if (!match) return;
-
- return this.timeFieldOptions.find(option => (
- // comparison is not done with _.isEqual() because options get a unique
- // `$$hashKey` tag attached to them by ng-repeat
- option.fieldName === match.fieldName &&
- option.display === match.display
- ));
- };
-
- const pickDefaultTimeFieldOption = () => {
- const noOptions = this.timeFieldOptions.length === 0;
- // options that represent a time field
- const fieldOptions = this.timeFieldOptions.filter(option => !!option.fieldName);
- // options like "I don't want the time filter" or "There are no date fields"
- const nonFieldOptions = this.timeFieldOptions.filter(option => !option.fieldName);
- // if there are multiple field or non-field options then we can't select a default, the user must choose
- const tooManyOptions = fieldOptions.length > 1 || nonFieldOptions.length > 1;
-
- if (noOptions || tooManyOptions) {
- return null;
- }
-
- if (fieldOptions.length === 1) {
- return fieldOptions[0];
- }
-
- return nonFieldOptions[0];
- };
-
- this.isTimeBased = () => {
- if (!this.formValues.timeFieldOption) {
- // if they haven't choosen a time field, assume they will
- return true;
- }
-
- // if timeFieldOption has a fieldName it's a time field, otherwise
- // it's a way to opt-out of the time field or an indication that there
- // are no fields available
- return Boolean(this.formValues.timeFieldOption.fieldName);
- };
-
- this.isCrossClusterName = () => {
- return (
- this.formValues.name &&
- this.formValues.name.includes(':')
- );
- };
-
- this.isLoading = () => {
- return loadingCount > 0;
- };
-
- let activeRefreshTimeFieldOptionsCall;
- this.refreshTimeFieldOptions = () => {
- // if there is an active refreshTimeFieldOptions() call then we use
- // their prevOption, allowing the previous selection to persist
- // across simultaneous calls to refreshTimeFieldOptions()
- const prevOption = activeRefreshTimeFieldOptionsCall
- ? activeRefreshTimeFieldOptionsCall.prevOption
- : this.formValues.timeFieldOption;
-
- // `thisCall` is our unique "token" to verify that we are still the
- // most recent call. When we are not the most recent call we don't
- // modify the controller in any way to prevent race conditions
- const thisCall = activeRefreshTimeFieldOptionsCall = { prevOption };
-
- loadingCount += 1;
- this.timeFieldOptions = [];
- this.timeFieldOptionsError = null;
- this.formValues.timeFieldOption = null;
- getTimeFieldOptions()
- .then(({ options, error }) => {
- if (thisCall !== activeRefreshTimeFieldOptionsCall) return;
-
- this.timeFieldOptions = options;
- this.timeFieldOptionsError = error;
- if (!this.timeFieldOptions) {
- return;
- }
-
- // Restore the preivously selected state, or select the default option in the UI
- const restoredOption = findTimeFieldOption(prevOption);
- const defaultOption = pickDefaultTimeFieldOption();
- this.formValues.timeFieldOption = restoredOption || defaultOption;
- })
- .catch(notify.error)
- .finally(() => {
- loadingCount -= 1;
- if (thisCall === activeRefreshTimeFieldOptionsCall) {
- activeRefreshTimeFieldOptionsCall = null;
- }
- });
- };
-
- this.toggleAdvancedIndexOptions = () => {
- this.showAdvancedOptions = !!!this.showAdvancedOptions;
- };
-
- this.createIndexPattern = () => {
- const {
- id,
- name,
- timeFieldOption,
- } = this.formValues;
-
- const timeFieldName = timeFieldOption
- ? timeFieldOption.fieldName
- : undefined;
-
- loadingCount += 1;
- sendCreateIndexPatternRequest(indexPatterns, {
- id,
- name,
- timeFieldName,
- }).then(createdId => {
- if (!createdId) {
- return;
- }
-
- if (!config.get('defaultIndex')) {
- config.set('defaultIndex', createdId);
- }
-
- indexPatterns.cache.clear(createdId);
- kbnUrl.change(`/management/kibana/indices/${createdId}`);
-
- // force loading while kbnUrl.change takes effect
- loadingCount = Infinity;
- }).catch(err => {
- if (err instanceof IndexPatternMissingIndices) {
- return notify.error('Could not locate any indices matching that pattern. Please add the index to Elasticsearch');
- }
-
- notify.fatal(err);
- }).finally(() => {
- loadingCount -= 1;
- });
- };
-
- $scope.$watch('controller.formValues.name', () => {
- this.refreshTimeFieldOptions();
- });
-
- $scope.$watchMulti([
- 'controller.isLoading()',
- 'form.name.$error.indexNameInput',
- 'controller.formValues.timeFieldOption'
- ], ([loading, invalidIndexName, timeFieldOption]) => {
- const state = { loading, invalidIndexName, timeFieldOption };
- this.createButtonText = pickCreateButtonText($translate, state);
- });
-});
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/get_default_pattern_for_interval.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/get_default_pattern_for_interval.js
deleted file mode 100644
index 6ce16209644de..0000000000000
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/get_default_pattern_for_interval.js
+++ /dev/null
@@ -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-*';
-}
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/index.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/index.js
deleted file mode 100644
index fd1fdd05795e9..0000000000000
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/index.js
+++ /dev/null
@@ -1 +0,0 @@
-import './create_index_pattern';
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/pick_create_button_text.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/pick_create_button_text.js
deleted file mode 100644
index 03416502efbab..0000000000000
--- a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/pick_create_button_text.js
+++ /dev/null
@@ -1,21 +0,0 @@
-export function pickCreateButtonText($translate, state) {
- const {
- loading,
- invalidIndexName,
- timeFieldOption
- } = state;
-
- if (loading) {
- return 'Loading';
- }
-
- if (invalidIndexName) {
- return 'Invalid index name pattern.';
- }
-
- if (!timeFieldOption) {
- return 'Time Filter field name is required';
- }
-
- return 'Create';
-}
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/create_index_pattern_wizard.html b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/create_index_pattern_wizard.html
new file mode 100644
index 0000000000000..1869e794d3844
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/create_index_pattern_wizard.html
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+ Create index pattern
+
+
+
+
+
+
+
+
+ Include system indices
+
+
+
+
+
+
+ Kibana uses index patterns to retrieve data from Elasticsearch indices for things like visualizations.
+
+
+
+
+
+
+
+
+
+
+ Checking for Elasticsearch data
+
+
+
+
+
+
+
+ Reticulating splines...
+
+
+
+
+
+
+
+
+
+
+
+
+ Couldn't find any Elasticsearch data
+
+
+
+
+
+ You'll need to index some data into Elasticsearch before you can create an index pattern.
+
+
+ Learn how.
+
+
+
+
+
+
+
+
+ Check for new data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/create_index_pattern_wizard.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/create_index_pattern_wizard.js
new file mode 100644
index 0000000000000..bab0e309569e9
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/create_index_pattern_wizard.js
@@ -0,0 +1,282 @@
+import _ from 'lodash';
+import { IndexPatternMissingIndices } from 'ui/errors';
+import 'ui/directives/validate_index_pattern';
+import 'ui/directives/auto_select_if_only_one';
+import { documentationLinks } from 'ui/documentation_links/documentation_links';
+import uiRoutes from 'ui/routes';
+import { uiModules } from 'ui/modules';
+import template from './create_index_pattern_wizard.html';
+import { sendCreateIndexPatternRequest } from './send_create_index_pattern_request';
+import './step_index_pattern';
+import './step_time_field';
+import './matching_indices_list';
+
+uiRoutes
+.when('/management/kibana/index', {
+ template,
+});
+
+uiModules.get('apps/management')
+.controller('managementIndicesCreate', function (
+ $routeParams,
+ $scope,
+ $timeout,
+ config,
+ es,
+ indexPatterns,
+ kbnUrl,
+ Notifier,
+ Promise
+) {
+ const MAX_NUMBER_OF_MATCHING_INDICES = 20;
+ const notify = new Notifier();
+ const disabledDividerOption = {
+ isDisabled: true,
+ display: '───',
+ };
+ const noTimeFieldOption = {
+ display: `I don't want to use the Time Filter`,
+ };
+
+ this.documentationLinks = documentationLinks;
+
+ // Configure the new index pattern we're going to create.
+ this.formValues = {
+ id: $routeParams.id ? decodeURIComponent($routeParams.id) : undefined,
+ name: '',
+ expandWildcard: false,
+ timeFieldOption: undefined,
+ };
+
+ // UI state.
+ this.timeFieldOptions = [];
+ this.wizardStep = 'indexPattern';
+ this.isFetchingExistingIndices = true;
+ this.isFetchingMatchingIndices = false;
+ this.isFetchingTimeFieldOptions = false;
+ this.isCreatingIndexPattern = false;
+ this.doesIncludeSystemIndices = false;
+ let allIndices = [];
+ let matchingIndices = [];
+ let partialMatchingIndices = [];
+ this.allIndices = [];
+ this.matchingIndices = [];
+ this.partialMatchingIndices = [];
+
+ function createReasonableWait() {
+ return new Promise(resolve => {
+ // Make every fetch take a set amount of time so the user gets some feedback that something
+ // is happening.
+ $timeout(() => {
+ resolve();
+ }, 500);
+ });
+ }
+
+ function getIndices(pattern, limit = MAX_NUMBER_OF_MATCHING_INDICES) {
+ const params = {
+ index: pattern,
+ ignore: [404],
+ body: {
+ size: 0, // no hits
+ aggs: {
+ indices: {
+ terms: {
+ field: '_index',
+ size: limit,
+ }
+ }
+ }
+ }
+ };
+
+ return es.search(params)
+ .then(response => {
+ if (!response || response.error || !response.aggregations) {
+ return [];
+ }
+
+ return _.sortBy(response.aggregations.indices.buckets.map(bucket => {
+ return {
+ name: bucket.key
+ };
+ }), 'name');
+ });
+ }
+
+ const whiteListIndices = indices => {
+ if (!indices) {
+ return indices;
+ }
+
+ if (this.doesIncludeSystemIndices) {
+ return indices;
+ }
+
+ // All system indices begin with a period.
+ return indices.filter(index => !index.name.startsWith('.'));
+ };
+
+ const updateWhiteListedIndices = () => {
+ this.allIndices = whiteListIndices(allIndices);
+ this.matchingIndices = whiteListIndices(matchingIndices);
+ this.partialMatchingIndices = whiteListIndices(partialMatchingIndices);
+ };
+
+ this.onIncludeSystemIndicesChange = () => {
+ updateWhiteListedIndices();
+ };
+
+ let mostRecentFetchMatchingIndicesRequest;
+
+ this.fetchMatchingIndices = () => {
+ this.isFetchingMatchingIndices = true;
+
+ // Default to searching for all indices.
+ const exactSearchQuery = this.formValues.name;
+ let partialSearchQuery = this.formValues.name;
+
+ if (!_.endsWith(partialSearchQuery, '*')) {
+ partialSearchQuery = `${partialSearchQuery}*`;
+ }
+ if (!_.startsWith(partialSearchQuery, '*')) {
+ partialSearchQuery = `*${partialSearchQuery}`;
+ }
+
+ const thisFetchMatchingIndicesRequest = mostRecentFetchMatchingIndicesRequest = Promise.all([
+ getIndices(exactSearchQuery),
+ getIndices(partialSearchQuery),
+ createReasonableWait()
+ ])
+ .then(([
+ matchingIndicesResponse,
+ partialMatchingIndicesResponse
+ ]) => {
+ if (thisFetchMatchingIndicesRequest === mostRecentFetchMatchingIndicesRequest) {
+ matchingIndices = matchingIndicesResponse;
+ partialMatchingIndices = partialMatchingIndicesResponse;
+ updateWhiteListedIndices();
+ this.isFetchingMatchingIndices = false;
+ }
+ }).catch(error => {
+ notify.error(error);
+ });
+ };
+
+ this.fetchExistingIndices = () => {
+ this.isFetchingExistingIndices = true;
+ const allExistingLocalAndRemoteIndicesPattern = '*,*:*';
+
+ Promise.all([
+ getIndices(allExistingLocalAndRemoteIndicesPattern),
+ createReasonableWait()
+ ])
+ .then(([allIndicesResponse]) => {
+ // Cache all indices.
+ allIndices = allIndicesResponse;
+ updateWhiteListedIndices();
+ this.isFetchingExistingIndices = false;
+ }).catch(error => {
+ notify.error(error);
+ });
+ };
+
+ this.isSystemIndicesCheckBoxVisible = () => (
+ this.wizardStep === 'indexPattern'
+ );
+
+ this.goToIndexPatternStep = () => {
+ this.wizardStep = 'indexPattern';
+ };
+
+ this.goToTimeFieldStep = () => {
+ // Re-initialize this step.
+ this.formValues.timeFieldOption = undefined;
+ this.fetchTimeFieldOptions();
+ this.wizardStep = 'timeField';
+ };
+
+ this.hasIndices = () => (
+ this.allIndices.length
+ );
+
+ const extractTimeFieldsFromFields = fields => {
+ const dateFields = fields.filter(field => field.type === 'date');
+
+ if (dateFields.length === 0) {
+ return [{
+ display: `The indices which match this index pattern don't contain any time fields.`,
+ }];
+ }
+
+ return [
+ ...dateFields.map(field => ({
+ display: field.name,
+ fieldName: field.name
+ })),
+ disabledDividerOption,
+ noTimeFieldOption,
+ ];
+ };
+
+ this.fetchTimeFieldOptions = () => {
+ this.isFetchingTimeFieldOptions = true;
+ this.formValues.timeFieldOption = undefined;
+ this.timeFieldOptions = [];
+
+ Promise.all([
+ indexPatterns.fieldsFetcher.fetchForWildcard(this.formValues.name),
+ createReasonableWait(),
+ ])
+ .then(([fields]) => {
+ this.timeFieldOptions = extractTimeFieldsFromFields(fields);
+ })
+ .catch(error => {
+ notify.error(error);
+ })
+ .finally(() => {
+ this.isFetchingTimeFieldOptions = false;
+ });
+ };
+
+ this.createIndexPattern = () => {
+ this.isCreatingIndexPattern = true;
+
+ const {
+ id,
+ name,
+ timeFieldOption,
+ } = this.formValues;
+
+ const timeFieldName = timeFieldOption
+ ? timeFieldOption.fieldName
+ : undefined;
+
+ sendCreateIndexPatternRequest(indexPatterns, {
+ id,
+ name,
+ timeFieldName,
+ }).then(createdId => {
+ if (!createdId) {
+ return;
+ }
+
+ if (!config.get('defaultIndex')) {
+ config.set('defaultIndex', createdId);
+ }
+
+ indexPatterns.cache.clear(createdId);
+ kbnUrl.change(`/management/kibana/indices/${createdId}`);
+ }).catch(err => {
+ if (err instanceof IndexPatternMissingIndices) {
+ return notify.error(`Couldn't locate any indices matching that pattern. Please add the index to Elasticsearch`);
+ }
+
+ notify.fatal(err);
+ }).finally(() => {
+ this.isCreatingIndexPattern = false;
+ });
+ };
+
+ this.fetchExistingIndices();
+});
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/index.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/index.js
new file mode 100644
index 0000000000000..e6b701ce12c25
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/index.js
@@ -0,0 +1 @@
+import './create_index_pattern_wizard';
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/index.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/index.js
new file mode 100644
index 0000000000000..5571f47154ed6
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/index.js
@@ -0,0 +1 @@
+import './matching_indices_list';
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.html b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.html
new file mode 100644
index 0000000000000..51c9a2f09db44
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.html
@@ -0,0 +1,61 @@
+
+
+
+
+ Looking for matching indices
+
+
+
+
+
+
+
+
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.js
new file mode 100644
index 0000000000000..de44769bfcb43
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.js
@@ -0,0 +1,62 @@
+import 'ui/pager_control';
+import 'ui/pager';
+import { last } from 'lodash';
+import { uiModules } from 'ui/modules';
+import './matching_indices_list.less';
+import template from './matching_indices_list.html';
+
+const module = uiModules.get('apps/management');
+
+module.directive('matchingIndicesList', function ($filter, pagerFactory) {
+ return {
+ restrict: 'E',
+ replace: true,
+ template,
+ transclude: true,
+ controllerAs: 'matchingIndicesList',
+ bindToController: true,
+ scope: {
+ indices: '=',
+ pattern: '=',
+ isLoading: '=',
+ },
+ link: function (scope) {
+ scope.$watch('matchingIndicesList.indices', () => {
+ scope.matchingIndicesList.calculateItemsOnPage();
+ });
+ scope.$watch('matchingIndicesList.pattern', () => {
+ if (last(scope.matchingIndicesList.pattern) === '*') {
+ const end = scope.matchingIndicesList.pattern.length - 1;
+ scope.matchingIndicesList.formattedPattern = scope.matchingIndicesList.pattern.substring(0, end);
+ } else {
+ scope.matchingIndicesList.formattedPattern = scope.matchingIndicesList.pattern;
+ }
+ });
+ },
+ controller: function () {
+ this.pageOfIndices = [];
+
+ this.calculateItemsOnPage = () => {
+ const limitTo = $filter('limitTo');
+ this.pager.setTotalItems(this.indices.length);
+ this.pageOfIndices = limitTo(this.indices, this.pager.pageSize, this.pager.startIndex);
+ };
+
+ this.pager = pagerFactory.create(this.indices.length, 10, 1);
+
+ this.hasMultiplePages = () => {
+ return this.indices.length > this.pager.pageSize;
+ };
+
+ this.onPageNext = () => {
+ this.pager.nextPage();
+ this.calculateItemsOnPage();
+ };
+
+ this.onPagePrevious = () => {
+ this.pager.previousPage();
+ this.calculateItemsOnPage();
+ };
+ },
+ };
+});
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.less b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.less
new file mode 100644
index 0000000000000..598a153ca5055
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/matching_indices_list/matching_indices_list.less
@@ -0,0 +1,3 @@
+.matchingIndicesListLoadingPrompt {
+ min-height: 60px;
+}
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/send_create_index_pattern_request.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/send_create_index_pattern_request.js
similarity index 100%
rename from src/core_plugins/kibana/public/management/sections/indices/create_index_pattern/send_create_index_pattern_request.js
rename to src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/send_create_index_pattern_request.js
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/index.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/index.js
new file mode 100644
index 0000000000000..90b9da92c0559
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/index.js
@@ -0,0 +1 @@
+import './step_index_pattern';
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.html b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.html
new file mode 100644
index 0000000000000..3159e868baf25
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.html
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ You've entered an invalid index pattern. Please adjust it to match any of your {{stepIndexPattern.allIndices.length}} indices , below.
+
+
+
+
+
+
+
+ You only have a single index. You can create an index pattern to match it.
+
+
+
+ Your index pattern can match any of your {{stepIndexPattern.allIndices.length}} indices , below.
+
+
+
+
+
+
+
+
+
+ The index pattern you've entered doesn't match any indices. You can match any of your {{stepIndexPattern.allIndices.length}} indices , below.
+
+
+
+
+
+
+
+
+
+ Your index pattern doesn't match any indices, but you have {{stepIndexPattern.partialMatchingIndices.length}} {{stepIndexPattern.partialMatchingIndices.length > 1 ? 'indices' : 'index'}} which {{stepIndexPattern.partialMatchingIndices.length > 1 ? 'look' : 'looks'}} similar.
+
+
+
+
+
+
+
+
+
+
+
+ Success! Your index pattern matches {{stepIndexPattern.matchingIndices.length}} {{stepIndexPattern.matchingIndices.length > 1 ? 'indices' : 'index'}} .
+
+
+
+
+
+
+
+
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.js
new file mode 100644
index 0000000000000..cbf1169bc065e
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.js
@@ -0,0 +1,110 @@
+import { uiModules } from 'ui/modules';
+import './step_index_pattern.less';
+import template from './step_index_pattern.html';
+import { documentationLinks } from 'ui/documentation_links/documentation_links';
+
+const module = uiModules.get('apps/management');
+
+module.directive('stepIndexPattern', function () {
+ return {
+ restrict: 'E',
+ template,
+ replace: true,
+ controllerAs: 'stepIndexPattern',
+ bindToController: true,
+ scope: {
+ fetchExistingIndices: '&',
+ isFetchingExistingIndices: '=',
+ fetchMatchingIndices: '&',
+ isFetchingMatchingIndices: '=',
+ hasIndices: '&',
+ indexPatternName: '=',
+ allIndices: '=',
+ partialMatchingIndices: '=',
+ matchingIndices: '=',
+ goToNextStep: '&',
+ },
+ link: function (scope, element) {
+ scope.stepIndexPattern.appendedWildcard = false;
+
+ scope.$watch('stepIndexPattern.allIndices', scope.stepIndexPattern.updateList);
+ scope.$watch('stepIndexPattern.matchingIndices', scope.stepIndexPattern.updateList);
+ scope.$watch('stepIndexPattern.indexPatternName', () => {
+ if (scope.stepIndexPattern.indexPatternName && scope.stepIndexPattern.indexPatternName.length === 1) {
+ if (scope.stepIndexPattern.indexPatternName === '*') {
+ if (scope.stepIndexPattern.appendedWildcard) {
+ scope.stepIndexPattern.indexPatternName = '';
+ scope.stepIndexPattern.appendedWildcard = false;
+ }
+ } else {
+ scope.stepIndexPattern.indexPatternName += '*';
+ scope.stepIndexPattern.appendedWildcard = true;
+ setTimeout(() => element.find('#indexPatternNameField')[0].setSelectionRange(1, 1));
+ }
+ }
+ // Only send the request if there's valid input.
+ if (scope.stepIndexPattern.indexPatternNameForm && scope.stepIndexPattern.indexPatternNameForm.$valid) {
+ scope.stepIndexPattern.fetchMatchingIndices();
+ }
+
+ // If the index pattern name is invalid, we should reflect that state in the list.
+ scope.stepIndexPattern.updateList();
+ });
+ scope.$watchCollection('stepIndexPattern.indexPatternNameForm.$error', () => {
+ // If we immediately replace the input with an invalid string, then only the form state
+ // changes, but not the `indexPatternName` value, so we need to watch both.
+ scope.stepIndexPattern.updateList();
+ });
+ },
+ controller: function () {
+ this.matchingIndicesListType = 'noMatches';
+ this.documentationLinks = documentationLinks;
+
+ this.canGoToNextStep = () => (
+ !this.isFetchingMatchingIndices
+ && !this.indexPatternNameForm.$invalid
+ && this.hasExactMatches()
+ );
+
+ const hasInvalidIndexPattern = () => (
+ this.indexPatternNameForm
+ && !this.indexPatternNameForm.$error.required
+ && this.indexPatternNameForm.$error.indexPattern
+ );
+
+ const hasNoInput = () => (
+ !this.indexPatternName
+ || !this.indexPatternName.trim()
+ );
+
+ this.hasExactMatches = () => (
+ this.matchingIndices.length
+ );
+
+ const hasPartialMatches = () => (
+ !this.matchingIndices.length
+ && this.partialMatchingIndices.length
+ );
+
+ this.updateList = () => {
+ if (hasInvalidIndexPattern()) {
+ return this.matchingIndicesListType = 'invalidIndexPattern';
+ }
+
+ if (hasNoInput()) {
+ return this.matchingIndicesListType = 'noInput';
+ }
+
+ if (this.hasExactMatches()) {
+ return this.matchingIndicesListType = 'exactMatches';
+ }
+
+ if (hasPartialMatches()) {
+ return this.matchingIndicesListType = 'partialMatches';
+ }
+
+ this.matchingIndicesListType = 'noMatches';
+ };
+ },
+ };
+});
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.less b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.less
new file mode 100644
index 0000000000000..fc1ba64b06723
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_index_pattern/step_index_pattern.less
@@ -0,0 +1,9 @@
+.createIndexPatternInputContainer {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+}
+
+.createIndexPatternInputField.ng-untouched {
+ border-color: #dedede !important;
+}
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/index.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/index.js
new file mode 100644
index 0000000000000..02994a505bd11
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/index.js
@@ -0,0 +1 @@
+import './step_time_field';
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.html b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.html
new file mode 100644
index 0000000000000..778154edc6daa
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.html
@@ -0,0 +1,174 @@
+
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.js b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.js
new file mode 100644
index 0000000000000..073ec8c90d37c
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.js
@@ -0,0 +1,68 @@
+import 'ui/toggle_panel';
+import { uiModules } from 'ui/modules';
+import './step_time_field.less';
+import template from './step_time_field.html';
+
+const module = uiModules.get('apps/management');
+
+module.directive('stepTimeField', function () {
+ return {
+ restrict: 'E',
+ template,
+ replace: true,
+ controllerAs: 'stepTimeField',
+ bindToController: true,
+ scope: {
+ indexPatternId: '=',
+ indexPatternName: '=',
+ timeFieldOptions: '=',
+ selectedTimeFieldOption: '=',
+ fetchTimeFieldOptions: '&',
+ isFetchingTimeFieldOptions: '=',
+ goToPreviousStep: '&',
+ createIndexPattern: '&',
+ },
+ controller: function () {
+ this.isTimeFieldSelectDisabled = () => (
+ this.isFetchingTimeFieldOptions
+ || this.timeFieldOptionsError
+ );
+
+ this.isFormValid = () => (
+ this.form.$valid
+ );
+
+ this.hasTimeFieldOptions = () => (
+ this.timeFieldOptions.length > 1
+ );
+
+ this.canCreateIndexPattern = () => (
+ !this.timeFieldOptionsError
+ && !this.isFetchingTimeFieldOptions
+ && this.isFormValid()
+ );
+
+ this.canShowMainSelect = () => (
+ !this.isFetchingTimeFieldOptions && this.hasTimeFieldOptions()
+ );
+
+ this.canShowLoadingSelect = () => (
+ this.isFetchingTimeFieldOptions
+ );
+
+ this.canShowNoTimeBasedFieldsMessage = () => (
+ !this.isFetchingTimeFieldOptions && !this.hasTimeFieldOptions()
+ );
+
+ this.canShowHelpText = () => (
+ this.isFetchingTimeFieldOptions || this.hasTimeFieldOptions()
+ );
+
+ this.toggleAdvancedOptions = () => {
+ this.showAdvancedOptions = !this.showAdvancedOptions;
+ };
+
+ this.showAdvancedOptions = !!this.indexPatternId;
+ },
+ };
+});
diff --git a/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.less b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.less
new file mode 100644
index 0000000000000..3548c6d861ade
--- /dev/null
+++ b/src/core_plugins/kibana/public/management/sections/indices/create_index_pattern_wizard/step_time_field/step_time_field.less
@@ -0,0 +1,9 @@
+/**
+ * 1. Match select width.
+ */
+.timeFieldNameLabel {
+ width: 400px; /* 1 */
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
diff --git a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.html b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.html
index 94a12aa777b0b..4257def01ebdc 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.html
+++ b/src/core_plugins/kibana/public/management/sections/indices/edit_index_pattern/edit_index_pattern.html
@@ -15,7 +15,8 @@
- Time Filter field name : {{indexPattern.timeFieldName}}
+
+ Time Filter field name : {{indexPattern.timeFieldName}}
diff --git a/src/core_plugins/kibana/public/management/sections/indices/index.html b/src/core_plugins/kibana/public/management/sections/indices/index.html
index 71fac6935ae14..0ae82644399a1 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/index.html
+++ b/src/core_plugins/kibana/public/management/sections/indices/index.html
@@ -6,7 +6,6 @@
ng-if="editingId"
href="#/management/kibana/index"
class="kuiButton kuiButton--primary kuiButton--small"
- aria-label="Create Index Pattern"
>
Create Index Pattern
@@ -20,7 +19,9 @@
>
diff --git a/src/core_plugins/kibana/public/management/sections/indices/index.js b/src/core_plugins/kibana/public/management/sections/indices/index.js
index 5fedf3ca08be2..8c04db6774ef9 100644
--- a/src/core_plugins/kibana/public/management/sections/indices/index.js
+++ b/src/core_plugins/kibana/public/management/sections/indices/index.js
@@ -1,5 +1,5 @@
import { management } from 'ui/management';
-import './create_index_pattern';
+import './create_index_pattern_wizard';
import './edit_index_pattern';
import uiRoutes from 'ui/routes';
import { uiModules } from 'ui/modules';
diff --git a/src/ui/public/directives/__tests__/validate_index_name.js b/src/ui/public/directives/__tests__/validate_index_pattern.js
similarity index 91%
rename from src/ui/public/directives/__tests__/validate_index_name.js
rename to src/ui/public/directives/__tests__/validate_index_pattern.js
index 26c43f3be3148..68998e35107fc 100644
--- a/src/ui/public/directives/__tests__/validate_index_name.js
+++ b/src/ui/public/directives/__tests__/validate_index_pattern.js
@@ -1,15 +1,15 @@
import expect from 'expect.js';
import ngMock from 'ng_mock';
-import 'ui/directives/validate_index_name';
+import 'ui/directives/validate_index_pattern';
// Load the kibana app dependencies.
-describe('Validate index name directive', function () {
+describe('Validate index pattern directive', function () {
let $compile;
let $rootScope;
- const noWildcardHtml = ' ';
- const requiredHtml = ' ';
- const allowWildcardHtml = ' ';
+ const noWildcardHtml = ' ';
+ const requiredHtml = ' ';
+ const allowWildcardHtml = ' ';
beforeEach(ngMock.module('kibana'));
diff --git a/src/ui/public/directives/info.js b/src/ui/public/directives/info.js
index ee0ccbf1eb0ef..5099c72fba922 100644
--- a/src/ui/public/directives/info.js
+++ b/src/ui/public/directives/info.js
@@ -1,4 +1,4 @@
-import html from 'ui/partials/info.html';
+import template from 'ui/partials/info.html';
import { uiModules } from 'ui/modules';
uiModules
@@ -10,7 +10,7 @@ uiModules
info: '@',
placement: '@'
},
- template: html,
+ template,
link: function ($scope) {
$scope.placement = $scope.placement || 'top';
}
diff --git a/src/ui/public/directives/validate_index_name.js b/src/ui/public/directives/validate_index_pattern.js
similarity index 74%
rename from src/ui/public/directives/validate_index_name.js
rename to src/ui/public/directives/validate_index_pattern.js
index 6c20e34560a09..27759250fd246 100644
--- a/src/ui/public/directives/validate_index_name.js
+++ b/src/ui/public/directives/validate_index_pattern.js
@@ -4,13 +4,17 @@ import { uiModules } from 'ui/modules';
uiModules
.get('kibana')
- .directive('validateIndexName', function () {
+ .directive('validateIndexPattern', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function ($scope, elem, attr, ngModel) {
const illegalCharacters = ['\\', '/', '?', '"', '<', '>', '|', ' ', ','];
- const allowWildcard = !_.isUndefined(attr.allowWildcard) && attr.allowWildcard !== 'false';
+
+ const allowWildcard =
+ !_.isUndefined(attr.validateIndexPatternAllowWildcard)
+ && attr.validateIndexPatternAllowWildcard !== 'false';
+
if (!allowWildcard) {
illegalCharacters.push('*');
}
@@ -26,7 +30,7 @@ uiModules
return !match;
};
- ngModel.$validators.indexNameInput = function (modelValue, viewValue) {
+ ngModel.$validators.indexPattern = function (modelValue, viewValue) {
return isValid(viewValue);
};
}
diff --git a/src/ui/public/documentation_links/documentation_links.js b/src/ui/public/documentation_links/documentation_links.js
index 6355b8c249631..a776994d629bc 100644
--- a/src/ui/public/documentation_links/documentation_links.js
+++ b/src/ui/public/documentation_links/documentation_links.js
@@ -21,6 +21,10 @@ export const documentationLinks = {
painlessSyntax: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/modules-scripting-painless-syntax.html`,
luceneExpressions: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/modules-scripting-expression.html`
},
+ indexPatterns: {
+ loadingData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/tutorial-load-dataset.html`,
+ introduction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index-patterns.html`,
+ },
query: {
luceneQuerySyntax:
`${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/query-dsl-query-string-query.html#query-string-syntax`,
diff --git a/src/ui/public/partials/info.html b/src/ui/public/partials/info.html
index 01a0670fb7a6a..65091ced75b09 100644
--- a/src/ui/public/partials/info.html
+++ b/src/ui/public/partials/info.html
@@ -1,4 +1,10 @@
-
\ No newline at end of file
+
+
+
diff --git a/test/functional/apps/management/_create_index_pattern_wizard.js b/test/functional/apps/management/_create_index_pattern_wizard.js
new file mode 100644
index 0000000000000..f2cf5e69fd384
--- /dev/null
+++ b/test/functional/apps/management/_create_index_pattern_wizard.js
@@ -0,0 +1,35 @@
+import expect from 'expect.js';
+
+export default function ({ getService, getPageObjects }) {
+ const kibanaServer = getService('kibanaServer');
+ const PageObjects = getPageObjects(['settings', 'common']);
+
+ describe('"Create Index Pattern" wizard', function () {
+ beforeEach(function () {
+ // delete .kibana index and then wait for Kibana to re-create it
+ return kibanaServer.uiSettings.replace({})
+ .then(function () {
+ return PageObjects.settings.navigateTo();
+ })
+ .then(function () {
+ return PageObjects.settings.clickKibanaIndices();
+ });
+ });
+
+ describe('step 1 next button', function () {
+ it('is disabled by default', async function () {
+ const btn = await PageObjects.settings.getCreateIndexPatternGoToStep2Button();
+ const isEnabled = await btn.isEnabled();
+ expect(isEnabled).not.to.be.ok();
+ });
+
+ it('is enabled once an index pattern with matching indices has been entered', async function () {
+ await PageObjects.settings.setIndexPatternField();
+ await PageObjects.common.sleep(1000);
+ const btn = await PageObjects.settings.getCreateIndexPatternGoToStep2Button();
+ const isEnabled = await btn.isEnabled();
+ expect(isEnabled).to.be.ok();
+ });
+ });
+ });
+}
diff --git a/test/functional/apps/management/_creation_form_changes.js b/test/functional/apps/management/_creation_form_changes.js
deleted file mode 100644
index 7a4ac392f0d5f..0000000000000
--- a/test/functional/apps/management/_creation_form_changes.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import expect from 'expect.js';
-
-export default function ({ getService, getPageObjects }) {
- const kibanaServer = getService('kibanaServer');
- const screenshots = getService('screenshots');
- const PageObjects = getPageObjects(['settings', 'common']);
-
- describe('user input reactions', function () {
- beforeEach(function () {
- // delete .kibana index and then wait for Kibana to re-create it
- return kibanaServer.uiSettings.replace({})
- .then(function () {
- return PageObjects.settings.navigateTo();
- })
- .then(function () {
- return PageObjects.settings.clickKibanaIndices();
- });
- });
-
- it('should enable creation after selecting time field', async function () {
- // select a time field and check that Create button is enabled
- await PageObjects.settings.selectTimeFieldOption('@timestamp');
- const createButton = await PageObjects.settings.getCreateButton();
- const enabled = await createButton.isEnabled();
- screenshots.take('Settings-indices-enable-creation');
- expect(enabled).to.be.ok();
- });
- });
-}
diff --git a/test/functional/apps/management/_index_pattern_create_delete.js b/test/functional/apps/management/_index_pattern_create_delete.js
index 726e8154a6d20..561a6096a552a 100644
--- a/test/functional/apps/management/_index_pattern_create_delete.js
+++ b/test/functional/apps/management/_index_pattern_create_delete.js
@@ -84,7 +84,7 @@ export default function ({ getService, getPageObjects }) {
it('should return to index pattern creation page', function returnToPage() {
return retry.try(function tryingForTime() {
- return PageObjects.settings.getCreateButton();
+ return PageObjects.settings.getCreateIndexPatternGoToStep2Button();
});
});
diff --git a/test/functional/apps/management/_initial_state.js b/test/functional/apps/management/_initial_state.js
deleted file mode 100644
index 4d346b0b4f42b..0000000000000
--- a/test/functional/apps/management/_initial_state.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import expect from 'expect.js';
-
-export default function ({ getService, getPageObjects }) {
- const kibanaServer = getService('kibanaServer');
- const log = getService('log');
- const PageObjects = getPageObjects(['settings', 'common']);
-
- describe('initial state', function () {
- before(function () {
- // delete .kibana index and then wait for Kibana to re-create it
- return kibanaServer.uiSettings.replace({})
- .then(function () {
- return PageObjects.settings.navigateTo();
- })
- .then(function () {
- return PageObjects.settings.clickKibanaIndices();
- });
- });
-
- it('should contain default index pattern', async function () {
- const defaultPattern = 'logstash-*';
-
- const indexPatternField = await PageObjects.settings.getIndexPatternField();
- const pattern = await indexPatternField.getProperty('value');
- expect(pattern).to.be(defaultPattern);
- });
-
- it('should not select the time field', async function () {
- const timeFieldNameField = await PageObjects.settings.getTimeFieldNameField();
- const timeFieldIsSelected = await timeFieldNameField.isSelected();
- log.debug('timeField isSelected = ' + timeFieldIsSelected);
- expect(timeFieldIsSelected).to.not.be.ok();
- });
-
- it('should not enable creation', async function () {
- const createIndexPatternButton = await PageObjects.settings.getCreateIndexPatternButton();
- const enabled = await createIndexPatternButton.isEnabled();
- expect(enabled).to.not.be.ok();
- });
- });
-}
diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.js
index afaea2d42cff7..df5de51c23572 100644
--- a/test/functional/apps/management/index.js
+++ b/test/functional/apps/management/index.js
@@ -15,8 +15,7 @@ export default function ({ getService, loadTestFile }) {
await esArchiver.unload('empty_kibana');
});
- loadTestFile(require.resolve('./_initial_state'));
- loadTestFile(require.resolve('./_creation_form_changes'));
+ loadTestFile(require.resolve('./_create_index_pattern_wizard'));
loadTestFile(require.resolve('./_index_pattern_create_delete'));
loadTestFile(require.resolve('./_index_pattern_results_sort'));
loadTestFile(require.resolve('./_index_pattern_popularity'));
diff --git a/test/functional/page_objects/settings_page.js b/test/functional/page_objects/settings_page.js
index 5bf1b94a6f668..efd828cc495be 100644
--- a/test/functional/page_objects/settings_page.js
+++ b/test/functional/page_objects/settings_page.js
@@ -271,14 +271,16 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
await PageObjects.header.waitUntilLoadingHasFinished();
}
- async createIndexPattern(indexPatternName = 'logstash-*', timefield = '@timestamp') {
+ async createIndexPattern(indexPatternName, timefield = '@timestamp') {
await retry.try(async () => {
await this.navigateTo();
await this.clickKibanaIndices();
await this.setIndexPatternField(indexPatternName);
+ await PageObjects.common.sleep(2000);
+ await (await this.getCreateIndexPatternGoToStep2Button()).click();
+ await PageObjects.common.sleep(2000);
await this.selectTimeFieldOption(timefield);
- const createButton = await this.getCreateButton();
- await createButton.click();
+ await (await this.getCreateIndexPatternCreateButton()).click();
});
await PageObjects.header.waitUntilLoadingHasFinished();
await retry.try(async () => {
@@ -303,11 +305,20 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
return indexPatternId;
}
- async setIndexPatternField(pattern) {
- log.debug(`setIndexPatternField(${pattern})`);
- return await testSubjects.setValue('createIndexPatternNameInput', pattern);
+ async setIndexPatternField(indexPatternName = 'logstash-') {
+ log.debug(`setIndexPatternField(${indexPatternName})`);
+ const field = await this.getIndexPatternField();
+ await field.clearValue();
+ field.type(indexPatternName);
}
+ async getCreateIndexPatternGoToStep2Button() {
+ return await testSubjects.find('createIndexPatternGoToStep2Button');
+ }
+
+ async getCreateIndexPatternCreateButton() {
+ return await testSubjects.find('createIndexPatternCreateButton');
+ }
async removeIndexPattern() {
let alertText;
diff --git a/ui_framework/dist/ui_framework.css b/ui_framework/dist/ui_framework.css
index f56f5286a2ccb..b2959cfd56446 100644
--- a/ui_framework/dist/ui_framework.css
+++ b/ui_framework/dist/ui_framework.css
@@ -1469,6 +1469,7 @@ main {
.kuiInfoButton {
font-size: 16px;
+ line-height: 0;
background-color: transparent;
color: #0079a5;
cursor: pointer;
@@ -2623,6 +2624,33 @@ main {
border: 1px solid #D9D9D9;
border-radius: 4px; }
+.kuiPanel--prompt {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-box-align: center;
+ -webkit-align-items: center;
+ -ms-flex-align: center;
+ align-items: center;
+ text-align: center;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ min-height: 300px; }
+ .kuiPanel--prompt .kuiPanelBody {
+ padding: 30px;
+ max-width: 500px; }
+
+.kuiPanel--noBorder {
+ border: none; }
+
.kuiPanel--withToolBar {
border-top: none;
border-radius: 0; }
@@ -2699,9 +2727,14 @@ main {
outline: none;
border-color: #0079a5; }
+/**
+ * 1. This way we can use h1, h2, etc.
+ */
.kuiPanelHeader__title {
font-size: 18px;
- line-height: 1.5; }
+ line-height: 1.5;
+ margin: 0;
+ /* 1 */ }
/**
* 1. Undo what barSection mixin does.
@@ -2885,7 +2918,7 @@ main {
/* 2 */ }
/**
- * 1. Make seamless transition from ToolBar to Table header.
+ * 1. Make seamless transition from ToolBar to Table header and contained Menu.
* 1. Make seamless transition from Table to ToolBarFooter header.
*/
.kuiControlledTable .kuiTable {
@@ -2896,6 +2929,10 @@ main {
border-top: none;
/* 2 */ }
+.kuiControlledTable .kuiMenu--contained {
+ border-top: none;
+ /* 1 */ }
+
/**
* 1. Prevent cells from expanding based on content size. This substitutes for table-layout: fixed.
*/
diff --git a/ui_framework/src/components/info_button/_info_button.scss b/ui_framework/src/components/info_button/_info_button.scss
index 104dbb9d7cbde..0bd55d7506ddc 100644
--- a/ui_framework/src/components/info_button/_info_button.scss
+++ b/ui_framework/src/components/info_button/_info_button.scss
@@ -1,5 +1,6 @@
.kuiInfoButton {
font-size: 16px;
+ line-height: 0;
background-color: transparent;
color: $globalLinkColor;
cursor: pointer;
diff --git a/ui_framework/src/components/panel/_panel.scss b/ui_framework/src/components/panel/_panel.scss
index cf8e398fab2fb..bd0e55b41f563 100644
--- a/ui_framework/src/components/panel/_panel.scss
+++ b/ui_framework/src/components/panel/_panel.scss
@@ -3,12 +3,29 @@
border-radius: $globalBorderRadius;
}
+.kuiPanel--prompt {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ justify-content: center;
+ min-height: 300px;
+
+ .kuiPanelBody {
+ padding: 30px;
+ max-width: 500px;
+ }
+}
+
+.kuiPanel--noBorder {
+ border: none;
+}
+
.kuiPanel--withToolBar {
border-top: none;
border-radius: 0;
}
-
.kuiPanel--centered {
display: flex;
justify-content: center;
@@ -25,9 +42,13 @@
border-bottom: $globalBorderThin;
}
+ /**
+ * 1. This way we can use h1, h2, etc.
+ */
.kuiPanelHeader__title {
font-size: $globalTitleFontSize;
line-height: $globalLineHeight;
+ margin: 0; /* 1 */
}
/**
diff --git a/ui_framework/src/components/table/_controlled_table.scss b/ui_framework/src/components/table/_controlled_table.scss
index 57993e52bdced..8d8d241685bad 100644
--- a/ui_framework/src/components/table/_controlled_table.scss
+++ b/ui_framework/src/components/table/_controlled_table.scss
@@ -1,5 +1,5 @@
/**
- * 1. Make seamless transition from ToolBar to Table header.
+ * 1. Make seamless transition from ToolBar to Table header and contained Menu.
* 1. Make seamless transition from Table to ToolBarFooter header.
*/
.kuiControlledTable {
@@ -10,4 +10,8 @@
.kuiToolBarFooter {
border-top: none; /* 2 */
}
+
+ .kuiMenu--contained {
+ border-top: none; /* 1 */
+ }
}