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',