Skip to content

Commit 2ec8900

Browse files
committed
Merge pull request #6831 from BigFunger/add-data-processors-date
Add data processors date
2 parents 3f8cdab + 6bd9dba commit 2ec8900

File tree

10 files changed

+378
-5
lines changed

10 files changed

+378
-5
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import './processor_ui_container';
22
import './processor_ui_append';
33
import './processor_ui_convert';
4+
import './processor_ui_date';
45
import './processor_ui_gsub';
56
import './processor_ui_set';
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import uiModules from 'ui/modules';
2+
import template from '../views/processor_ui_date.html';
3+
import _ from 'lodash';
4+
import keysDeep from '../lib/keys_deep';
5+
const createMultiSelectModel = require('../lib/create_multi_select_model');
6+
import '../styles/_processor_ui_date.less';
7+
8+
const app = uiModules.get('kibana');
9+
10+
//scope.processor, scope.pipeline are attached by the process_container.
11+
app.directive('processorUiDate', function () {
12+
return {
13+
restrict: 'E',
14+
template: template,
15+
controller : function ($scope, debounce) {
16+
const processor = $scope.processor;
17+
const pipeline = $scope.pipeline;
18+
19+
function consumeNewInputObject() {
20+
$scope.fields = keysDeep(processor.inputObject);
21+
refreshFieldData();
22+
}
23+
24+
function refreshFieldData() {
25+
$scope.fieldData = _.get(processor.inputObject, processor.sourceField);
26+
}
27+
28+
function processorUiChanged() {
29+
pipeline.setDirty();
30+
}
31+
32+
const updateFormats = debounce(() => {
33+
processor.formats = _($scope.formats)
34+
.filter('selected')
35+
.map('title')
36+
.value();
37+
38+
$scope.customFormatSelected = _.includes(processor.formats, 'Custom');
39+
processorUiChanged();
40+
}, 200);
41+
42+
$scope.updateFormats = updateFormats;
43+
$scope.formats = createMultiSelectModel(['ISO8601', 'UNIX', 'UNIX_MS', 'TAI64N', 'Custom'], processor.formats);
44+
45+
$scope.$watch('processor.inputObject', consumeNewInputObject);
46+
47+
$scope.$watch('processor.sourceField', () => {
48+
refreshFieldData();
49+
processorUiChanged();
50+
});
51+
52+
$scope.$watch('processor.customFormat', updateFormats);
53+
$scope.$watch('processor.targetField', processorUiChanged);
54+
$scope.$watch('processor.timezone', processorUiChanged);
55+
$scope.$watch('processor.locale', processorUiChanged);
56+
}
57+
};
58+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import expect from 'expect.js';
2+
import sinon from 'sinon';
3+
import createMultiSelectModel from '../create_multi_select_model';
4+
5+
describe('createMultiSelectModel', function () {
6+
7+
it('should throw an error if the first argument is not an array', () => {
8+
expect(createMultiSelectModel).withArgs('foo', []).to.throwError();
9+
expect(createMultiSelectModel).withArgs(1234, []).to.throwError();
10+
expect(createMultiSelectModel).withArgs(undefined, []).to.throwError();
11+
expect(createMultiSelectModel).withArgs(null, []).to.throwError();
12+
expect(createMultiSelectModel).withArgs([], []).to.not.throwError();
13+
});
14+
15+
it('should throw an error if the second argument is not an array', () => {
16+
expect(createMultiSelectModel).withArgs([], 'foo').to.throwError();
17+
expect(createMultiSelectModel).withArgs([], 1234).to.throwError();
18+
expect(createMultiSelectModel).withArgs([], undefined).to.throwError();
19+
expect(createMultiSelectModel).withArgs([], null).to.throwError();
20+
expect(createMultiSelectModel).withArgs([], []).to.not.throwError();
21+
});
22+
23+
it('should output an array with an item for each passed in', () => {
24+
const items = [ 'foo', 'bar', 'baz' ];
25+
const expected = [
26+
{ title: 'foo', selected: false },
27+
{ title: 'bar', selected: false },
28+
{ title: 'baz', selected: false }
29+
];
30+
const actual = createMultiSelectModel(items, []);
31+
32+
expect(actual).to.eql(expected);
33+
});
34+
35+
it('should set the selected property in the output', () => {
36+
const items = [ 'foo', 'bar', 'baz' ];
37+
const selectedItems = [ 'bar', 'baz' ];
38+
const expected = [
39+
{ title: 'foo', selected: false },
40+
{ title: 'bar', selected: true },
41+
{ title: 'baz', selected: true }
42+
];
43+
const actual = createMultiSelectModel(items, selectedItems);
44+
45+
expect(actual).to.eql(expected);
46+
});
47+
48+
it('should trim values when comparing for selected', () => {
49+
const items = [ 'foo', 'bar', 'baz' ];
50+
const selectedItems = [ ' bar ', ' baz ' ];
51+
const expected = [
52+
{ title: 'foo', selected: false },
53+
{ title: 'bar', selected: true },
54+
{ title: 'baz', selected: true }
55+
];
56+
const actual = createMultiSelectModel(items, selectedItems);
57+
58+
expect(actual).to.eql(expected);
59+
});
60+
61+
it('should be case insensitive when comparing for selected', () => {
62+
const items = [ 'foo', 'bar', 'baz' ];
63+
const selectedItems = [ ' Bar ', ' BAZ ' ];
64+
const expected = [
65+
{ title: 'foo', selected: false },
66+
{ title: 'bar', selected: true },
67+
{ title: 'baz', selected: true }
68+
];
69+
const actual = createMultiSelectModel(items, selectedItems);
70+
71+
expect(actual).to.eql(expected);
72+
});
73+
74+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import _ from 'lodash';
2+
3+
export default function selectableArray(items, selectedItems) {
4+
if (!_.isArray(items)) throw new Error('First argument must be an array');
5+
if (!_.isArray(selectedItems)) throw new Error('Second argument must be an array');
6+
7+
return items.map((item) => {
8+
const selected = _.find(selectedItems, (selectedItem) => {
9+
return cleanItem(selectedItem) === cleanItem(item);
10+
});
11+
12+
return {
13+
title: item,
14+
selected: !_.isUndefined(selected)
15+
};
16+
});
17+
};
18+
19+
function cleanItem(item) {
20+
return _.trim(item).toUpperCase();
21+
}

src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/processor_types.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,37 @@ export class Convert extends Processor {
7070
}
7171
};
7272

73+
export class Date extends Processor {
74+
constructor(processorId) {
75+
super(processorId, 'date', 'Date');
76+
this.sourceField = '';
77+
this.targetField = '@timestamp';
78+
this.formats = [];
79+
this.timezone = 'Etc/UTC';
80+
this.locale = 'ENGLISH';
81+
this.customFormat = '';
82+
}
83+
84+
get description() {
85+
const source = this.sourceField || '?';
86+
const target = this.targetField || '?';
87+
return `[${source}] -> [${target}]`;
88+
}
89+
90+
get model() {
91+
return {
92+
processorId: this.processorId,
93+
typeId: this.typeId,
94+
sourceField: this.sourceField || '',
95+
targetField: this.targetField || '',
96+
formats: this.formats || [],
97+
timezone: this.timezone || '',
98+
locale: this.locale || '',
99+
customFormat: this.customFormat || ''
100+
};
101+
}
102+
};
103+
73104
export class Gsub extends Processor {
74105
constructor(processorId) {
75106
super(processorId, 'gsub', 'Gsub');
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<div class="form-group">
2+
<label>Field:</label>
3+
<select
4+
class="form-control"
5+
ng-options="field as field for field in fields"
6+
ng-model="processor.sourceField">
7+
</select>
8+
</div>
9+
<div class="form-group">
10+
<label>Field Data:</label>
11+
<pre>{{ fieldData }}</pre>
12+
</div>
13+
<div class="form-group">
14+
<label>Target Field:</label>
15+
<input type="text" class="form-control" ng-model="processor.targetField">
16+
</div>
17+
<div class="form-group">
18+
<label>Formats:</label>
19+
<div ng-repeat="format in formats">
20+
<input
21+
type="checkbox"
22+
id="format_{{processor.processorId}}_{{$index}}"
23+
ng-model="format.selected"
24+
ng-click="updateFormats()" />
25+
<label for="format_{{processor.processorId}}_{{$index}}">
26+
{{format.title}}
27+
<a
28+
aria-label="Custom Date Format Help"
29+
tooltip="Custom Date Format Help"
30+
tooltip-append-to-body="true"
31+
href="http://www.joda.org/joda-time/key_format.html"
32+
target="_blank"
33+
ng-show="format.title === 'Custom'">
34+
<i aria-hidden="true" class="fa fa-question-circle"></i>
35+
</a>
36+
</label>
37+
</div>
38+
<div
39+
class="custom-date-format"
40+
ng-show="customFormatSelected">
41+
<input
42+
type="text"
43+
class="form-control"
44+
ng-model="processor.customFormat">
45+
</div>
46+
</div>
47+
<div class="form-group">
48+
<label>
49+
Timezone:
50+
<a
51+
aria-label="Timezone Help"
52+
tooltip="Timezone Help"
53+
tooltip-append-to-body="true"
54+
href="http://joda-time.sourceforge.net/timezones.html"
55+
target="_blank">
56+
<i aria-hidden="true" class="fa fa-question-circle"></i>
57+
</a>
58+
</label>
59+
<input type="text" class="form-control" ng-model="processor.timezone"></div>
60+
</div>
61+
<div class="form-group">
62+
<label>
63+
Locale:
64+
<a
65+
aria-label="Locale Help"
66+
tooltip="Locale Help"
67+
tooltip-append-to-body="true"
68+
href="https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html"
69+
target="_blank">
70+
<i aria-hidden="true" class="fa fa-question-circle"></i>
71+
</a>
72+
</label>
73+
<input type="text" class="form-control" ng-model="processor.locale"></div>
74+
</div>

src/plugins/kibana/server/lib/converters/ingest_processor_api_kibana_to_es_converters.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,30 @@ export function convert(processorApiDocument) {
3333
return processor;
3434
}
3535

36+
export function date(processorApiDocument) {
37+
const formats = [];
38+
processorApiDocument.formats.forEach((format) => {
39+
if (format.toUpperCase() === 'CUSTOM') {
40+
if (processorApiDocument.custom_format) {
41+
formats.push(processorApiDocument.custom_format);
42+
}
43+
} else {
44+
formats.push(format);
45+
}
46+
});
47+
48+
return {
49+
date: {
50+
tag: processorApiDocument.processor_id,
51+
match_field: processorApiDocument.source_field,
52+
target_field: processorApiDocument.target_field,
53+
match_formats: formats,
54+
timezone: processorApiDocument.timezone,
55+
locale: processorApiDocument.locale
56+
}
57+
};
58+
}
59+
3660
export function gsub(processorApiDocument) {
3761
return {
3862
gsub: {

src/plugins/kibana/server/lib/schemas/resources/ingest_processor_schemas.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ export const convert = base.keys({
1717
type: Joi.string()
1818
});
1919

20+
export const date = base.keys({
21+
type_id: Joi.string().only('date').required(),
22+
source_field: Joi.string().allow(''),
23+
target_field: Joi.string().allow(''),
24+
formats: Joi.array().items(Joi.string().allow('')),
25+
timezone: Joi.string().allow(''),
26+
locale: Joi.string().allow(''),
27+
custom_format: Joi.string().allow('')
28+
});
29+
2030
export const gsub = base.keys({
2131
type_id: Joi.string().only('gsub').required(),
2232
source_field: Joi.string().allow(''),

0 commit comments

Comments
 (0)