diff --git a/src/core_plugins/kibana/index.js b/src/core_plugins/kibana/index.js index 6175bfd1155990..d3ced99f5dac5b 100644 --- a/src/core_plugins/kibana/index.js +++ b/src/core_plugins/kibana/index.js @@ -11,6 +11,7 @@ import { exportApi } from './server/routes/api/export'; import { homeApi } from './server/routes/api/home'; import scripts from './server/routes/api/scripts'; import { registerSuggestionsApi } from './server/routes/api/suggestions'; +import { registerValuesApi } from './server/routes/api/values'; import { registerFieldFormats } from './server/field_formats/register'; import { registerTutorials } from './server/tutorials/register'; import * as systemApi from './server/lib/system_api'; @@ -151,6 +152,7 @@ export default function (kibana) { exportApi(server); homeApi(server); registerSuggestionsApi(server); + registerValuesApi(server); registerFieldFormats(server); registerTutorials(server); server.expose('systemApi', systemApi); diff --git a/src/core_plugins/kibana/server/routes/api/values/index.js b/src/core_plugins/kibana/server/routes/api/values/index.js new file mode 100644 index 00000000000000..872212c65a246e --- /dev/null +++ b/src/core_plugins/kibana/server/routes/api/values/index.js @@ -0,0 +1,5 @@ +import { registerValues } from './register_values'; + +export function registerValuesApi(server) { + registerValues(server); +} diff --git a/src/core_plugins/kibana/server/routes/api/values/register_values.js b/src/core_plugins/kibana/server/routes/api/values/register_values.js new file mode 100644 index 00000000000000..da6136dd57b6bc --- /dev/null +++ b/src/core_plugins/kibana/server/routes/api/values/register_values.js @@ -0,0 +1,57 @@ +import handleESError from '../../../lib/handle_es_error'; + +export function registerValues(server) { + server.route({ + path: '/api/kibana/values/{index}', + method: ['POST'], + handler: async function (req, reply) { + const { index } = req.params; + const { field } = req.payload; + const { query } = req.payload; + + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); + const body = getBody(field, query); + let fieldList = []; + try { + + const response = await callWithRequest(req, 'search', { index, body }); + const buckets = response.aggregations.aggResults.buckets; + for (let i = 0; i < buckets.length; i < i++) { + const currentField = buckets[i].key; + if (typeof (currentField) === 'object') { + // its an array + fieldList = fieldList.concat(currentField); + } else { + fieldList.push(currentField); + } + } + const uniqueValues = Array.from(new Set(fieldList)); + reply(uniqueValues); + } catch (error) { + reply(handleESError(error)); + } + } + }); +} + +function getBody(field, { query }) { + const aggregationSize = 10000; + return { + size: 0, + timeout: '60s', + aggs: { + aggResults: { + terms: { + field: field, + size: aggregationSize + } + } + }, + query: query + }; +} + +// function getEscapedQuery(query = '') { +// // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators +// return query.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`); +// } diff --git a/src/ui/public/courier/fetch/request/request_fetch_params_to_body.js b/src/ui/public/courier/fetch/request/request_fetch_params_to_body.js index cb7787a0f0cb1c..b77e77c65cbb22 100644 --- a/src/ui/public/courier/fetch/request/request_fetch_params_to_body.js +++ b/src/ui/public/courier/fetch/request/request_fetch_params_to_body.js @@ -26,6 +26,7 @@ function emptySearch() { * @return {Promise.} */ export function requestFetchParamsToBody( + $rootScope, requestsFetchParams, Promise, timeFilter, @@ -68,6 +69,10 @@ export function requestFetchParamsToBody( index = indexList; } + + $rootScope.filterState = { query: body.query }; + + return JSON.stringify({ index, type: fetchParams.type, diff --git a/src/ui/public/courier/fetch/request/request_fetch_params_to_body_provider.js b/src/ui/public/courier/fetch/request/request_fetch_params_to_body_provider.js index 169d7cc6711f11..c82c1d593e0922 100644 --- a/src/ui/public/courier/fetch/request/request_fetch_params_to_body_provider.js +++ b/src/ui/public/courier/fetch/request/request_fetch_params_to_body_provider.js @@ -1,8 +1,9 @@ import { requestFetchParamsToBody } from './request_fetch_params_to_body'; -export function RequestFetchParamsToBodyProvider(Promise, timefilter, kbnIndex, sessionId) { +export function RequestFetchParamsToBodyProvider($rootScope, Promise, timefilter, kbnIndex, sessionId) { return (requestsFetchParams) => ( requestFetchParamsToBody( + $rootScope, requestsFetchParams, Promise, timefilter, diff --git a/src/ui/public/filter_bar/lib/map_filter.js b/src/ui/public/filter_bar/lib/map_filter.js index 50c080098ae6f0..8a17711fb421e6 100644 --- a/src/ui/public/filter_bar/lib/map_filter.js +++ b/src/ui/public/filter_bar/lib/map_filter.js @@ -9,6 +9,7 @@ import { FilterBarLibMapMissingProvider } from './map_missing'; import { FilterBarLibMapQueryStringProvider } from './map_query_string'; import { FilterBarLibMapGeoBoundingBoxProvider } from './map_geo_bounding_box'; import { FilterBarLibMapGeoPolygonProvider } from './map_geo_polygon'; +import { FilterBarLibMapValuesProvider } from './map_values'; import { FilterBarLibMapDefaultProvider } from './map_default'; export function FilterBarLibMapFilterProvider(Promise, Private) { @@ -41,6 +42,7 @@ export function FilterBarLibMapFilterProvider(Promise, Private) { Private(FilterBarLibMapQueryStringProvider), Private(FilterBarLibMapGeoBoundingBoxProvider), Private(FilterBarLibMapGeoPolygonProvider), + Private(FilterBarLibMapValuesProvider), Private(FilterBarLibMapDefaultProvider), ]; diff --git a/src/ui/public/filter_bar/lib/map_values.js b/src/ui/public/filter_bar/lib/map_values.js new file mode 100644 index 00000000000000..9a485ff9f7afac --- /dev/null +++ b/src/ui/public/filter_bar/lib/map_values.js @@ -0,0 +1,10 @@ +export function FilterBarLibMapValuesProvider(Promise) { + return function (filter) { + const { type, key, value, params } = filter.meta; + if (type !== 'values') { + return Promise.reject(filter); + } else { + return Promise.resolve({ type, key, value, params }); + } + }; +} diff --git a/src/ui/public/filter_editor/filter_editor.html b/src/ui/public/filter_editor/filter_editor.html index 52a0f1152d5298..b4ba3cb53694fa 100644 --- a/src/ui/public/filter_editor/filter_editor.html +++ b/src/ui/public/filter_editor/filter_editor.html @@ -68,6 +68,8 @@ params="filterEditor.params" > + + diff --git a/src/ui/public/filter_editor/lib/filter_editor_utils.js b/src/ui/public/filter_editor/lib/filter_editor_utils.js index f6d5f8e6e7154f..a2611c106ab6a4 100644 --- a/src/ui/public/filter_editor/lib/filter_editor_utils.js +++ b/src/ui/public/filter_editor/lib/filter_editor_utils.js @@ -27,6 +27,8 @@ export function getParamsFromFilter(filter) { params = filter.query ? filter.query.match[key].query : filter.script.script.params.value; } else if (type === 'phrases') { params = filter.meta.params; + } else if (type === 'values') { + params = filter.meta.params.values; } else if (type === 'range') { const range = filter.range ? filter.range[key] : filter.script.script.params; const from = _.has(range, 'gte') ? range.gte : range.gt; @@ -77,6 +79,8 @@ export function buildFilter({ indexPattern, field, operator, params, filterBuild filter = filterBuilder.buildRangeFilter(field, { gte: params.range.from, lt: params.range.to }, indexPattern); } else if (operator.type === 'exists') { filter = filterBuilder.buildExistsFilter(field, indexPattern); + } else if (operator.type === 'values') { + filter = filterBuilder.buildValuesFilter(field, params, indexPattern); } filter.meta.negate = operator.negate; return filter; diff --git a/src/ui/public/filter_editor/lib/filter_operators.js b/src/ui/public/filter_editor/lib/filter_operators.js index 967571fb930938..c6a465f7c1da40 100644 --- a/src/ui/public/filter_editor/lib/filter_operators.js +++ b/src/ui/public/filter_editor/lib/filter_operators.js @@ -45,6 +45,12 @@ export const FILTER_OPERATORS = [ type: 'exists', negate: true, }, + { + name: 'values', + type: 'values', + negate: false, + fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'] + }, ]; export const FILTER_OPERATOR_TYPES = _(FILTER_OPERATORS) diff --git a/src/ui/public/filter_editor/params_editor/filter_params_controller.js b/src/ui/public/filter_editor/params_editor/filter_params_controller.js new file mode 100644 index 00000000000000..e9f61b60643451 --- /dev/null +++ b/src/ui/public/filter_editor/params_editor/filter_params_controller.js @@ -0,0 +1,6 @@ + +export function filterParamsController($http, $scope) { + $scope.$on('value_event', function (event, data) { + $scope.params = { values: data }; + }); +} diff --git a/src/ui/public/filter_editor/params_editor/filter_params_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_editor.html index f242ce2af5a2f6..dbdfa68e3d8cd1 100644 --- a/src/ui/public/filter_editor/params_editor/filter_params_editor.html +++ b/src/ui/public/filter_editor/params_editor/filter_params_editor.html @@ -1,4 +1,5 @@ + - + + + + \ No newline at end of file diff --git a/src/ui/public/filter_editor/params_editor/filter_params_editor.js b/src/ui/public/filter_editor/params_editor/filter_params_editor.js index fff50d1098c2cb..f6b8f6af051c9d 100644 --- a/src/ui/public/filter_editor/params_editor/filter_params_editor.js +++ b/src/ui/public/filter_editor/params_editor/filter_params_editor.js @@ -2,10 +2,13 @@ import { uiModules } from 'ui/modules'; import template from './filter_params_editor.html'; import './filter_params_phrase_editor'; import './filter_params_phrases_editor'; +import './filter_params_values_editor'; import './filter_params_range_editor'; +import { filterParamsController } from './filter_params_controller'; const module = uiModules.get('kibana'); module.directive('filterParamsEditor', function () { + return { restrict: 'E', template, @@ -13,6 +16,9 @@ module.directive('filterParamsEditor', function () { field: '=', operator: '=', params: '=' - } + }, + + controllerAs: 'filterParamsEditor', + controller: filterParamsController }; }); diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html index 36d509f28decef..7d940d511532c4 100644 --- a/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html +++ b/src/ui/public/filter_editor/params_editor/filter_params_phrase_editor.html @@ -42,4 +42,4 @@ > Accepted date formats - + \ No newline at end of file diff --git a/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html index 4a2935c4ba0b71..ce73b6ec20d1f4 100644 --- a/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html +++ b/src/ui/public/filter_editor/params_editor/filter_params_phrases_editor.html @@ -25,4 +25,4 @@ >
- + \ No newline at end of file diff --git a/src/ui/public/filter_editor/params_editor/filter_params_range_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_range_editor.html index 483d6c7408bd85..8cddfed9dea9c1 100644 --- a/src/ui/public/filter_editor/params_editor/filter_params_range_editor.html +++ b/src/ui/public/filter_editor/params_editor/filter_params_range_editor.html @@ -27,4 +27,4 @@ > Accepted date formats - + \ No newline at end of file diff --git a/src/ui/public/filter_editor/params_editor/filter_params_values_controller.js b/src/ui/public/filter_editor/params_editor/filter_params_values_controller.js new file mode 100644 index 00000000000000..379f9d8d0131ad --- /dev/null +++ b/src/ui/public/filter_editor/params_editor/filter_params_values_controller.js @@ -0,0 +1,32 @@ + + +import _ from 'lodash'; +import chrome from 'ui/chrome'; + +const baseUrl = chrome.addBasePath('/api/kibana/values'); + +export function filterParamsValuesController($http, $scope, $rootScope) { + + this.getAllFieldValues = getAllFieldValues; + + + function getAllFieldValues(field) { + + field = $scope.field; + if (!_.get(field, 'aggregatable') || field.type !== 'string') { + return Promise.resolve([]); + } + + const postParams = { + field: field.name, + query: $rootScope.filterState + }; + + + // TODO pass the response to the scope so that the build function can pick it up + $http.post(`${baseUrl}/${field.indexPattern.title}`, postParams) + .then(response => { + $scope.$emit('value_event', response.data); + }); + } +} diff --git a/src/ui/public/filter_editor/params_editor/filter_params_values_editor.html b/src/ui/public/filter_editor/params_editor/filter_params_values_editor.html new file mode 100644 index 00000000000000..a5ce183deca5bf --- /dev/null +++ b/src/ui/public/filter_editor/params_editor/filter_params_values_editor.html @@ -0,0 +1,5 @@ + diff --git a/src/ui/public/filter_editor/params_editor/filter_params_values_editor.js b/src/ui/public/filter_editor/params_editor/filter_params_values_editor.js new file mode 100644 index 00000000000000..0eeea4d5bd77e2 --- /dev/null +++ b/src/ui/public/filter_editor/params_editor/filter_params_values_editor.js @@ -0,0 +1,19 @@ + +import { uiModules } from 'ui/modules'; +import template from './filter_params_values_editor.html'; +import { filterParamsValuesController } from './filter_params_values_controller'; +import './filter_params_input_type'; + +const module = uiModules.get('kibana'); +module.directive('filterParamsValuesEditor', function () { + return { + restrict: 'E', + template, + scope: { + field: '=', + params: '=' + }, + controllerAs: 'filterParamsValuesEditor', + controller: filterParamsValuesController + }; +}); diff --git a/src/ui/public/filter_manager/lib/index.js b/src/ui/public/filter_manager/lib/index.js index b3fb9fc80e5c55..a681e72967d260 100644 --- a/src/ui/public/filter_manager/lib/index.js +++ b/src/ui/public/filter_manager/lib/index.js @@ -3,3 +3,4 @@ export { buildPhraseFilter } from './phrase'; export { buildPhrasesFilter } from './phrases'; export { buildQueryFilter } from './query'; export { buildRangeFilter } from './range'; +export { buildValuesFilter } from './values'; diff --git a/src/ui/public/filter_manager/lib/values.js b/src/ui/public/filter_manager/lib/values.js new file mode 100644 index 00000000000000..ae664b05001c6b --- /dev/null +++ b/src/ui/public/filter_manager/lib/values.js @@ -0,0 +1,36 @@ +// import { getPhraseScript } from './values'; + +export function buildValuesFilter(field, params, indexPattern) { + const index = indexPattern.id; + const type = 'values'; + const key = field.name; + const values = params.values; + + const filter = { + meta: { index, type, key, values, params } + }; + + // if (field.scripted) { + // should = params.map((value) => ({ + // script: getPhraseScript(field, value) + // })); + // } else { + // should = params.map((value) => ({ + // match_phrase: { + // [field.name]: value + // } + // })); + // } + + filter.query = { + bool: { + filter: { + terms: { + [field.name]: values + } + } + } + }; + + return filter; +}