diff --git a/.gitignore b/.gitignore index 687857fc490af..5cc67775074e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .DS_Store node_modules src/bower_components -**/styles/*.css +**/*.css trash build target diff --git a/src/kibana/apps/dashboard/index.html b/src/kibana/apps/dashboard/index.html index 536699645c16c..a1b1d8235e17b 100644 --- a/src/kibana/apps/dashboard/index.html +++ b/src/kibana/apps/dashboard/index.html @@ -5,11 +5,19 @@ {{dash.title}} -
- - + +
+ + +
diff --git a/src/kibana/apps/dashboard/index.js b/src/kibana/apps/dashboard/index.js index ea2437fb62338..940959a4c1ea5 100644 --- a/src/kibana/apps/dashboard/index.js +++ b/src/kibana/apps/dashboard/index.js @@ -85,11 +85,7 @@ define(function (require) { function updateQueryOnRootSource() { if ($state.query) { dash.searchSource.set('filter', { - query: { - query_string: { - query: $state.query - } - } + query: $state.query }); } else { dash.searchSource.set('filter', null); diff --git a/src/kibana/apps/discover/controllers/discover.js b/src/kibana/apps/discover/controllers/discover.js index 8c3678e27b1bd..a81af5c3a7f61 100644 --- a/src/kibana/apps/discover/controllers/discover.js +++ b/src/kibana/apps/discover/controllers/discover.js @@ -15,6 +15,7 @@ define(function (require) { require('filters/moment'); require('components/courier/courier'); require('components/index_patterns/index_patterns'); + require('components/query_input/query_input'); require('components/state_management/app_state'); require('services/timefilter'); @@ -70,7 +71,7 @@ define(function (require) { var defaultFormat = courier.indexPatterns.fieldFormats.defaultByType.string; var stateDefaults = { - query: initialQuery ? initialQuery.query_string.query : '', + query: initialQuery || '', columns: ['_source'], index: config.get('defaultIndex'), interval: 'auto' @@ -291,11 +292,7 @@ define(function (require) { } return sort; }) - .query(!$state.query ? null : { - query_string: { - query: $state.query - } - }); + .query(!$state.query ? null : $state.query); // get the current indexPattern var indexPattern = $scope.searchSource.get('index'); diff --git a/src/kibana/apps/discover/index.html b/src/kibana/apps/discover/index.html index 5b68be96dcee6..b90f272890d5b 100644 --- a/src/kibana/apps/discover/index.html +++ b/src/kibana/apps/discover/index.html @@ -1,9 +1,18 @@
-
- - - + +
+ + + +
diff --git a/src/kibana/apps/visualize/controllers/editor.js b/src/kibana/apps/visualize/controllers/editor.js index bd4de16da3465..1ec01328ffbd8 100644 --- a/src/kibana/apps/visualize/controllers/editor.js +++ b/src/kibana/apps/visualize/controllers/editor.js @@ -228,7 +228,7 @@ define(function (require) { delete vis.savedSearchId; var q = vis.searchSource.get('query'); - $state.query = _.isObject(q) ? q.query_string.query : q; + $state.query = q; var parent = vis.searchSource.parent(); // we will copy over all state minus the "aggs" @@ -249,7 +249,7 @@ define(function (require) { delete $state.query; } else { var q = $state.query || vis.searchSource.get('query'); - $state.query = _.isObject(q) ? q.query_string.query : q; + $state.query = q; } // init diff --git a/src/kibana/apps/visualize/editor.html b/src/kibana/apps/visualize/editor.html index 267e292639ba9..a76cadc9edab0 100644 --- a/src/kibana/apps/visualize/editor.html +++ b/src/kibana/apps/visualize/editor.html @@ -17,13 +17,19 @@ Unlinked!
-
- - + +
+ + +
diff --git a/src/kibana/components/query_input/query_input.js b/src/kibana/components/query_input/query_input.js new file mode 100644 index 0000000000000..4e0f50523748b --- /dev/null +++ b/src/kibana/components/query_input/query_input.js @@ -0,0 +1,112 @@ +define(function (require) { + var _ = require('lodash'); + var $ = require('jquery'); + + require('css!components/query_input/query_input.css'); + + require('modules') + .get('kibana') + .directive('queryInput', function (es, $compile, timefilter, configFile) { + return { + restrict: 'A', + require: 'ngModel', + scope: { + 'ngModel': '=', + 'queryInput': '=?', + }, + link: function ($scope, elem, attr, ngModel) { + + // track request so we can abort it if needed + var request = {}; + + var errorElem = $('').hide(); + + var init = function () { + elem.after(errorElem); + validater($scope.ngModel); + }; + + var validater = function (query) { + var index, type; + + var error = function (resp) { + ngModel.$setValidity('queryInput', false); + + errorElem.attr('tooltip', resp.explanations && resp.explanations[0] ? + resp.explanations[0].error : undefined); + + // Compile is needed for the tooltip + $compile(errorElem)($scope); + errorElem.show(); + + return undefined; + }; + + var success = function (resp) { + if (resp.valid) { + ngModel.$setValidity('queryInput', true); + errorElem.hide(); + return query; + } else { + return error(resp); + } + }; + + if ($scope.queryInput) { + index = $scope.queryInput.get('index').toIndexList(); + } else { + index = configFile.kibanaIndex; + type = '__kibanaQueryValidator'; + } + + if (request.abort) request.abort(); + + request = es.indices.validateQuery({ + index: index, + type: type, + explain: true, + ignoreUnavailable: true, + body: { + query: query || { match_all: {} } + } + }).then(success, error); + }; + + var debouncedValidator = _.debounce(validater, 300); + + + // What should I make with the input from the user? + var fromUser = function (text) { + try { + return JSON.parse(text); + } catch (e) { + return { + query_string: { + query: text || '*' + } + }; + } + }; + + // How should I present the data back to the user in the input field? + var toUser = function (text) { + if (_.isString(text)) return text; + if (_.isObject(text)) { + if (text.query_string) return text.query_string.query; + return JSON.stringify(text); + } + return undefined; + }; + + ngModel.$parsers.push(fromUser); + ngModel.$formatters.push(toUser); + + // Use a model watch instead of parser/formatter. Debounced anyway. Parsers require the + // user to actually enter input, which may not happen if the back button is clicked + $scope.$watch('ngModel', debouncedValidator); + + init(); + } + }; + }); +}); \ No newline at end of file diff --git a/src/kibana/components/query_input/query_input.less b/src/kibana/components/query_input/query_input.less new file mode 100644 index 0000000000000..6dacbdfde9e2d --- /dev/null +++ b/src/kibana/components/query_input/query_input.less @@ -0,0 +1,11 @@ +@import (reference) "../../styles/_bootstrap.less"; +@import (reference) "../../styles/theme/_theme.less"; +@import (reference) "../../styles/_variables.less"; + +i.query-input-error { + position: absolute; + margin-left: -25px; + color: @brand-danger; + margin-top: 10px; + z-index: 5; +} \ No newline at end of file diff --git a/src/kibana/styles/_navbar.less b/src/kibana/styles/_navbar.less index 7ac1ce95aa141..edd8ec18a332c 100644 --- a/src/kibana/styles/_navbar.less +++ b/src/kibana/styles/_navbar.less @@ -76,7 +76,8 @@ navbar { // horizontal group of buttons/form elements .button-group, - .inline-form { + .inline-form .input-group { + margin-bottom: 0px; .display(flex); > * { @@ -90,7 +91,7 @@ navbar { } } - .inline-form { + .inline-form .input-group { input { height: auto; } diff --git a/tasks/config/less.js b/tasks/config/less.js index 81bec21eb94ee..108cc50659589 100644 --- a/tasks/config/less.js +++ b/tasks/config/less.js @@ -3,6 +3,7 @@ var bc = require('path').join(__dirname, '../../src/bower_components'); module.exports = { src: { src: [ + '<%= src %>/kibana/components/*/*.less', '<%= src %>/kibana/apps/dashboard/styles/main.less', '<%= src %>/kibana/apps/discover/styles/main.less', '<%= src %>/kibana/apps/settings/styles/main.less',