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. -

- -
- -
- - -
- - -
- -
- - -
-

- - - {{controller.timeFieldOptionsError}} - -

-
- - -
-

- Patterns allow you to define dynamic index names using * as a wildcard. Example: logstash-* -

-
-
- - -
- - -
- -
- - -
-

- Creates the index pattern with the specified ID. -

-
-
- - -
- - -
- -
-
- - - -
-
-
-
-
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 +

+ +
+ + +
+
+ +

+ 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. + +

+
+ + +
+
+
+
+
+ + +
+ + + + + +
+
+
+
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 +

+ +
+

+ Just a sec... +

+
+
+
+ +
+ +
+
+

+ +

+
+ +
+ + + + +
+
+ + +
    +
  • +

    +

    +
  • +
+
+
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 @@ +
+
+
+

+ Step 1 of 2: Define index pattern +

+
+
+ +
+
+
+
+ +
+ + +
+ +
+ +

+ You can use a * as a wildcard in your index pattern. +

+ +

+ You can't use empty spaces or the characters \ / ? " < > , |. +

+
+ + + +
+
+ + +
+
+ + + + + + 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 @@ +
+
+
+

+ Step 2 of 2: Configure settings +

+
+
+ +
+

+ You've defined {{stepTimeField.indexPatternName}} as your index pattern. Now you can specify some settings before we create it. +

+ + +
+
+ + + + Refresh + + +

+ +

+
+ +
+ + +

+ The indices which match this index pattern don't contain any time fields. +

+
+ +

+ The Time Filter will use this field to filter your data by time. + You can choose not to have a time field, but you will not be able to narrow down your data by a time range. +

+
+ +
+ +
+ + +
+ + +
+ +
+ +

+ Kibana will provide a unique identifier for each index pattern. + If you do not want to use this unique ID, enter a custom one. +

+
+ + +
+
+
+ + + +
+
+
+
+
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 */ + } }