diff --git a/src/core_plugins/kibana/index.js b/src/core_plugins/kibana/index.js index 6b22f5271fb0a..98fab64495695 100644 --- a/src/core_plugins/kibana/index.js +++ b/src/core_plugins/kibana/index.js @@ -1,6 +1,7 @@ import ingest from './server/routes/api/ingest'; import search from './server/routes/api/search'; import settings from './server/routes/api/settings'; +import scripts from './server/routes/api/scripts'; module.exports = function (kibana) { return new kibana.Plugin({ @@ -84,6 +85,7 @@ module.exports = function (kibana) { ingest(server); search(server); settings(server); + scripts(server); } }); diff --git a/src/core_plugins/kibana/public/management/sections/indices/_scripted_fields.js b/src/core_plugins/kibana/public/management/sections/indices/_scripted_fields.js index e4a00ad8540b6..094d2cb23c633 100644 --- a/src/core_plugins/kibana/public/management/sections/indices/_scripted_fields.js +++ b/src/core_plugins/kibana/public/management/sections/indices/_scripted_fields.js @@ -25,6 +25,7 @@ uiModules.get('apps/management') $scope.perPage = 25; $scope.columns = [ { title: 'name' }, + { title: 'lang' }, { title: 'script' }, { title: 'format' }, { title: 'controls', sortable: false } @@ -46,6 +47,7 @@ uiModules.get('apps/management') return [ _.escape(field.name), + _.escape(field.lang), _.escape(field.script), _.get($scope.indexPattern, ['fieldFormatMap', field.name, 'type', 'title']), { diff --git a/src/core_plugins/kibana/server/routes/api/scripts/index.js b/src/core_plugins/kibana/server/routes/api/scripts/index.js new file mode 100644 index 0000000000000..a625649df423e --- /dev/null +++ b/src/core_plugins/kibana/server/routes/api/scripts/index.js @@ -0,0 +1,5 @@ +import { registerLanguages } from './register_languages'; + +export default function (server) { + registerLanguages(server); +} diff --git a/src/core_plugins/kibana/server/routes/api/scripts/register_languages.js b/src/core_plugins/kibana/server/routes/api/scripts/register_languages.js new file mode 100644 index 0000000000000..82bb5cc9ba22c --- /dev/null +++ b/src/core_plugins/kibana/server/routes/api/scripts/register_languages.js @@ -0,0 +1,27 @@ +import _ from 'lodash'; +import handleESError from '../../../lib/handle_es_error'; + +export function registerLanguages(server) { + server.route({ + path: '/api/kibana/scripts/languages', + method: 'GET', + handler: function (request, reply) { + const callWithRequest = server.plugins.elasticsearch.callWithRequest; + + return callWithRequest(request, 'cluster.getSettings', { + include_defaults: true, + filter_path: '**.script.engine.*.inline' + }) + .then((esResponse) => { + const langs = _.get(esResponse, 'defaults.script.engine', {}); + const inlineLangs = _.pick(langs, (lang) => lang.inline === 'true'); + const supportedLangs = _.omit(inlineLangs, 'mustache'); + return _.keys(supportedLangs); + }) + .then(reply) + .catch((error) => { + reply(handleESError(error)); + }); + } + }); +} diff --git a/src/fixtures/logstash_fields.js b/src/fixtures/logstash_fields.js index e4f993da81470..824a66ba25094 100644 --- a/src/fixtures/logstash_fields.js +++ b/src/fixtures/logstash_fields.js @@ -22,6 +22,7 @@ function stubbedLogstashFields() { { name: 'custom_user_field', type: 'conflict', indexed: false, analyzed: false, sortable: false, filterable: true }, { name: 'script string', type: 'string', scripted: true, script: '\'i am a string\'', lang: 'expression' }, { name: 'script number', type: 'number', scripted: true, script: '1234', lang: 'expression' }, + { name: 'script date', type: 'date', scripted: true, script: '1234', lang: 'painless' }, { name: 'script murmur3', type: 'murmur3', scripted: true, script: '1234', lang: 'expression'}, ].map(function (field) { field.count = field.count || 0; diff --git a/src/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js b/src/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js index 9efc4515038aa..76daadfe70b4b 100644 --- a/src/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js +++ b/src/ui/public/agg_types/__tests__/buckets/create_filter/date_histogram.js @@ -35,7 +35,7 @@ describe('AggConfig Filters', function () { interval = interval || 'auto'; if (interval === 'custom') interval = agg.params.customInterval; duration = duration || moment.duration(15, 'minutes'); - field = _.sample(indexPattern.fields.byType.date); + field = _.sample(_.reject(indexPattern.fields.byType.date, 'scripted')); vis = new Vis(indexPattern, { type: 'histogram', aggs: [ diff --git a/src/ui/public/documentation_links/documentation_links.js b/src/ui/public/documentation_links/documentation_links.js index b148e90210f1d..36f83e0e969cd 100644 --- a/src/ui/public/documentation_links/documentation_links.js +++ b/src/ui/public/documentation_links/documentation_links.js @@ -14,5 +14,13 @@ export default { elasticsearchOutputAnchorParameters: `${baseUrl}guide/en/beats/filebeat/${urlVersion}/elasticsearch-output.html#_parameters`, startup: `${baseUrl}guide/en/beats/filebeat/${urlVersion}/_step_5_starting_filebeat.html`, exportedFields: `${baseUrl}guide/en/beats/filebeat/${urlVersion}/exported-fields.html` + }, + scriptedFields: { + scriptFields: `${baseUrl}guide/en/elasticsearch/reference/${urlVersion}/search-request-script-fields.html`, + scriptAggs: `${baseUrl}guide/en/elasticsearch/reference/${urlVersion}/search-aggregations.html#_values_source`, + painless: `${baseUrl}guide/en/elasticsearch/reference/${urlVersion}/modules-scripting-painless.html`, + painlessApi: `${baseUrl}guide/en/elasticsearch/reference/${urlVersion}/modules-scripting-painless.html#painless-api`, + painlessSyntax: `${baseUrl}guide/en/elasticsearch/reference/${urlVersion}/modules-scripting-painless-syntax.html`, + luceneExpressions: `${baseUrl}guide/en/elasticsearch/reference/${urlVersion}/modules-scripting-expression.html` } }; diff --git a/src/ui/public/field_editor/__tests__/field_editor.js b/src/ui/public/field_editor/__tests__/field_editor.js index 43b200fde76ca..366fe48ea2b56 100644 --- a/src/ui/public/field_editor/__tests__/field_editor.js +++ b/src/ui/public/field_editor/__tests__/field_editor.js @@ -4,6 +4,8 @@ import expect from 'expect.js'; import IndexPatternsFieldProvider from 'ui/index_patterns/_field'; import RegistryFieldFormatsProvider from 'ui/registry/field_formats'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import _ from 'lodash'; + describe('FieldEditor directive', function () { let Field; @@ -14,8 +16,15 @@ describe('FieldEditor directive', function () { let $scope; let $el; + let $httpBackend; + beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function ($compile, $injector, Private) { + $httpBackend = $injector.get('$httpBackend'); + $httpBackend + .when('GET', '/api/kibana/scripts/languages') + .respond(['expression', 'painless']); + $rootScope = $injector.get('$rootScope'); Field = Private(IndexPatternsFieldProvider); StringFormat = Private(RegistryFieldFormatsProvider).getType('string'); @@ -127,6 +136,56 @@ describe('FieldEditor directive', function () { }); }); + + describe('scripted fields', function () { + let editor; + let field; + + beforeEach(function () { + $rootScope.field = $rootScope.indexPattern.fields.byName['script string']; + compile(); + editor = $scope.editor; + field = editor.field; + }); + + it('has a scripted flag set to true', function () { + expect(field.scripted).to.be(true); + }); + + it('contains a lang param', function () { + expect(field).to.have.property('lang'); + expect(field.lang).to.be('expression'); + }); + + it('provides lang options based on what is enabled for inline use in ES', function () { + $httpBackend.flush(); + expect(_.isEqual(editor.scriptingLangs, ['expression', 'painless'])).to.be.ok(); + }); + + it('provides curated type options based on language', function () { + $rootScope.$apply(); + expect(editor.fieldTypes).to.have.length(1); + expect(editor.fieldTypes[0]).to.be('number'); + + editor.field.lang = 'painless'; + $rootScope.$apply(); + + expect(editor.fieldTypes).to.have.length(4); + expect(_.isEqual(editor.fieldTypes, ['number', 'string', 'date', 'boolean'])).to.be.ok(); + }); + + it('updates formatter options based on field type', function () { + field.lang = 'painless'; + + $rootScope.$apply(); + expect(editor.field.type).to.be('string'); + const stringFormats = editor.fieldFormatTypes; + + field.type = 'date'; + $rootScope.$apply(); + expect(editor.fieldFormatTypes).to.not.be(stringFormats); + }); + }); }); }); diff --git a/src/ui/public/field_editor/field_editor.html b/src/ui/public/field_editor/field_editor.html index 18e065a6b7b6d..590d15154981a 100644 --- a/src/ui/public/field_editor/field_editor.html +++ b/src/ui/public/field_editor/field_editor.html @@ -1,4 +1,11 @@