diff --git a/src/kibana/components/index_patterns/_cast_mapping_type.js b/src/kibana/components/index_patterns/_cast_mapping_type.js
index 38b4076d95e2..e082caab47cb 100644
--- a/src/kibana/components/index_patterns/_cast_mapping_type.js
+++ b/src/kibana/components/index_patterns/_cast_mapping_type.js
@@ -1,30 +1,41 @@
define(function (require) {
return function CastMappingTypeFn() {
+ var IndexedArray = require('utils/indexed_array/index');
+
+ castMappingType.types = new IndexedArray({
+ index: ['name'],
+ group: ['type'],
+ immutable: true,
+ initialSet: [
+ {name: 'string', type: 'string', group: 'base'},
+ {name: 'date', type: 'date', group: 'base'},
+ {name: 'boolean', type: 'boolean', group: 'base'},
+ {name: 'float', type: 'number', group: 'number'},
+ {name: 'double', type: 'number', group: 'number'},
+ {name: 'integer', type: 'number', group: 'number'},
+ {name: 'long', type: 'number', group: 'number'},
+ {name: 'short', type: 'number', group: 'number'},
+ {name: 'byte', type: 'number', group: 'number'},
+ {name: 'token_count', type: 'number', group: 'number'},
+ {name: 'geo_point', type: 'geo_point', group: 'geo'},
+ {name: 'geo_shape', type: 'geo_shape', group: 'geo'},
+ {name: 'ip', type: 'ip', group: 'other'},
+ {name: 'attachment', type: 'attachment', group: 'other'},
+ ]
+ });
+
/**
* Accepts a mapping type, and converts it into it's js equivilent
* @param {String} type - the type from the mapping's 'type' field
* @return {String} - the most specific type that we care for
*/
- return function castMappingType(type) {
- switch (type) {
- case 'float':
- case 'double':
- case 'integer':
- case 'long':
- case 'short':
- case 'byte':
- case 'token_count':
- return 'number';
- case 'date':
- case 'boolean':
- case 'ip':
- case 'attachment':
- case 'geo_point':
- case 'geo_shape':
- return type;
- default: // including 'string'
- return 'string';
- }
- };
+ function castMappingType(name) {
+ var match = castMappingType.types.byName[name];
+
+ if (match) return match.type;
+ return 'string';
+ }
+
+ return castMappingType;
};
});
\ No newline at end of file
diff --git a/src/kibana/components/index_patterns/_index_pattern.js b/src/kibana/components/index_patterns/_index_pattern.js
index dc42a019cd4e..872f8e33c075 100644
--- a/src/kibana/components/index_patterns/_index_pattern.js
+++ b/src/kibana/components/index_patterns/_index_pattern.js
@@ -24,8 +24,7 @@ define(function (require) {
timeFieldName: 'string',
intervalName: 'string',
customFormats: 'json',
- fields: 'json',
- scriptedFields: 'json'
+ fields: 'json'
});
function IndexPattern(id) {
@@ -71,7 +70,7 @@ define(function (require) {
_.assign(self, resp._source);
if (self.id) {
- if (!self.fields || !self.scriptedFields) {
+ if (!self.fields) {
return self.refreshFields();
} else {
setIndexedValue('fields');
@@ -99,6 +98,7 @@ define(function (require) {
// non-enumerable type so that it does not get included in the JSON
Object.defineProperties(field, {
format: {
+ configurable: true,
enumerable: false,
get: function () {
var formatName = self.customFormats && self.customFormats[field.name];
@@ -106,6 +106,7 @@ define(function (require) {
}
},
displayName: {
+ configurable: true,
enumerable: false,
get: function () {
return shortDotsFilter(field.name);
@@ -118,6 +119,26 @@ define(function (require) {
});
}
+ self.addScriptedField = function (name, script, type) {
+ type = type || 'string';
+ var scriptedField = self.fields.push({
+ name: name,
+ script: script,
+ type: type,
+ scripted: true,
+ });
+ self.save();
+ };
+
+ self.removeScriptedField = function (name) {
+ var fieldIndex = _.findIndex(self.fields, {
+ name: name,
+ scripted: true
+ });
+ self.fields.splice(fieldIndex, 1);
+ self.save();
+ };
+
self.popularizeField = function (fieldName, unit) {
if (_.isUndefined(unit)) unit = 1;
if (!(self.fields.byName && self.fields.byName[fieldName])) return;
@@ -129,6 +150,13 @@ define(function (require) {
self.save();
};
+ self.getFields = function (type) {
+ var getScripted = (type === 'scripted');
+ return _.where(self.fields, function (field) {
+ return field.scripted ? getScripted : !getScripted;
+ });
+ };
+
self.getInterval = function () {
return this.intervalName && _.find(intervals, { name: this.intervalName });
};
@@ -172,7 +200,6 @@ define(function (require) {
return mapper.clearCache(self)
.then(function () {
return self._fetchFields()
- .then(self._fetchScriptedFields)
.then(self.save);
});
};
@@ -180,14 +207,12 @@ define(function (require) {
self._fetchFields = function () {
return mapper.getFieldsForIndexPattern(self, true)
.then(function (fields) {
+ // append existing scripted fields
+ fields = fields.concat(self.getFields('scripted'));
setIndexedValue('fields', fields);
});
};
- self._fetchScriptedFields = function () {
- setIndexedValue('scriptedFields', []);
- };
-
self.toJSON = function () {
return self.id;
};
diff --git a/src/kibana/components/index_patterns/_mapper.js b/src/kibana/components/index_patterns/_mapper.js
index 458944f90ee8..43e279016a66 100644
--- a/src/kibana/components/index_patterns/_mapper.js
+++ b/src/kibana/components/index_patterns/_mapper.js
@@ -46,7 +46,8 @@ define(function (require) {
}
return es.indices.getFieldMapping({
- // TODO: Change index to be the resolved in some way, last three months, last hour, last year, whatever
+ // TODO: Change index to be the resolved in some way,
+ // last three months, last hour, last year, whatever
index: indexList,
field: '*',
ignoreUnavailable: _.isArray(indexList),
diff --git a/src/kibana/components/paginated_table/paginated_table.html b/src/kibana/components/paginated_table/paginated_table.html
index 8262ffc98477..a1443d245ec2 100644
--- a/src/kibana/components/paginated_table/paginated_table.html
+++ b/src/kibana/components/paginated_table/paginated_table.html
@@ -14,6 +14,7 @@
{{ fieldType.title }}
- ({{ indexPattern[fieldType.index].length }})
+ ({{ fieldType.count }})
-
-
-
-
+
-
- No scripted fields defined
-
+
diff --git a/src/kibana/plugins/settings/sections/indices/_edit.js b/src/kibana/plugins/settings/sections/indices/_edit.js
index 4b401038ecc3..a0f1a78df57f 100644
--- a/src/kibana/plugins/settings/sections/indices/_edit.js
+++ b/src/kibana/plugins/settings/sections/indices/_edit.js
@@ -1,6 +1,7 @@
define(function (require) {
var _ = require('lodash');
- require('components/paginated_table/paginated_table');
+ require('plugins/settings/sections/indices/_indexed_fields');
+ require('plugins/settings/sections/indices/_scripted_fields');
require('routes')
.when('/settings/indices/:id', {
@@ -14,10 +15,8 @@ define(function (require) {
});
require('modules').get('apps/settings')
- .controller('settingsIndicesEdit', function ($scope, $location, $route, $compile,
- config, courier, Notifier, Private, AppState) {
+ .controller('settingsIndicesEdit', function ($scope, $location, $route, config, courier, Notifier, Private, AppState) {
- var rowScopes = []; // track row scopes, so they can be destroyed as needed
var notify = new Notifier();
var $state = $scope.state = new AppState();
var popularityHtml = require('text!plugins/settings/sections/indices/_popularity.html');
@@ -26,52 +25,11 @@ define(function (require) {
$scope.indexPattern = $route.current.locals.indexPattern;
var otherIds = _.without($route.current.locals.indexPatternIds, $scope.indexPattern.id);
- $scope.fieldTypes = Private(require('plugins/settings/sections/indices/_field_types'));
-
- $scope.fieldColumns = [{
- title: 'name'
- }, {
- title: 'type'
- }, {
- title: 'analyzed',
- info: 'Analyzed fields may require extra memory to visualize'
- }, {
- title: 'indexed',
- info: 'Fields that are not indexed are unavailable for search'
- }, {
- title: 'popularity',
- info: 'A gauge of how often this field is used',
- }];
-
- $scope.showPopularityControls = function (field) {
- $scope.popularityHoverState = (field) ? field : null;
- };
-
- $scope.$watchCollection('indexPattern.fields', function () {
- _.invoke(rowScopes, '$destroy');
-
- $scope.fieldRows = $scope.indexPattern.fields.map(function (field) {
- var childScope = $scope.$new();
- rowScopes.push(childScope);
- childScope.field = field;
-
- // update the active field via object comparison
- if (_.isEqual(field, $scope.popularityHoverState)) {
- $scope.showPopularityControls(field);
- }
-
- return [field.name, field.type, field.analyzed, field.indexed,
- {
- markup: $compile(popularityHtml)(childScope),
- value: field.count
- }
- ];
- });
+ var fieldTypes = Private(require('plugins/settings/sections/indices/_field_types'));
+ $scope.$watch('indexPattern.fields', function () {
+ $scope.fieldTypes = fieldTypes($scope.indexPattern);
});
-
- $scope.perPage = 25;
-
$scope.changeTab = function (obj) {
$state.tab = obj.index;
$state.save();
diff --git a/src/kibana/plugins/settings/sections/indices/_field_types.js b/src/kibana/plugins/settings/sections/indices/_field_types.js
index 4822cdbfbb83..ad239fdb5630 100644
--- a/src/kibana/plugins/settings/sections/indices/_field_types.js
+++ b/src/kibana/plugins/settings/sections/indices/_field_types.js
@@ -1,13 +1,26 @@
define(function (require) {
- return function GetFieldTyles($route) {
- var indexPattern = $route.current.locals.indexPattern;
+ return function GetFieldTypes() {
+ var _ = require('lodash');
- return [{
- title: 'fields',
- index: 'fields'
- }, {
- title: 'scripted fields',
- index: 'scriptedFields'
- }];
+ return function (indexPattern) {
+ var fieldCount = _.countBy(indexPattern.fields, function (field) {
+ return (field.scripted) ? 'scripted' : 'indexed';
+ });
+
+ _.defaults(fieldCount, {
+ indexed: 0,
+ scripted: 0
+ });
+
+ return [{
+ title: 'fields',
+ index: 'indexedFields',
+ count: fieldCount.indexed
+ }, {
+ title: 'scripted fields',
+ index: 'scriptedFields',
+ count: fieldCount.scripted
+ }];
+ };
};
});
\ No newline at end of file
diff --git a/src/kibana/plugins/settings/sections/indices/_indexed_fields.html b/src/kibana/plugins/settings/sections/indices/_indexed_fields.html
new file mode 100644
index 000000000000..0f606e7e334f
--- /dev/null
+++ b/src/kibana/plugins/settings/sections/indices/_indexed_fields.html
@@ -0,0 +1,5 @@
+
+
diff --git a/src/kibana/plugins/settings/sections/indices/_indexed_fields.js b/src/kibana/plugins/settings/sections/indices/_indexed_fields.js
new file mode 100644
index 000000000000..8871deed8cac
--- /dev/null
+++ b/src/kibana/plugins/settings/sections/indices/_indexed_fields.js
@@ -0,0 +1,60 @@
+define(function (require) {
+ var _ = require('lodash');
+ require('components/paginated_table/paginated_table');
+
+ require('modules').get('apps/settings')
+ .directive('indexedFields', function ($compile) {
+ var popularityHtml = require('text!plugins/settings/sections/indices/_popularity.html');
+
+ return {
+ restrict: 'E',
+ template: require('text!plugins/settings/sections/indices/_indexed_fields.html'),
+ scope: true,
+ link: function ($scope, $el, attr) {
+ var rowScopes = []; // track row scopes, so they can be destroyed as needed
+ $scope.perPage = 25;
+
+ $scope.columns = [{
+ title: 'name'
+ }, {
+ title: 'type'
+ }, {
+ title: 'analyzed',
+ info: 'Analyzed fields may require extra memory to visualize'
+ }, {
+ title: 'indexed',
+ info: 'Fields that are not indexed are unavailable for search'
+ }, {
+ title: 'popularity',
+ info: 'A gauge of how often this field is used',
+ }];
+
+ $scope.showPopularityControls = function (field) {
+ $scope.popularityHoverState = (field) ? field : null;
+ };
+
+ $scope.$watchCollection('indexPattern.fields', function () {
+ _.invoke(rowScopes, '$destroy');
+
+ $scope.rows = $scope.indexPattern.getFields().map(function (field) {
+ var childScope = $scope.$new();
+ rowScopes.push(childScope);
+ childScope.field = field;
+
+ // update the active field via object comparison
+ if (_.isEqual(field, $scope.popularityHoverState)) {
+ $scope.showPopularityControls(field);
+ }
+
+ return [field.name, field.type, field.analyzed, field.indexed,
+ {
+ markup: $compile(popularityHtml)(childScope),
+ value: field.count
+ }
+ ];
+ });
+ });
+ }
+ };
+ });
+});
\ No newline at end of file
diff --git a/src/kibana/plugins/settings/sections/indices/_scripted_field_controls.html b/src/kibana/plugins/settings/sections/indices/_scripted_field_controls.html
new file mode 100644
index 000000000000..fa3d29e84bf8
--- /dev/null
+++ b/src/kibana/plugins/settings/sections/indices/_scripted_field_controls.html
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/kibana/plugins/settings/sections/indices/_scripted_fields.html b/src/kibana/plugins/settings/sections/indices/_scripted_fields.html
new file mode 100644
index 000000000000..dbc485458c6f
--- /dev/null
+++ b/src/kibana/plugins/settings/sections/indices/_scripted_fields.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
No scripted fields
\ No newline at end of file
diff --git a/src/kibana/plugins/settings/sections/indices/_scripted_fields.js b/src/kibana/plugins/settings/sections/indices/_scripted_fields.js
new file mode 100644
index 000000000000..e89f47a0fcc2
--- /dev/null
+++ b/src/kibana/plugins/settings/sections/indices/_scripted_fields.js
@@ -0,0 +1,72 @@
+define(function (require) {
+ var _ = require('lodash');
+ require('components/paginated_table/paginated_table');
+
+ require('modules').get('apps/settings')
+ .directive('scriptedFields', function ($compile, kbnUrl) {
+ var rowScopes = []; // track row scopes, so they can be destroyed as needed
+ var controlsHtml = require('text!plugins/settings/sections/indices/_scripted_field_controls.html');
+
+ return {
+ restrict: 'E',
+ template: require('text!plugins/settings/sections/indices/_scripted_fields.html'),
+ scope: true,
+ link: function ($scope, $el, attr) {
+ var fieldCreatorPath = '/settings/indices/{{ indexPattern }}/scriptedField';
+ var fieldEditorPath = fieldCreatorPath + '/{{ fieldName }}';
+
+ $scope.perPage = 25;
+
+ $scope.columns = [{
+ title: 'name'
+ }, {
+ title: 'script'
+ }, {
+ title: 'type'
+ }, {
+ title: 'controls',
+ sortable: false
+ }];
+
+ $scope.$watch('indexPattern.fields', function () {
+ _.invoke(rowScopes, '$destroy');
+ rowScopes.length = 0;
+
+ $scope.rows = $scope.indexPattern.getFields('scripted').map(function (field) {
+ var rowScope = $scope.$new();
+ var columns = [field.name, field.script, field.type];
+ rowScope.field = field;
+ rowScopes.push(rowScope);
+
+ columns.push({
+ markup: $compile(controlsHtml)(rowScope)
+ });
+
+ return columns;
+ });
+ });
+
+ $scope.create = function () {
+ var params = {
+ indexPattern: $scope.indexPattern.id
+ };
+
+ kbnUrl.change(fieldCreatorPath, params);
+ };
+
+ $scope.edit = function (field) {
+ var params = {
+ indexPattern: $scope.indexPattern.id,
+ fieldName: field.name
+ };
+
+ kbnUrl.change(fieldEditorPath, params);
+ };
+
+ $scope.remove = function (field) {
+ $scope.indexPattern.removeScriptedField(field.name);
+ };
+ }
+ };
+ });
+});
\ No newline at end of file
diff --git a/src/kibana/plugins/settings/sections/indices/index.html b/src/kibana/plugins/settings/sections/indices/index.html
index f5bd3380af39..597dd77b48ae 100644
--- a/src/kibana/plugins/settings/sections/indices/index.html
+++ b/src/kibana/plugins/settings/sections/indices/index.html
@@ -32,4 +32,5 @@
+
\ No newline at end of file
diff --git a/src/kibana/plugins/settings/sections/indices/index.js b/src/kibana/plugins/settings/sections/indices/index.js
index f36d0c15d4cf..82bc4e569110 100644
--- a/src/kibana/plugins/settings/sections/indices/index.js
+++ b/src/kibana/plugins/settings/sections/indices/index.js
@@ -1,6 +1,7 @@
define(function (require) {
var _ = require('lodash');
+ require('plugins/settings/sections/indices/scripted_fields/index');
require('plugins/settings/sections/indices/_create');
require('plugins/settings/sections/indices/_edit');
diff --git a/src/kibana/plugins/settings/sections/indices/scripted_fields/index.html b/src/kibana/plugins/settings/sections/indices/scripted_fields/index.html
new file mode 100644
index 000000000000..15d71c47b646
--- /dev/null
+++ b/src/kibana/plugins/settings/sections/indices/scripted_fields/index.html
@@ -0,0 +1,44 @@
+
+
+
+
+
{{ action }} Scripted Field
+
+
+
Proceed with caution
+
+
Scripted fields can be used to display and aggregate calculated values. As such,
+ they can be very slow, and if done incorrectly, can cause Kibana to be unusable.
+ You already have a field with the name {{ scriptedField.name }}. Naming your scripted
+ field with the same name means you won't be able to query both fields at the same time.
+
+
+
+
+
+
+
+
diff --git a/src/kibana/plugins/settings/sections/indices/scripted_fields/index.js b/src/kibana/plugins/settings/sections/indices/scripted_fields/index.js
new file mode 100644
index 000000000000..bba8b67f49e4
--- /dev/null
+++ b/src/kibana/plugins/settings/sections/indices/scripted_fields/index.js
@@ -0,0 +1,73 @@
+define(function (require) {
+ var _ = require('lodash');
+ require('plugins/settings/sections/indices/_indexed_fields');
+ require('plugins/settings/sections/indices/_scripted_fields');
+
+ require('routes')
+ .addResolves(/settings\/indices\/(.+)\/scriptedField/, {
+ indexPattern: function ($route, courier) {
+ return courier.indexPatterns.get($route.current.params.id)
+ .catch(courier.redirectWhenMissing('/settings/indices'));
+ }
+ })
+ .when('/settings/indices/:id/scriptedField', {
+ template: require('text!plugins/settings/sections/indices/scripted_fields/index.html'),
+ })
+ .when('/settings/indices/:id/scriptedField/:field', {
+ template: require('text!plugins/settings/sections/indices/scripted_fields/index.html'),
+ });
+
+ require('modules').get('apps/settings')
+ .controller('scriptedFieldsEdit', function ($scope, $route, $window, Notifier, Private) {
+ var typeOptions = Private(require('components/index_patterns/_cast_mapping_type'));
+ var fieldEditorPath = '/settings/indices/{{ indexPattern }}/scriptedField';
+ var notify = new Notifier();
+ var createMode = (!$route.current.params.field);
+
+ $scope.indexPattern = $route.current.locals.indexPattern;
+ $scope.indexTypes = typeOptions.types;
+
+ if (createMode) {
+ $scope.action = 'Create';
+ } else {
+ var scriptName = $route.current.params.field;
+ $scope.action = 'Edit';
+ $scope.scriptedField = _.find($scope.indexPattern.fields, {
+ name: scriptName,
+ scripted: true
+ });
+ }
+
+ $scope.cancel = function () {
+ $window.history.back();
+ };
+
+ $scope.submit = function () {
+ var field = $scope.scriptedField;
+ if (createMode) {
+ $scope.indexPattern.addScriptedField(field.name, field.script, field.type);
+ } else {
+ $scope.indexPattern.save();
+ }
+
+ notify.info('Scripted field \'' + $scope.scriptedField.name + '\' successfully saved');
+ $window.history.back();
+ };
+
+ $scope.$watch('scriptedField.name', function (name) {
+ checkConflict(name);
+ });
+
+ function checkConflict(name) {
+ var match = _.find($scope.indexPattern.getFields(), {
+ name: name
+ });
+
+ if (match) {
+ $scope.namingConflict = true;
+ } else {
+ $scope.namingConflict = false;
+ }
+ }
+ });
+});
\ No newline at end of file
diff --git a/src/kibana/plugins/settings/styles/main.less b/src/kibana/plugins/settings/styles/main.less
index aa52bbe826d9..03b6296d8ccc 100644
--- a/src/kibana/plugins/settings/styles/main.less
+++ b/src/kibana/plugins/settings/styles/main.less
@@ -136,6 +136,19 @@ kbn-settings-indices .fields {
}
}
+kbn-settings-indices .scripted-fields {
+ & header {
+ margin: 5px 0;
+ text-align: right;
+ }
+
+ & th:last-child,
+ & td:last-child {
+ text-align: right;
+ }
+}
+
+
.kbn-settings-indices-create {
.time-and-pattern > div {}
}
diff --git a/test/unit/specs/components/index_pattern/_cast_mapping_type.js b/test/unit/specs/components/index_pattern/_cast_mapping_type.js
index b996295d002f..87e0eb66bf2d 100644
--- a/test/unit/specs/components/index_pattern/_cast_mapping_type.js
+++ b/test/unit/specs/components/index_pattern/_cast_mapping_type.js
@@ -13,6 +13,10 @@ define(function (require) {
expect(fn).to.be.a(Function);
});
+ it('should have a types property', function () {
+ expect(fn).to.have.property('types');
+ });
+
it('should cast numeric types to "number"', function () {
var types = [
'float',