Merge pull request #5208 from BigFunger/parse_query

removed validate-query directive. replaced with parse-query directive
This commit is contained in:
Joe Fleming 2015-10-28 13:10:41 -07:00
commit a2dac5d6b4
11 changed files with 41 additions and 242 deletions

View file

@ -11,14 +11,15 @@
<div class="input-group"
ng-class="queryInput.$invalid ? 'has-error' : ''">
<input type="text"
<input
type="text"
placeholder="Filter..."
aria-label="Filter input"
class="form-control"
ng-model="state.query"
input-focus
kbn-typeahead-input
validate-query>
parse-query>
<button type="submit" class="btn btn-default" ng-disabled="queryInput.$invalid" aria-label="Filter dashboards">
<span aria-hidden="true" class="fa fa-search"></span>
</button>

View file

@ -14,7 +14,6 @@ define(function (require) {
require('ui/timepicker');
require('ui/fixedScroll');
require('ui/directives/validate_json');
require('ui/validate_query');
require('ui/filters/moment');
require('ui/courier');
require('ui/index_patterns');
@ -246,8 +245,7 @@ define(function (require) {
}()));
$scope.searchSource.onError(function (err) {
console.log(err);
notify.error('An error occurred with your request. Reset your inputs and try again.');
notify.error(err);
}).catch(notify.fatal);
function initForTime() {

View file

@ -4,7 +4,8 @@
<div class="typeahead" kbn-typeahead="discover">
<div class="input-group"
ng-class="discoverSearch.$invalid ? 'has-error' : ''">
<input validate-query="searchSource"
<input
parse-query
input-focus
kbn-typeahead-input
ng-model="state.query"
@ -16,7 +17,6 @@
ng-disabled="discoverSearch.$invalid"
aria-label="Search">
<span aria-hidden="true" class="fa fa-search"></span></button>
<!--<button type="button" ng-click="resetQuery()" aria-label="Reset query"><span aria-hidden="true" class="fa fa-ban"></span></button>-->
</div>
<kbn-typeahead-items></kbn-typeahead-items>
</div>

View file

@ -46,7 +46,7 @@
ng-class="queryInput.$invalid ? 'has-error' : ''">
<input
ng-model="state.query"
validate-query
parse-query
input-focus
kbn-typeahead-input
placeholder="Search..."

View file

@ -52,6 +52,7 @@ exports.reload = function () {
'ui/index_patterns',
'ui/listen',
'ui/notify',
'ui/parse_query',
'ui/persisted_log',
'ui/private',
'ui/promises',
@ -67,7 +68,6 @@ exports.reload = function () {
'ui/typeahead',
'ui/url',
'ui/validateDateInterval',
'ui/validate_query',
'ui/watch_multi'
];

View file

@ -23,7 +23,8 @@
</div>
<div class="form-group">
<input validate-query
<input
parse-query
ng-model="filter.input.query"
type="text"
class="form-control"

View file

@ -4,7 +4,7 @@ var expect = require('expect.js');
var ngMock = require('ngMock');
// Load the kibana app dependencies.
require('ui/validate_query');
require('ui/parse_query');
var $rootScope;
var $timeout;
@ -18,42 +18,15 @@ var $elem;
var cycleIndex = 0;
var mockValidateQuery;
var markup = '<input ng-model="mockModel" validate-query="mockQueryInput" input-focus type="text">';
var markup = '<input ng-model="mockModel" parse-query input-focus type="text">';
var fromUser;
var toUser = require('ui/validate_query/lib/to_user');
var validEsResponse = function () {
return Promise.resolve({ valid: true });
};
var invalidEsResponse = function () {
return Promise.reject({ body: { error: 'mock invalid query' } });
};
var checkClass = function (className) {
expect($elem.hasClass(className)).to.be(true);
};
var toUser = require('ui/parse_query/lib/to_user');
var init = function () {
// Load the application
ngMock.module('kibana');
ngMock.module('kibana', function ($provide) {
$provide.service('es', function () {
return { indices: { validateQuery: function () {} } };
});
// Super simple config stub
$provide.service('config', function () {
var keys = {};
return {
get: function (key) { return keys[key]; },
set: function (key, value) { keys[key] = value; }
};
});
$provide.constant('kbnIndex', 'test-index');
});
// Create the scope
@ -66,9 +39,6 @@ var init = function () {
// Give us a scope
$rootScope = _$rootScope_;
var es = $injector.get('es');
mockValidateQuery = sinon.stub(es.indices, 'validateQuery');
});
};
@ -82,96 +52,22 @@ var compile = function () {
$rootScope.$digest();
};
describe('validate-query directive', function () {
describe('parse-query directive', function () {
describe('initialization', function () {
beforeEach(function () {
init();
mockValidateQuery.returns(validEsResponse());
compile();
});
it('should use the model', function () {
expect($elemScope).to.have.property('ngModel');
});
it('should call validate with changes', function () {
// once for init
expect(mockValidateQuery.callCount).to.be(1);
$rootScope.mockModel = 'different input';
$rootScope.$digest();
// once on change (leading edge)
expect(mockValidateQuery.callCount).to.be(2);
$timeout.flush();
// once on change (trailing edge)
expect(mockValidateQuery.callCount).to.be(3);
});
});
describe('valid querystring', function () {
var mockValidateReturns;
beforeEach(function () {
init();
mockValidateQuery.returns(validEsResponse());
compile();
});
it('should set valid state', function () {
// give angular time to set up the directive
checkClass('ng-valid-query-input');
checkClass('ng-valid');
});
});
describe('invalid querystring', function () {
var mockValidateReturns;
beforeEach(function () {
init();
mockValidateQuery.returns(invalidEsResponse());
compile();
});
it('should set invalid state', function () {
checkClass('ng-invalid');
});
});
describe('changing input', function () {
it('should change validity based on response', function () {
init();
mockValidateQuery.onCall(0).returns(validEsResponse());
compile();
$rootScope.$digest();
checkClass('ng-valid');
// leading and trailing edges
mockValidateQuery.onCall(1).returns(invalidEsResponse());
mockValidateQuery.onCall(2).returns(invalidEsResponse());
$rootScope.mockModel = 'invalid:';
// trigger model change, which fires watcher
$rootScope.$digest();
// leading edge
$timeout.flush(); // trailing edge
checkClass('ng-invalid');
// leading and trailing edges
mockValidateQuery.onCall(3).returns(validEsResponse());
mockValidateQuery.onCall(4).returns(validEsResponse());
$rootScope.mockModel = 'valid';
// trigger model change, which fires watcher
$rootScope.$digest();
// leading edge
$timeout.flush(); // trailing edge
checkClass('ng-valid');
});
});
describe('user input parser', function () {
beforeEach(function () {
fromUser = Private(require('ui/validate_query/lib/from_user'));
fromUser = Private(require('ui/parse_query/lib/from_user'));
config.set('query:queryString:options', {});
});
@ -193,7 +89,6 @@ describe('validate-query directive', function () {
expect(fromUser('')).to.eql({query_string: {query: '*', analyze_wildcard: true}});
});
it('should treat input that does not start with { as a query string', function () {
expect(fromUser('foo')).to.eql({query_string: {query: 'foo'}});
expect(fromUser('400')).to.eql({query_string: {query: '400'}});

View file

@ -0,0 +1,26 @@
define(function (require) {
require('ui/modules')
.get('kibana')
.directive('parseQuery', function (Private) {
var fromUser = Private(require('ui/parse_query/lib/from_user'));
var toUser = require('ui/parse_query/lib/to_user');
return {
restrict: 'A',
require: 'ngModel',
scope: {
'ngModel': '='
},
link: function ($scope, elem, attr, ngModel) {
var init = function () {
$scope.ngModel = fromUser($scope.ngModel);
};
ngModel.$parsers.push(fromUser);
ngModel.$formatters.push(toUser);
init();
}
};
});
});

View file

@ -1,122 +0,0 @@
define(function (require) {
var _ = require('lodash');
var $ = require('jquery');
require('ui/debounce');
require('ui/modules')
.get('kibana')
.directive('validateQuery', function (es, $compile, timefilter, kbnIndex, debounce, Promise, Private) {
var fromUser = Private(require('ui/validate_query/lib/from_user'));
var toUser = require('ui/validate_query/lib/to_user');
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 = $('<i tooltip={{tooltipMsg}} class="fa fa-ban input-error"></i>').hide();
$compile(errorElem)($scope);
var init = function () {
elem.after(errorElem);
$scope.ngModel = fromUser($scope.ngModel);
validator($scope.ngModel);
};
function validator(query) {
if (request.abort) request.abort();
var prepare = $scope.queryInput ? useSearchSource : useDefaults;
request = prepare().then(sendRequest);
function useSearchSource() {
var pattern = $scope.queryInput.get('index');
if (_.isString(pattern)) {
return Promise.resolve({ index: pattern });
} else if (_.isFunction(_.get(pattern, 'toIndexList'))) {
return pattern.toIndexList().then(function (indexList) {
return { index: indexList };
});
} else {
return useDefaults();
}
}
function useDefaults() {
return Promise.resolve({
index: kbnIndex,
type: '__kibanaQueryValidator'
});
}
function sendRequest(config) {
return es.indices.validateQuery({
index: config.index,
type: config.type,
explain: true,
ignoreUnavailable: true,
body: {
query: query || { match_all: {} }
}
})
.then(success, error);
}
function error(resp) {
var msg;
ngModel.$setValidity('queryInput', false);
if (resp.explanations && resp.explanations[0]) {
msg = resp.explanations[0].error;
} else {
msg = resp.body.error;
}
$scope.tooltipMsg = msg;
errorElem.show();
return undefined;
}
function success(resp) {
if (resp.valid) {
ngModel.$setValidity('queryInput', true);
errorElem.hide();
return query;
} else {
return error(resp);
}
}
}
var debouncedValidator = debounce(validator, 300, {
leading: true,
trailing: true
});
ngModel.$parsers.push(fromUser);
ngModel.$formatters.push(toUser);
// Use a model watch instead of parser/formatter. Parsers require the
// user to actually enter input, which may not happen if the back button is clicked
$scope.$watch('ngModel', function (newValue, oldValue) {
if (newValue === oldValue) return;
debouncedValidator(newValue);
});
init();
}
};
});
});