[Kuery] Remove Angular dependencies (#17836)

* [kuery] Remove `byName` dependency

* Remove $http from kuery

* Fix test

* Add request headers

* Replace byName with getFieldByName; add isFilterable

* Replace Angular mock with static mock

* Update index pattern for query bar

* manually attach format.convert method to field

* Use KBN_FIELD_TYPES to determine isFilterable

* Bump yarn.lock file

* Add kfetch (Kibana fetch)

* Moved `getFromLegacyIndexPattern` to static index pattern functions

* Fix tests

* Mock out kfetch

* Move value formatting from getRangeScript to buildRangeFilter

* Remove getFieldByName (over abstraction)

* Split kfetch options into two
This commit is contained in:
Søren Louv-Jansen 2018-05-15 00:10:40 +02:00 committed by GitHub
parent 2bcb0d2975
commit f1380e3ac5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 684 additions and 250 deletions

View file

@ -121,6 +121,7 @@
"even-better": "7.0.2",
"expiry-js": "0.1.7",
"extract-text-webpack-plugin": "3.0.1",
"fetch-mock": "^5.13.1",
"file-loader": "1.1.4",
"font-awesome": "4.4.0",
"glob": "5.0.13",

View file

@ -1,5 +1,21 @@
import _ from 'lodash';
const OPERANDS_IN_RANGE = 2;
const operators = {
gt: '>',
gte: '>=',
lte: '<=',
lt: '<',
};
const comparators = {
gt: 'boolean gt(Supplier s, def v) {return s.get() > v}',
gte: 'boolean gte(Supplier s, def v) {return s.get() >= v}',
lte: 'boolean lte(Supplier s, def v) {return s.get() <= v}',
lt: 'boolean lt(Supplier s, def v) {return s.get() < v}',
};
function formatValue(field, params) {
return _.map(params, (val, key) => operators[key] + field.format.convert(val)).join(' ');
}
export function buildRangeFilter(field, params, indexPattern, formattedValue) {
const filter = { meta: { index: indexPattern.id } };
@ -29,6 +45,8 @@ export function buildRangeFilter(field, params, indexPattern, formattedValue) {
filter.meta.field = field.name;
} else if (field.scripted) {
filter.script = getRangeScript(field, params);
filter.script.script.params.value = formatValue(field, filter.script.script.params);
filter.meta.field = field.name;
} else {
filter.range = {};
@ -39,19 +57,6 @@ export function buildRangeFilter(field, params, indexPattern, formattedValue) {
}
export function getRangeScript(field, params) {
const operators = {
gt: '>',
gte: '>=',
lte: '<=',
lt: '<',
};
const comparators = {
gt: 'boolean gt(Supplier s, def v) {return s.get() > v}',
gte: 'boolean gte(Supplier s, def v) {return s.get() >= v}',
lte: 'boolean lte(Supplier s, def v) {return s.get() <= v}',
lt: 'boolean lt(Supplier s, def v) {return s.get() < v}',
};
const knownParams = _.pick(params, (val, key) => {
return key in operators;
});
@ -70,17 +75,10 @@ export function getRangeScript(field, params) {
script = `${currentComparators}${comparisons}`;
}
const value = _.map(knownParams, function (val, key) {
return operators[key] + field.format.convert(val);
}).join(' ');
return {
script: {
inline: script,
params: {
...knownParams,
value,
},
params: knownParams,
lang: field.lang
}
};

View file

@ -1,2 +1,4 @@
export { IndexPatternsProvider } from './index_patterns';
export { IndexPatternsApiClientProvider } from './index_patterns_api_client_provider';
export {
IndexPatternsApiClientProvider,
} from './index_patterns_api_client_provider';

View file

@ -0,0 +1,26 @@
import expect from 'expect.js';
import { isFilterable } from '../index';
describe('static utils', () => {
describe('isFilterable', () => {
it('should be filterable', () => {
['string', 'number', 'date', 'ip', 'boolean'].forEach(type => {
expect(isFilterable({ type })).to.be(true);
});
});
it('should not be filterable', () => {
[
'geo_point',
'geo_shape',
'attachment',
'murmur3',
'_source',
'unknown',
'conflict',
].forEach(type => {
expect(isFilterable({ type })).to.be(false);
});
});
});
});

View file

@ -0,0 +1,23 @@
import { KBN_FIELD_TYPES } from '../../../../utils/kbn_field_types';
const filterableTypes = KBN_FIELD_TYPES.filter(type => type.filterable).map(
type => type.name
);
export function isFilterable(field) {
return filterableTypes.includes(field.type);
}
export function getFromSavedObject(savedObject) {
return {
fields: JSON.parse(savedObject.attributes.fields),
title: savedObject.attributes.title,
};
}
export function getFromLegacyIndexPattern(indexPatterns) {
return indexPatterns.map(indexPattern => ({
fields: indexPattern.fields.raw,
title: indexPattern.title,
}));
}

View file

@ -0,0 +1,47 @@
import 'isomorphic-fetch';
import url from 'url';
import chrome from '../chrome';
import { metadata } from '../metadata';
import { merge } from 'lodash';
class FetchError extends Error {
constructor(res) {
super(res.statusText);
this.res = res;
Error.captureStackTrace(this, FetchError);
}
}
export async function kfetch(fetchOptions, kibanaOptions) {
// fetch specific options with defaults
const { pathname, query, ...combinedFetchOptions } = merge(
{
method: 'GET',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'kbn-version': metadata.version,
},
},
fetchOptions
);
// kibana specific options with defaults
const combinedKibanaOptions = {
prependBasePath: true,
...kibanaOptions,
};
const fullUrl = url.format({
pathname: combinedKibanaOptions.prependBasePath ? chrome.addBasePath(pathname) : pathname,
query,
});
const res = await fetch(fullUrl, combinedFetchOptions);
if (!res.ok) {
throw new FetchError(res);
}
return res.json();
}

View file

@ -0,0 +1,98 @@
import fetchMock from 'fetch-mock';
import { kfetch } from './index';
jest.mock('../chrome', () => ({
addBasePath: path => `myBase/${path}`,
}));
jest.mock('../metadata', () => ({
metadata: {
version: 'my-version',
},
}));
describe('kfetch', () => {
const matcherName = /my\/path/;
describe('resolves', () => {
beforeEach(() =>
fetchMock.get({
matcher: matcherName,
response: new Response(JSON.stringify({ foo: 'bar' })),
}));
afterEach(() => fetchMock.restore());
it('should return response', async () => {
expect(await kfetch({ pathname: 'my/path', query: { a: 'b' } })).toEqual({
foo: 'bar',
});
});
it('should prepend with basepath by default', async () => {
await kfetch({ pathname: 'my/path', query: { a: 'b' } });
expect(fetchMock.lastUrl(matcherName)).toBe('myBase/my/path?a=b');
});
it('should not prepend with basepath when disabled', async () => {
await kfetch(
{
pathname: 'my/path',
query: { a: 'b' },
},
{
prependBasePath: false,
}
);
expect(fetchMock.lastUrl(matcherName)).toBe('my/path?a=b');
});
it('should call with default options', async () => {
await kfetch({ pathname: 'my/path', query: { a: 'b' } });
expect(fetchMock.lastOptions(matcherName)).toEqual({
method: 'GET',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'kbn-version': 'my-version',
},
});
});
it('should merge headers', async () => {
await kfetch({
pathname: 'my/path',
query: { a: 'b' },
headers: { myHeader: 'foo' },
});
expect(fetchMock.lastOptions(matcherName).headers).toEqual({
'Content-Type': 'application/json',
'kbn-version': 'my-version',
myHeader: 'foo',
});
});
});
describe('rejects', () => {
beforeEach(() => {
fetchMock.get({
matcher: matcherName,
response: {
status: 404,
},
});
});
afterEach(() => fetchMock.restore());
it('should throw custom error containing response object', () => {
return kfetch({
pathname: 'my/path',
query: { a: 'b' }
}).catch(e => {
expect(e.message).toBe('Not Found');
expect(e.res.status).toBe(404);
expect(e.res.url).toBe('myBase/my/path?a=b');
});
});
});
});

View file

@ -0,0 +1,256 @@
{
"title": "logstash-*",
"fields": [
{
"name": "bytes",
"type": "number",
"count": 10,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "ssl",
"type": "boolean",
"count": 20,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "@timestamp",
"type": "date",
"count": 30,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "time",
"type": "date",
"count": 30,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "@tags",
"type": "string",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "utc_time",
"type": "date",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "phpmemory",
"type": "number",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "ip",
"type": "ip",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "request_body",
"type": "attachment",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "point",
"type": "geo_point",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "area",
"type": "geo_shape",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "hashed",
"type": "murmur3",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": false,
"readFromDocValues": false
},
{
"name": "geo.coordinates",
"type": "geo_point",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "extension",
"type": "string",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "machine.os",
"type": "string",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "machine.os.raw",
"type": "string",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "geo.src",
"type": "string",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "_id",
"type": "string",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "_type",
"type": "string",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "_source",
"type": "_source",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "non-filterable",
"type": "string",
"count": 0,
"scripted": false,
"searchable": false,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "non-sortable",
"type": "string",
"count": 0,
"scripted": false,
"searchable": false,
"aggregatable": false,
"readFromDocValues": false
},
{
"name": "custom_user_field",
"type": "conflict",
"count": 0,
"scripted": false,
"searchable": true,
"aggregatable": true,
"readFromDocValues": true
},
{
"name": "script string",
"type": "string",
"count": 0,
"scripted": true,
"script": "'i am a string'",
"lang": "expression",
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "script number",
"type": "number",
"count": 0,
"scripted": true,
"script": "1234",
"lang": "expression",
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "script date",
"type": "date",
"count": 0,
"scripted": true,
"script": "1234",
"lang": "painless",
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
},
{
"name": "script murmur3",
"type": "murmur3",
"count": 0,
"scripted": true,
"script": "1234",
"lang": "expression",
"searchable": true,
"aggregatable": true,
"readFromDocValues": false
}
]
}

View file

@ -1,8 +1,8 @@
import * as ast from '../ast';
import expect from 'expect.js';
import { nodeTypes } from '../../node_types/index';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal.js';
@ -16,10 +16,10 @@ let indexPattern;
describe('kuery AST API', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('fromLegacyKueryExpression', function () {

View file

@ -2,8 +2,8 @@ import expect from 'expect.js';
import * as and from '../and';
import { nodeTypes } from '../../node_types';
import * as ast from '../../ast';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
let indexPattern;
@ -11,13 +11,11 @@ const childNode1 = nodeTypes.function.buildNode('is', 'machine.os', 'osx');
const childNode2 = nodeTypes.function.buildNode('is', 'extension', 'jpg');
describe('kuery functions', function () {
describe('and', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {
@ -27,7 +25,6 @@ describe('kuery functions', function () {
expect(actualChildNode1).to.be(childNode1);
expect(actualChildNode2).to.be(childNode2);
});
});
describe('toElasticsearchQuery', function () {

View file

@ -2,22 +2,19 @@ import expect from 'expect.js';
import * as exists from '../exists';
import { nodeTypes } from '../../node_types';
import _ from 'lodash';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
let indexPattern;
describe('kuery functions', function () {
describe('exists', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {
it('should return a single "arguments" param', function () {
const result = exists.buildNodeParams('response');
expect(result).to.only.have.key('arguments');
@ -28,11 +25,9 @@ describe('kuery functions', function () {
expect(arg).to.have.property('type', 'literal');
expect(arg).to.have.property('value', 'response');
});
});
describe('toElasticsearchQuery', function () {
it('should return an ES exists query', function () {
const expected = {
exists: { field: 'response' }

View file

@ -1,8 +1,7 @@
import expect from 'expect.js';
import * as geoBoundingBox from '../geo_bounding_box';
import { nodeTypes } from '../../node_types';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
let indexPattern;
const params = {
@ -17,13 +16,11 @@ const params = {
};
describe('kuery functions', function () {
describe('geoBoundingBox', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {

View file

@ -1,8 +1,8 @@
import expect from 'expect.js';
import * as geoPolygon from '../geo_polygon';
import { nodeTypes } from '../../node_types';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
let indexPattern;
const points = [
@ -24,10 +24,10 @@ describe('kuery functions', function () {
describe('geoPolygon', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {

View file

@ -1,8 +1,8 @@
import expect from 'expect.js';
import * as is from '../is';
import { nodeTypes } from '../../node_types';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal';
let indexPattern;
@ -11,10 +11,10 @@ describe('kuery functions', function () {
describe('is', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {

View file

@ -2,8 +2,8 @@ import expect from 'expect.js';
import * as not from '../not';
import { nodeTypes } from '../../node_types';
import * as ast from '../../ast';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
let indexPattern;
@ -13,10 +13,10 @@ describe('kuery functions', function () {
describe('not', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {

View file

@ -2,8 +2,8 @@ import expect from 'expect.js';
import * as or from '../or';
import { nodeTypes } from '../../node_types';
import * as ast from '../../ast';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
let indexPattern;
@ -14,10 +14,10 @@ describe('kuery functions', function () {
describe('or', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {

View file

@ -2,8 +2,8 @@ import expect from 'expect.js';
import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal';
import * as range from '../range';
import { nodeTypes } from '../../node_types';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
let indexPattern;
@ -11,10 +11,10 @@ describe('kuery functions', function () {
describe('range', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNodeParams', function () {

View file

@ -1,7 +1,7 @@
import { getFields } from '../../utils/get_fields';
import expect from 'expect.js';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../../__tests__/index_pattern_response.json';
import { nodeTypes } from '../../..';
import { expectDeepEqual } from '../../../../../../test_utils/expect_deep_equal';
@ -9,10 +9,10 @@ let indexPattern;
describe('getFields', function () {
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('field names without a wildcard', function () {
@ -34,13 +34,11 @@ describe('getFields', function () {
it('should not match a wildcard in a literal node', function () {
const indexPatternWithWildField = {
title: 'wildIndex',
fields: {
byName: {
'foo*': {
name: 'foo*'
}
}
}
fields: [
{
name: 'foo*',
},
],
};
const fieldNameNode = nodeTypes.literal.buildNode('foo*');

View file

@ -9,7 +9,7 @@ export function buildNodeParams(fieldName) {
export function toElasticsearchQuery(node, indexPattern) {
const { arguments: [ fieldNameArg ] } = node;
const fieldName = literal.toElasticsearchQuery(fieldNameArg);
const field = indexPattern.fields.byName[fieldName];
const field = indexPattern.fields.find(field => field.name === fieldName);
if (field && field.scripted) {
throw new Error(`Exists query does not support scripted fields`);

View file

@ -18,7 +18,7 @@ export function buildNodeParams(fieldName, params) {
export function toElasticsearchQuery(node, indexPattern) {
const [ fieldNameArg, ...args ] = node.arguments;
const fieldName = nodeTypes.literal.toElasticsearchQuery(fieldNameArg);
const field = indexPattern.fields.byName[fieldName];
const field = indexPattern.fields.find(field => field.name === fieldName);
const queryParams = args.reduce((acc, arg) => {
const snakeArgName = _.snakeCase(arg.name);
return {

View file

@ -16,7 +16,7 @@ export function buildNodeParams(fieldName, points) {
export function toElasticsearchQuery(node, indexPattern) {
const [ fieldNameArg, ...points ] = node.arguments;
const fieldName = nodeTypes.literal.toElasticsearchQuery(fieldNameArg);
const field = indexPattern.fields.byName[fieldName];
const field = indexPattern.fields.find(field => field.name === fieldName);
const queryParams = {
points: points.map(ast.toElasticsearchQuery)
};

View file

@ -38,9 +38,7 @@ export function toElasticsearchQuery(node, indexPattern) {
const queries = fields.map((field) => {
if (field.scripted) {
return {
script: {
...getRangeScript(field, queryParams)
}
script: getRangeScript(field, queryParams),
};
}

View file

@ -4,7 +4,7 @@ import * as wildcard from '../../node_types/wildcard';
export function getFields(node, indexPattern) {
if (node.type === 'literal') {
const fieldName = literal.toElasticsearchQuery(node);
const field = indexPattern.fields.byName[fieldName];
const field = indexPattern.fields.find(field => field.name === fieldName);
if (!field) {
return [];
}

View file

@ -3,8 +3,8 @@ import _ from 'lodash';
import expect from 'expect.js';
import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal.js';
import * as isFunction from '../../functions/is';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import ngMock from 'ng_mock';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
import { nodeTypes } from '../../node_types';
describe('kuery node types', function () {
@ -13,10 +13,10 @@ describe('kuery node types', function () {
let indexPattern;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
}));
beforeEach(() => {
indexPattern = indexPatternResponse;
});
describe('buildNode', function () {

View file

@ -1,19 +1,18 @@
import expect from 'expect.js';
import ngMock from 'ng_mock';
import { getSuggestionsProvider } from '../field';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
import { isFilterable } from '../../../index_patterns/static_utils';
describe('Kuery field suggestions', function () {
let indexPattern;
let indexPatterns;
let getSuggestions;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPattern = Private(StubbedLogstashIndexPatternProvider);
beforeEach(() => {
indexPattern = indexPatternResponse;
indexPatterns = [indexPattern];
getSuggestions = getSuggestionsProvider({ indexPatterns });
}));
});
it('should return a function', function () {
expect(typeof getSuggestions).to.be('function');
@ -23,7 +22,7 @@ describe('Kuery field suggestions', function () {
const prefix = '';
const suffix = '';
const suggestions = getSuggestions({ prefix, suffix });
const filterableFields = indexPattern.fields.filter(field => field.filterable);
const filterableFields = indexPattern.fields.filter(isFilterable);
expect(suggestions.length).to.be(filterableFields.length);
});

View file

@ -1,17 +1,15 @@
import expect from 'expect.js';
import ngMock from 'ng_mock';
import { getSuggestionsProvider } from '../operator';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
describe('Kuery operator suggestions', function () {
let indexPatterns;
let getSuggestions;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private) {
indexPatterns = [Private(StubbedLogstashIndexPatternProvider)];
beforeEach(() => {
indexPatterns = [indexPatternResponse];
getSuggestions = getSuggestionsProvider({ indexPatterns });
}));
});
it('should return a function', function () {
expect(typeof getSuggestions).to.be('function');

View file

@ -1,26 +1,26 @@
import expect from 'expect.js';
import sinon from 'sinon';
import ngMock from 'ng_mock';
import fetchMock from 'fetch-mock';
import { getSuggestionsProvider } from '../value';
import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import indexPatternResponse from '../../__tests__/index_pattern_response.json';
describe('Kuery value suggestions', function () {
let $http;
let config;
let indexPatterns;
let getSuggestions;
const mockValues = ['foo', 'bar'];
const fetchUrlMatcher = /\/api\/kibana\/suggestions\/values\/*/;
beforeEach(ngMock.module('kibana'));
beforeEach(() => fetchMock.post(fetchUrlMatcher, mockValues));
afterEach(() => fetchMock.restore());
describe('with config setting turned off', () => {
beforeEach(ngMock.inject(function (Private) {
$http = getHttpStub(false, mockValues);
beforeEach(() => {
config = getConfigStub(false);
indexPatterns = [Private(StubbedLogstashIndexPatternProvider)];
getSuggestions = getSuggestionsProvider({ $http, config, indexPatterns });
}));
indexPatterns = [indexPatternResponse];
getSuggestions = getSuggestionsProvider({ config, indexPatterns });
});
it('should return a function', function () {
expect(typeof getSuggestions).to.be('function');
@ -31,18 +31,17 @@ describe('Kuery value suggestions', function () {
const prefix = '';
const suffix = '';
const suggestions = await getSuggestions({ fieldName, prefix, suffix });
sinon.assert.notCalled($http.post);
expect(fetchMock.called(fetchUrlMatcher)).to.be(false);
expect(suggestions).to.eql([]);
});
});
describe('with config setting turned on', () => {
beforeEach(ngMock.inject(function (Private) {
$http = getHttpStub(false, mockValues);
beforeEach(() => {
config = getConfigStub(true);
indexPatterns = [Private(StubbedLogstashIndexPatternProvider)];
getSuggestions = getSuggestionsProvider({ $http, config, indexPatterns });
}));
indexPatterns = [indexPatternResponse];
getSuggestions = getSuggestionsProvider({ config, indexPatterns });
});
it('should return a function', function () {
expect(typeof getSuggestions).to.be('function');
@ -64,20 +63,12 @@ describe('Kuery value suggestions', function () {
expect(suggestions.map(({ text }) => text)).to.eql(['false ']);
});
it('should return boolean suggestions for boolean fields', async () => {
const fieldName = 'ssl';
const prefix = '';
const suffix = '';
const suggestions = await getSuggestions({ fieldName, prefix, suffix });
expect(suggestions.map(({ text }) => text)).to.eql(['true ', 'false ']);
});
it('should not make a request for non-aggregatable fields', async () => {
const fieldName = 'non-sortable';
const prefix = '';
const suffix = '';
const suggestions = await getSuggestions({ fieldName, prefix, suffix });
sinon.assert.notCalled($http.post);
expect(fetchMock.called(fetchUrlMatcher)).to.be(false);
expect(suggestions).to.eql([]);
});
@ -86,7 +77,7 @@ describe('Kuery value suggestions', function () {
const prefix = '';
const suffix = '';
const suggestions = await getSuggestions({ fieldName, prefix, suffix });
sinon.assert.notCalled($http.post);
expect(fetchMock.called(fetchUrlMatcher)).to.be(false);
expect(suggestions).to.eql([]);
});
@ -95,7 +86,18 @@ describe('Kuery value suggestions', function () {
const prefix = '';
const suffix = '';
const suggestions = await getSuggestions({ fieldName, prefix, suffix });
sinon.assert.calledOnce($http.post);
const lastCall = fetchMock.lastCall(fetchUrlMatcher, 'POST');
expect(lastCall[0]).to.eql('/api/kibana/suggestions/values/logstash-*');
expect(lastCall[1]).to.eql({
method: 'POST',
body: '{"query":"","field":"machine.os.raw"}',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'kbn-version': '1.2.3',
},
});
expect(suggestions.map(({ text }) => text)).to.eql(['"foo" ', '"bar" ']);
});
@ -116,8 +118,3 @@ function getConfigStub(suggestValues) {
const get = sinon.stub().returns(suggestValues);
return { get };
}
function getHttpStub(reject, data) {
const post = sinon.stub().returns(reject ? Promise.reject() : Promise.resolve({ data }));
return { post };
}

View file

@ -1,6 +1,8 @@
import { escape, flatten } from 'lodash';
import { escapeKuery } from './escape_kuery';
import { sortPrefixFirst } from '../../utils/sort_prefix_first';
import { isFilterable } from '../../index_patterns/static_utils';
const type = 'field';
@ -9,10 +11,10 @@ function getDescription(fieldName) {
}
export function getSuggestionsProvider({ indexPatterns }) {
const allFields = flatten(indexPatterns.map(indexPattern => indexPattern.fields.raw));
const allFields = flatten(indexPatterns.map(indexPattern => indexPattern.fields));
return function getFieldSuggestions({ start, end, prefix, suffix }) {
const search = `${prefix}${suffix}`.toLowerCase();
const filterableFields = allFields.filter(field => field.filterable);
const filterableFields = allFields.filter(isFilterable);
const fieldNames = filterableFields.map(field => field.name);
const matchingFieldNames = fieldNames.filter(name => name.toLowerCase().includes(search));
const sortedFieldNames = sortPrefixFirst(matchingFieldNames.sort(keywordComparator), search);

View file

@ -7,9 +7,9 @@ import { getSuggestionsProvider as conjunction } from './conjunction';
const cursorSymbol = '@kuery-cursor@';
export function getSuggestionsProvider({ $http, config, indexPatterns }) {
export function getSuggestionsProvider({ config, indexPatterns }) {
const getSuggestionsByType = mapValues({ field, value, operator, conjunction }, provider => {
return provider({ $http, config, indexPatterns });
return provider({ config, indexPatterns });
});
return function getSuggestions({ query, selectionStart, selectionEnd }) {

View file

@ -33,7 +33,7 @@ function getDescription(operator) {
}
export function getSuggestionsProvider({ indexPatterns }) {
const allFields = flatten(indexPatterns.map(indexPattern => indexPattern.fields.raw));
const allFields = flatten(indexPatterns.map(indexPattern => indexPattern.fields));
return function getOperatorSuggestions({ end, fieldName }) {
const fields = allFields.filter(field => field.name === fieldName);
return flatten(fields.map(field => {

View file

@ -1,37 +1,59 @@
import 'isomorphic-fetch';
import { flatten, memoize } from 'lodash';
import chrome from '../../chrome';
import { escapeQuotes } from './escape_kuery';
import { kfetch } from '../../kfetch';
const baseUrl = chrome.addBasePath('/api/kibana/suggestions/values');
const type = 'value';
export function getSuggestionsProvider({ $http, config, indexPatterns }) {
const allFields = flatten(indexPatterns.map(indexPattern => indexPattern.fields.raw));
const requestSuggestions = memoize((query, field) => {
const queryParams = { query, field: field.name };
return $http.post(`${baseUrl}/${field.indexPattern.title}`, queryParams);
}, resolver);
const requestSuggestions = memoize((query, field) => {
return kfetch({
pathname: `/api/kibana/suggestions/values/${field.indexPatternTitle}`,
method: 'POST',
body: JSON.stringify({ query, field: field.name }),
});
}, resolver);
export function getSuggestionsProvider({ config, indexPatterns }) {
const allFields = flatten(
indexPatterns.map(indexPattern => {
return indexPattern.fields.map(field => ({
...field,
indexPatternTitle: indexPattern.title,
}));
})
);
const shouldSuggestValues = config.get('filterEditor:suggestValues');
return function getValueSuggestions({ start, end, prefix, suffix, fieldName }) {
return function getValueSuggestions({
start,
end,
prefix,
suffix,
fieldName,
}) {
const fields = allFields.filter(field => field.name === fieldName);
const query = `${prefix}${suffix}`;
const suggestionsByField = fields.map(field => {
if (field.type === 'boolean') {
return wrapAsSuggestions(start, end, query, ['true', 'false']);
} else if (!shouldSuggestValues || !field.aggregatable || field.type !== 'string') {
} else if (
!shouldSuggestValues ||
!field.aggregatable ||
field.type !== 'string'
) {
return [];
}
return requestSuggestions(query, field).then(({ data }) => {
return requestSuggestions(query, field).then(data => {
const quotedValues = data.map(value => `"${escapeQuotes(value)}"`);
return wrapAsSuggestions(start, end, query, quotedValues);
});
});
return Promise.all(suggestionsByField)
.then(suggestions => flatten(suggestions));
return Promise.all(suggestionsByField).then(suggestions =>
flatten(suggestions)
);
};
}
@ -47,5 +69,5 @@ function wrapAsSuggestions(start, end, query, values) {
function resolver(query, field) {
// Only cache results for a minute
const ttl = Math.floor(Date.now() / 1000 / 60);
return [ttl, query, field.indexPattern.title, field.name].join('|');
return [ttl, query, field.indexPatternTitle, field.name].join('|');
}

View file

@ -7,6 +7,7 @@ import { getSuggestionsProvider } from '../../kuery';
import './suggestion.less';
import '../../directives/match_pairs';
import './query_popover';
import { getFromLegacyIndexPattern } from '../../index_patterns/static_utils';
const module = uiModules.get('kibana');
@ -114,7 +115,11 @@ module.directive('queryBar', function () {
$scope.$watch('queryBar.indexPatterns', () => {
this.getIndexPatterns().then(indexPatterns => {
this.getKuerySuggestions = getSuggestionsProvider({ $http, config, indexPatterns });
this.getKuerySuggestions = getSuggestionsProvider({
config,
indexPatterns: getFromLegacyIndexPattern(indexPatterns)
});
this.updateSuggestions();
});
});

View file

@ -17,7 +17,7 @@ export class KbnFieldType {
}
}
const KBN_FIELD_TYPES = [
export const KBN_FIELD_TYPES = [
new KbnFieldType({
name: 'string',
sortable: true,

View file

@ -43,7 +43,6 @@
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "3.3.1",
"expect.js": "0.3.1",
"fetch-mock": "5.13.1",
"gulp": "3.9.1",
"gulp-load-plugins": "1.2.0",
"gulp-mocha": "2.2.0",

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export const kfetch = () => {
return Promise.resolve('mock value');
};

View file

@ -6,4 +6,5 @@
export const metadata = {
branch: 'my-metadata-branch',
version: 'my-metadata-version'
};

View file

@ -6,68 +6,52 @@
import 'isomorphic-fetch';
import { camelizeKeys } from 'humps';
import url from 'url';
import _ from 'lodash';
import chrome from 'ui/chrome';
import { kfetch } from 'ui/kfetch';
import { omit } from 'lodash';
async function callApi(options) {
const { pathname, query, camelcase, compact, ...requestOptions } = {
function removeEmpty(query) {
return omit(query, val => val == null);
}
async function callApi(fetchOptions, kibanaOptions) {
const combinedKibanaOptions = {
compact: true, // remove empty query args
camelcase: true,
credentials: 'same-origin',
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json',
'kbn-xsrf': true
}),
...options
...kibanaOptions
};
const fullUrl = url.format({
pathname,
query: compact ? _.omit(query, val => val == null) : query
});
const combinedFetchOptions = {
...fetchOptions,
query: combinedKibanaOptions.compact
? removeEmpty(fetchOptions.query)
: fetchOptions.query
};
try {
const response = await fetch(fullUrl, requestOptions);
const json = await response.json();
if (!response.ok) {
throw new Error(JSON.stringify(json, null, 4));
}
return camelcase ? camelizeKeys(json) : json;
} catch (err) {
console.error(
'Rest request error with options:\n',
JSON.stringify(options, null, 4),
'\n',
err.message,
err.stack
);
throw err;
}
const res = await kfetch(combinedFetchOptions, combinedKibanaOptions);
return combinedKibanaOptions.camelcase ? camelizeKeys(res) : res;
}
export async function loadLicense() {
return callApi({
pathname: chrome.addBasePath(`/api/xpack/v1/info`)
pathname: `/api/xpack/v1/info`
});
}
export async function loadServerStatus() {
return callApi({
pathname: chrome.addBasePath(`/api/apm/status/server`)
pathname: `/api/apm/status/server`
});
}
export async function loadAgentStatus() {
return callApi({
pathname: chrome.addBasePath(`/api/apm/status/agent`)
pathname: `/api/apm/status/agent`
});
}
export async function loadServiceList({ start, end }) {
return callApi({
pathname: chrome.addBasePath(`/api/apm/services`),
pathname: `/api/apm/services`,
query: {
start,
end
@ -77,7 +61,7 @@ export async function loadServiceList({ start, end }) {
export async function loadServiceDetails({ start, end, serviceName }) {
return callApi({
pathname: chrome.addBasePath(`/api/apm/services/${serviceName}`),
pathname: `/api/apm/services/${serviceName}`,
query: {
start,
end
@ -92,9 +76,7 @@ export async function loadTransactionList({
transactionType
}) {
return callApi({
pathname: chrome.addBasePath(
`/api/apm/services/${serviceName}/transactions`
),
pathname: `/api/apm/services/${serviceName}/transactions`,
query: {
start,
end,
@ -110,9 +92,7 @@ export async function loadTransactionDistribution({
transactionName
}) {
return callApi({
pathname: chrome.addBasePath(
`/api/apm/services/${serviceName}/transactions/distribution`
),
pathname: `/api/apm/services/${serviceName}/transactions/distribution`,
query: {
start,
end,
@ -123,9 +103,7 @@ export async function loadTransactionDistribution({
export async function loadSpans({ serviceName, start, end, transactionId }) {
return callApi({
pathname: chrome.addBasePath(
`/api/apm/services/${serviceName}/transactions/${transactionId}/spans`
),
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}/spans`,
query: {
start,
end
@ -140,9 +118,7 @@ export async function loadTransaction({
transactionId
}) {
const res = await callApi({
pathname: chrome.addBasePath(
`/api/apm/services/${serviceName}/transactions/${transactionId}`
),
pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`,
camelcase: false,
query: {
start,
@ -164,9 +140,7 @@ export async function loadCharts({
transactionName
}) {
return callApi({
pathname: chrome.addBasePath(
`/api/apm/services/${serviceName}/transactions/charts`
),
pathname: `/api/apm/services/${serviceName}/transactions/charts`,
query: {
start,
end,
@ -186,7 +160,7 @@ export async function loadErrorGroupList({
sortOrder
}) {
return callApi({
pathname: chrome.addBasePath(`/api/apm/services/${serviceName}/errors`),
pathname: `/api/apm/services/${serviceName}/errors`,
query: {
start,
end,
@ -205,9 +179,7 @@ export async function loadErrorGroupDetails({
end
}) {
const res = await callApi({
pathname: chrome.addBasePath(
`/api/apm/services/${serviceName}/errors/${errorGroupId}`
),
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`,
camelcase: false,
query: {
start,
@ -228,9 +200,7 @@ export async function loadErrorDistribution({
errorGroupId
}) {
return callApi({
pathname: chrome.addBasePath(
`/api/apm/services/${serviceName}/errors/${errorGroupId}/distribution`
),
pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}/distribution`,
query: {
start,
end
@ -241,7 +211,7 @@ export async function loadErrorDistribution({
export async function createWatch(id, watch) {
return callApi({
method: 'PUT',
pathname: chrome.addBasePath(`/api/watcher/watch/${id}`),
pathname: `/api/watcher/watch/${id}`,
body: JSON.stringify({ type: 'json', id, watch })
});
}

View file

@ -2533,14 +2533,6 @@ fd-slicer@~1.0.1:
dependencies:
pend "~1.2.0"
fetch-mock@5.13.1:
version "5.13.1"
resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7"
dependencies:
glob-to-regexp "^0.3.0"
node-fetch "^1.3.3"
path-to-regexp "^1.7.0"
figures@^1.3.5:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@ -2916,10 +2908,6 @@ glob-stream@^6.1.0:
to-absolute-glob "^2.0.0"
unique-stream "^2.0.2"
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
glob-watcher@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b"
@ -5136,7 +5124,7 @@ nigel@2.x.x:
hoek "4.x.x"
vise "2.x.x"
node-fetch@^1.0.1, node-fetch@^1.3.3:
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
dependencies:

View file

@ -4709,6 +4709,14 @@ fd-slicer@~1.0.1:
dependencies:
pend "~1.2.0"
fetch-mock@^5.13.1:
version "5.13.1"
resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7"
dependencies:
glob-to-regexp "^0.3.0"
node-fetch "^1.3.3"
path-to-regexp "^1.7.0"
figures@^1.3.5:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@ -8771,7 +8779,7 @@ node-fetch@1.3.2:
dependencies:
encoding "^0.1.11"
node-fetch@^1.0.1:
node-fetch@^1.0.1, node-fetch@^1.3.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
dependencies: