Change default query to match_all (#13047)

* Change default query to match_all

* Move files around and add tests

* Fix dashboard isQueryFilter

* Add secondary describe to separate tests

* Fix dashboard test

* Change default discover query

* Fix functional test
This commit is contained in:
Lukas Olson 2017-07-24 12:32:54 -07:00 committed by GitHub
parent a3768c0d4e
commit 858f6fbbd3
12 changed files with 128 additions and 222 deletions

View file

@ -1,4 +1,5 @@
import _ from 'lodash';
import { getDefaultQuery } from 'ui/parse_query';
/**
* @typedef {Object} QueryFilter
@ -14,7 +15,7 @@ export class FilterUtils {
* (e.g. goes in the query input bar), false otherwise (e.g. is in the filter bar).
*/
static isQueryFilter(filter) {
return filter.query && filter.query.query_string && !filter.meta;
return filter.query && !filter.meta;
}
/**
@ -33,10 +34,9 @@ export class FilterUtils {
* @returns {QueryFilter}
*/
static getQueryFilterForDashboard(dashboard) {
const defaultQueryFilter = { query_string: { query: '*' } };
const dashboardFilters = this.getDashboardFilters(dashboard);
const dashboardQueryFilter = _.find(dashboardFilters, this.isQueryFilter);
return dashboardQueryFilter ? dashboardQueryFilter.query : defaultQueryFilter;
return dashboardQueryFilter ? dashboardQueryFilter.query : getDefaultQuery();
}
/**

View file

@ -29,6 +29,7 @@ import indexTemplate from 'plugins/kibana/discover/index.html';
import { StateProvider } from 'ui/state_management/state';
import { documentationLinks } from 'ui/documentation_links/documentation_links';
import { SavedObjectsClientProvider } from 'ui/saved_objects';
import { getDefaultQuery } from 'ui/parse_query';
const app = uiModules.get('apps/discover', [
'kibana/notify',
@ -168,7 +169,7 @@ function discoverController($scope, config, courier, $route, $window, Notifier,
function getStateDefaults() {
return {
query: $scope.searchSource.get('query') || '',
query: $scope.searchSource.get('query') || getDefaultQuery(),
sort: getSort.array(savedSearch.sort, $scope.indexPattern),
columns: savedSearch.columns.length > 0 ? savedSearch.columns : config.get('defaultColumns').slice(),
index: $scope.indexPattern.id,

View file

@ -20,6 +20,7 @@ import editorTemplate from 'plugins/kibana/visualize/editor/editor.html';
import { DashboardConstants } from 'plugins/kibana/dashboard/dashboard_constants';
import { VisualizeConstants } from '../visualize_constants';
import { documentationLinks } from 'ui/documentation_links/documentation_links';
import { getDefaultQuery } from 'ui/parse_query';
uiRoutes
.when(VisualizeConstants.CREATE_PATH, {
@ -135,7 +136,7 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb
const stateDefaults = {
uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {},
linked: !!savedVis.savedSearchId,
query: searchSource.getOwn('query') || { query_string: { query: '*' } },
query: searchSource.getOwn('query') || getDefaultQuery(),
filters: searchSource.getOwn('filter') || [],
vis: savedVisState
};

View file

@ -1,9 +1,8 @@
import _ from 'lodash';
import angular from 'angular';
import { AggTypesBucketsBucketAggTypeProvider } from 'ui/agg_types/buckets/_bucket_agg_type';
import { AggTypesBucketsCreateFilterFiltersProvider } from 'ui/agg_types/buckets/create_filter/filters';
import { DecorateQueryProvider } from 'ui/courier/data_source/_decorate_query';
import { formatQuery } from 'ui/parse_query';
import filtersTemplate from 'ui/agg_types/controls/filters.html';
export function AggTypesBucketsFiltersProvider(Private, Notifier) {
@ -35,7 +34,7 @@ export function AggTypesBucketsFiltersProvider(Private, Notifier) {
decorateQuery(query);
const label = filter.label || _.get(query, 'query_string.query') || angular.toJson(query);
const label = filter.label || formatQuery(query) || '*';
filters[label] = input;
}, {});

View file

@ -1,126 +0,0 @@
import angular from 'angular';
import expect from 'expect.js';
import ngMock from 'ng_mock';
// Load the kibana app dependencies.
let $rootScope;
let $compile;
let Private;
let config;
let $elemScope;
let $elem;
let cycleIndex = 0;
const markup = '<input ng-model="mockModel" parse-query input-focus type="text">';
let fromUser;
import { toUser } from 'ui/parse_query/lib/to_user';
import 'ui/parse_query';
import { ParseQueryLibFromUserProvider } from 'ui/parse_query/lib/from_user';
const init = function () {
// Load the application
ngMock.module('kibana');
// Create the scope
ngMock.inject(function ($injector, _$rootScope_, _$compile_, _$timeout_, _Private_, _config_) {
$compile = _$compile_;
Private = _Private_;
config = _config_;
// Give us a scope
$rootScope = _$rootScope_;
});
};
const compile = function () {
$rootScope.mockModel = 'cycle' + cycleIndex++;
$rootScope.mockQueryInput = undefined;
$elem = angular.element(markup);
$compile($elem)($rootScope);
$elemScope = $elem.isolateScope();
$rootScope.$digest();
};
describe('parse-query directive', function () {
describe('initialization', function () {
beforeEach(function () {
init();
compile();
});
it('should use the model', function () {
expect($elemScope).to.have.property('ngModel');
});
});
describe('user input parser', function () {
beforeEach(function () {
fromUser = Private(ParseQueryLibFromUserProvider);
config.set('query:queryString:options', {});
});
it('should return the input if passed an object', function () {
expect(fromUser({ foo: 'bar' })).to.eql({ foo: 'bar' });
});
it('unless the object is empty, that implies a *', function () {
expect(fromUser({})).to.eql({ query_string: { query: '*' } });
});
it('should treat an empty string as a *', function () {
expect(fromUser('')).to.eql({ query_string: { query: '*' } });
});
it('should merge in the query string options', function () {
config.set('query:queryString:options', { analyze_wildcard: true });
expect(fromUser('foo')).to.eql({ query_string: { query: 'foo', analyze_wildcard: true } });
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' } });
expect(fromUser('true')).to.eql({ query_string: { query: 'true' } });
});
it('should parse valid JSON', function () {
expect(fromUser('{}')).to.eql({});
expect(fromUser('{a:b}')).to.eql({ query_string: { query: '{a:b}' } });
});
});
describe('model presentation formatter', function () {
it('should present undefined as empty string', function () {
let notDefined;
expect(toUser(notDefined)).to.be('');
});
it('should present null as empty string', function () {
expect(toUser(null)).to.be('');
});
it('should present objects as strings', function () {
expect(toUser({ foo: 'bar' })).to.be('{"foo":"bar"}');
});
it('should present query_string queries as strings', function () {
expect(toUser({ query_string: { query: 'lucene query string' } })).to.be('lucene query string');
});
it('should present query_string queries without a query as an empty string', function () {
expect(toUser({ query_string: {} })).to.be('');
});
it('should present string as strings', function () {
expect(toUser('foo')).to.be('foo');
});
it('should present numbers as strings', function () {
expect(toUser(400)).to.be('400');
});
});
});

View file

@ -0,0 +1,18 @@
import { parseQuery, formatQuery } from 'ui/parse_query';
import { uiModules } from 'ui/modules';
uiModules
.get('kibana')
.directive('parseQuery', function () {
return {
restrict: 'A',
require: 'ngModel',
scope: {
'ngModel': '='
},
link: function ($scope, elem, attr, ngModel) {
ngModel.$parsers.push(parseQuery);
ngModel.$formatters.push(formatQuery);
}
};
});

View file

@ -0,0 +1,52 @@
import expect from 'expect.js';
import { formatQuery, parseQuery } from 'ui/parse_query';
describe('Query parsing', function () {
describe('parseQuery', function () {
it('should treat an empty string as a match_all', function () {
expect(parseQuery('')).to.eql({ match_all: {} });
});
it('should treat input that does not start with { as a query string', function () {
expect(parseQuery('foo')).to.eql({ query_string: { query: 'foo' } });
expect(parseQuery('400')).to.eql({ query_string: { query: '400' } });
expect(parseQuery('true')).to.eql({ query_string: { query: 'true' } });
});
it('should parse valid JSON', function () {
expect(parseQuery('{}')).to.eql({});
expect(parseQuery('{a:b}')).to.eql({ query_string: { query: '{a:b}' } });
});
});
describe('formatQuery', function () {
it('should present undefined as empty string', function () {
let notDefined;
expect(formatQuery(notDefined)).to.be('');
});
it('should present null as empty string', function () {
expect(formatQuery(null)).to.be('');
});
it('should present objects as strings', function () {
expect(formatQuery({ foo: 'bar' })).to.be('{"foo":"bar"}');
});
it('should present query_string queries as strings', function () {
expect(formatQuery({ query_string: { query: 'lucene query string' } })).to.be('lucene query string');
});
it('should present query_string queries without a query as an empty string', function () {
expect(formatQuery({ query_string: {} })).to.be('');
});
it('should present string as strings', function () {
expect(formatQuery('foo')).to.be('foo');
});
it('should present numbers as strings', function () {
expect(formatQuery(400)).to.be('400');
});
});
});

View file

@ -1,43 +0,0 @@
import _ from 'lodash';
import { DecorateQueryProvider } from 'ui/courier/data_source/_decorate_query';
export function ParseQueryLibFromUserProvider(es, Private) {
const decorateQuery = Private(DecorateQueryProvider);
/**
* Take text from the user and make it into a query object
* @param {text} user's query input
* @returns {object}
*/
return function (text) {
function getQueryStringQuery(text) {
return decorateQuery({ query_string: { query: text } });
}
const matchAll = getQueryStringQuery('*');
// If we get an empty object, treat it as a *
if (_.isObject(text)) {
if (Object.keys(text).length) {
return text;
} else {
return matchAll;
}
}
// Nope, not an object.
text = (text || '').trim();
if (text.length === 0) return matchAll;
if (text[0] === '{') {
try {
return JSON.parse(text);
} catch (e) {
return getQueryStringQuery(text);
}
} else {
return getQueryStringQuery(text);
}
};
}

View file

@ -1,19 +0,0 @@
import _ from 'lodash';
import angular from 'angular';
/**
* Take text from the model and present it to the user as a string
* @param {text} model value
* @returns {string}
*/
export function toUser(text) {
if (text == null) return '';
if (_.isObject(text)) {
if (text.query_string) return toUser(text.query_string.query);
return angular.toJson(text);
}
if (text === '*') {
return '';
}
return '' + text;
}

View file

@ -1,27 +1,51 @@
import { toUser } from 'ui/parse_query/lib/to_user';
import { ParseQueryLibFromUserProvider } from 'ui/parse_query/lib/from_user';
import _ from 'lodash';
import angular from 'angular';
import { uiModules } from 'ui/modules';
uiModules
.get('kibana')
.directive('parseQuery', function (Private) {
const fromUser = Private(ParseQueryLibFromUserProvider);
export function getDefaultQuery() {
return { match_all: {} };
}
return {
restrict: 'A',
require: 'ngModel',
scope: {
'ngModel': '='
},
link: function ($scope, elem, attr, ngModel) {
const init = function () {
$scope.ngModel = fromUser($scope.ngModel);
};
export function isDefaultQuery(query) {
return _.isEqual(query, getDefaultQuery());
}
ngModel.$parsers.push(fromUser);
ngModel.$formatters.push(toUser);
export function getTextQuery(query) {
return {
query_string: { query }
};
}
init();
}
};
});
export function isTextQuery(query) {
return _.has(query, 'query_string');
}
export function getQueryText(query) {
return _.get(query, ['query_string', 'query']) || '';
}
export function parseQuery(query) {
if (!_.isString(query) || query.trim() === '') {
return getDefaultQuery();
}
try {
const parsedQuery = JSON.parse(query);
if (_.isObject(parsedQuery)) {
return parsedQuery;
}
return getTextQuery(query);
} catch (e) {
return getTextQuery(query);
}
}
export function formatQuery(query) {
if (query == null || isDefaultQuery(query)) {
return '';
} else if (isTextQuery(query)) {
return getQueryText(query);
} else if (_.isObject(query)) {
return angular.toJson(query);
}
return '' + query;
}

View file

@ -139,7 +139,7 @@ export default function ({ getService, getPageObjects }) {
const currentQuery = await PageObjects.dashboard.getQuery();
expect(currentQuery).to.equal('');
const currentUrl = await remote.getCurrentUrl();
const newUrl = currentUrl.replace('query:%27*%27', 'query:%27hi%27');
const newUrl = currentUrl.replace('match_all:()', 'query_string:(query:hi)');
// Don't add the timestamp to the url or it will cause a hard refresh and we want to test a
// soft refresh.
await remote.get(newUrl.toString(), false);

View file

@ -75,8 +75,7 @@ export default function ({ getService, getPageObjects }) {
+ '/discover?_g=(refreshInterval:(display:Off,pause:!f,value:0),time'
+ ':(from:\'2015-09-19T06:31:44.000Z\',mode:absolute,to:\'2015-09'
+ '-23T18:31:44.000Z\'))&_a=(columns:!(_source),index:\'logstash-'
+ '*\',interval:auto,query:(query_string:(analyze_wildcard:!t,query'
+ ':\'*\')),sort:!(\'@timestamp\',desc))';
+ '*\',interval:auto,query:(match_all:()),sort:!(\'@timestamp\',desc))';
return PageObjects.discover.getSharedUrl()
.then(function (actualUrl) {
// strip the timestamp out of each URL