diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/output_preview.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/output_preview.js
index 9eae71011792e..091da98c60c9c 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/output_preview.js
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/output_preview.js
@@ -12,7 +12,8 @@ app.directive('outputPreview', function () {
template: outputPreviewTemplate,
scope: {
oldObject: '=',
- newObject: '='
+ newObject: '=',
+ error: '='
},
link: function ($scope, $el) {
const div = $el.find('.visual')[0];
@@ -27,10 +28,10 @@ app.directive('outputPreview', function () {
});
$scope.updateUi = function () {
- const left = $scope.oldObject;
- const right = $scope.newObject;
+ let left = $scope.oldObject;
+ let right = $scope.newObject;
let delta = $scope.diffpatch.diff(left, right);
- if (!delta) delta = {};
+ if (!delta || $scope.error) delta = {};
div.innerHTML = htmlFormat(delta, left);
};
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_setup.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_setup.js
index 0202b1f435dfc..6b593e2f8fa27 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_setup.js
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_setup.js
@@ -50,8 +50,6 @@ app.directive('pipelineSetup', function () {
//initiates the simulate call if the pipeline is dirty
const simulatePipeline = debounce((event, message) => {
- if (!pipeline.dirty) return;
-
if (pipeline.processors.length === 0) {
pipeline.updateOutput();
return;
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui.js
index ae2542846547e..80c41ca103968 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui.js
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui.js
@@ -1,2 +1,3 @@
import './processor_ui_container';
+import './processor_ui_gsub';
import './processor_ui_set';
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_gsub.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_gsub.js
new file mode 100644
index 0000000000000..00336cd9fa878
--- /dev/null
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_gsub.js
@@ -0,0 +1,41 @@
+import uiModules from 'ui/modules';
+import _ from 'lodash';
+import keysDeep from '../lib/keys_deep';
+import template from '../views/processor_ui_gsub.html';
+
+const app = uiModules.get('kibana');
+
+//scope.processor, scope.pipeline are attached by the process_container.
+app.directive('processorUiGsub', function () {
+ return {
+ restrict: 'E',
+ template: template,
+ controller : function ($scope) {
+ const processor = $scope.processor;
+ const pipeline = $scope.pipeline;
+
+ function consumeNewInputObject() {
+ $scope.fields = keysDeep(processor.inputObject);
+ refreshFieldData();
+ }
+
+ function refreshFieldData() {
+ $scope.fieldData = _.get(processor.inputObject, processor.sourceField);
+ }
+
+ function processorUiChanged() {
+ pipeline.setDirty();
+ }
+
+ $scope.$watch('processor.inputObject', consumeNewInputObject);
+
+ $scope.$watch('processor.sourceField', () => {
+ refreshFieldData();
+ processorUiChanged();
+ });
+
+ $scope.$watch('processor.pattern', processorUiChanged);
+ $scope.$watch('processor.replacement', processorUiChanged);
+ }
+ };
+});
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_set.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_set.js
index 66f6a31f7231a..6c8b2e01d0f87 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_set.js
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_set.js
@@ -1,5 +1,5 @@
import uiModules from 'ui/modules';
-import processorUiSetTemplate from '../views/processor_ui_set.html';
+import template from '../views/processor_ui_set.html';
const app = uiModules.get('kibana');
@@ -7,13 +7,13 @@ const app = uiModules.get('kibana');
app.directive('processorUiSet', function () {
return {
restrict: 'E',
- template: processorUiSetTemplate,
+ template: template,
controller : function ($scope) {
const processor = $scope.processor;
const pipeline = $scope.pipeline;
function processorUiChanged() {
- pipeline.dirty = true;
+ pipeline.setDirty();
}
$scope.$watch('processor.targetField', processorUiChanged);
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/source_data.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/source_data.js
index 1a180fad04062..8251d276a90d7 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/source_data.js
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/directives/source_data.js
@@ -10,7 +10,8 @@ app.directive('sourceData', function () {
restrict: 'E',
scope: {
samples: '=',
- sample: '='
+ sample: '=',
+ disabled: '='
},
template: sourceDataTemplate,
controller: function ($scope) {
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/pipeline.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/pipeline.js
index a276640ce42e3..aa03d76171b2c 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/pipeline.js
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/pipeline.js
@@ -34,7 +34,10 @@ describe('processor pipeline', function () {
pipeline.processors[0].model = { bar: 'baz' };
const actual = pipeline.model;
- const expected = { input: pipeline.input, processors: [ pipeline.processors[0].model ]};
+ const expected = {
+ input: pipeline.input,
+ processors: [ pipeline.processors[0].model ]
+ };
expect(actual).to.eql(expected);
});
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/pipeline.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/pipeline.js
index 7f381b27ba51c..da9129414b036 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/pipeline.js
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/pipeline.js
@@ -1,20 +1,76 @@
import _ from 'lodash';
+function updateProcessorOutputs(pipeline, simulateResults) {
+ simulateResults.forEach((result) => {
+ const processor = pipeline.getProcessorById(result.processorId);
+
+ processor.outputObject = _.get(result, 'output');
+ processor.error = _.get(result, 'error');
+ });
+}
+
+//Updates the error state of the pipeline and its processors
+//If a pipeline compile error is returned, lock all processors but the error
+//If a pipeline data error is returned, lock all processors after the error
+function updateErrorState(pipeline) {
+ pipeline.hasCompileError = _.some(pipeline.processors, (processor) => {
+ return _.get(processor, 'error.compile');
+ });
+ _.forEach(pipeline.processors, processor => {
+ processor.locked = false;
+ });
+
+ const errorIndex = _.findIndex(pipeline.processors, 'error');
+ if (errorIndex === -1) return;
+
+ _.forEach(pipeline.processors, (processor, index) => {
+ if (pipeline.hasCompileError && index !== errorIndex) {
+ processor.locked = true;
+ }
+ if (!pipeline.hasCompileError && index > errorIndex) {
+ processor.locked = true;
+ }
+ });
+}
+
+function updateProcessorInputs(pipeline) {
+ pipeline.processors.forEach((processor) => {
+ //we don't want to change the inputObject if the parent processor
+ //is in error because that can cause us to lose state.
+ if (!_.get(processor, 'parent.error')) {
+ //the parent property of the first processor is set to the pipeline.input.
+ //In all other cases it is set to processor[index-1]
+ if (!processor.parent.processorId) {
+ processor.inputObject = _.cloneDeep(processor.parent);
+ } else {
+ processor.inputObject = _.cloneDeep(processor.parent.outputObject);
+ }
+ }
+ });
+}
+
+
export default class Pipeline {
constructor() {
this.processors = [];
- this.counter = 0;
+ this.processorCounter = 0;
this.input = {};
this.output = undefined;
this.dirty = false;
+ this.hasCompileError = false;
}
get model() {
- return {
+ const pipeline = {
input: this.input,
processors: _.map(this.processors, processor => processor.model)
};
+ return pipeline;
+ }
+
+ setDirty() {
+ this.dirty = true;
}
load(pipeline) {
@@ -64,8 +120,8 @@ export default class Pipeline {
add(ProcessorType) {
const processors = this.processors;
- this.counter += 1;
- const processorId = `processor_${this.counter}`;
+ this.processorCounter += 1;
+ const processorId = `processor_${this.processorCounter}`;
const newProcessor = new ProcessorType(processorId);
processors.push(newProcessor);
@@ -88,16 +144,6 @@ export default class Pipeline {
this.dirty = true;
}
- updateOutput() {
- const processors = this.processors;
-
- this.output = undefined;
- if (processors.length > 0) {
- this.output = processors[processors.length - 1].outputObject;
- }
- this.dirty = false;
- }
-
getProcessorById(processorId) {
const result = _.find(this.processors, { processorId });
@@ -108,34 +154,22 @@ export default class Pipeline {
return result;
}
- // Updates the state of the pipeline and processors with the results
- // from an ingest simulate call.
- applySimulateResults(results) {
- //update the outputObject of each processor
- results.forEach((result) => {
- const processor = this.getProcessorById(result.processorId);
-
- processor.outputObject = _.get(result, 'output');
- processor.error = _.get(result, 'error');
- });
+ updateOutput() {
+ const processors = this.processors;
- //update the inputObject of each processor
- results.forEach((result) => {
- const processor = this.getProcessorById(result.processorId);
-
- //we don't want to change the inputObject if the parent processor
- //is in error because that can cause us to lose state.
- if (!_.get(processor, 'error.isNested')) {
- //the parent property of the first processor is set to the pipeline.input.
- //In all other cases it is set to processor[index-1]
- if (!processor.parent.processorId) {
- processor.inputObject = _.cloneDeep(processor.parent);
- } else {
- processor.inputObject = _.cloneDeep(processor.parent.outputObject);
- }
- }
- });
+ this.output = undefined;
+ if (processors.length > 0) {
+ this.output = processors[processors.length - 1].outputObject;
+ }
+ this.dirty = false;
+ }
+ // Updates the state of the pipeline and processors with the results
+ // from an ingest simulate call.
+ applySimulateResults(simulateResults) {
+ updateProcessorOutputs(this, simulateResults);
+ updateErrorState(this);
+ updateProcessorInputs(this);
this.updateOutput();
}
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/processor_types.js b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/processor_types.js
index 10e3f51f3a63e..06fb71514f23a 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/processor_types.js
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/lib/processor_types.js
@@ -22,6 +22,30 @@ class Processor {
}
}
+export class Gsub extends Processor {
+ constructor(processorId) {
+ super(processorId, 'gsub', 'Gsub');
+ this.sourceField = '';
+ this.pattern = '';
+ this.replacement = '';
+ }
+
+ get description() {
+ const source = this.sourceField || '?';
+ return `[${source}] - /${this.pattern}/ -> '${this.replacement}'`;
+ }
+
+ get model() {
+ return {
+ processorId: this.processorId,
+ typeId: this.typeId,
+ sourceField: this.sourceField || '',
+ pattern: this.pattern || '',
+ replacement: this.replacement || ''
+ };
+ }
+};
+
export class Set extends Processor {
constructor(processorId) {
super(processorId, 'set', 'Set');
@@ -38,8 +62,8 @@ export class Set extends Processor {
return {
processorId: this.processorId,
typeId: this.typeId,
- targetField: this.targetField,
- value: this.value
+ targetField: this.targetField || '',
+ value: this.value || ''
};
}
};
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container.less b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container.less
index eeb5f6e655c39..144b868c033da 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container.less
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container.less
@@ -26,7 +26,7 @@ processor-ui-container {
background-color: @settings-pipeline-setup-processor-container-overlay-bg;
}
- &.dirty {
+ &.locked {
.overlay {
display: block;
}
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/pipeline_setup.html b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/pipeline_setup.html
index 954cd3b6b09bb..7a5ded1db4d22 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/pipeline_setup.html
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/pipeline_setup.html
@@ -1,4 +1,4 @@
-
+
@@ -28,6 +28,6 @@
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container.html b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container.html
index ef3878385198c..fa9257053eed8 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container.html
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container.html
@@ -5,7 +5,7 @@
+ ng-class="{locked: processor.locked}">
@@ -15,7 +15,11 @@
{{processor.error.message}}
-
+
+
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container_header.html b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container_header.html
index c5e7aa9e582d6..6cb09fc334e84 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container_header.html
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container_header.html
@@ -31,7 +31,8 @@
tooltip-append-to-body="true"
ng-click="pipeline.moveUp(processor)"
type="button"
- class="btn btn-xs btn-default">
+ class="btn btn-xs btn-default"
+ ng-disabled="pipeline.hasCompileError">
@@ -41,7 +42,8 @@
tooltip-append-to-body="true"
ng-click="pipeline.moveDown(processor)"
type="button"
- class="btn btn-xs btn-default">
+ class="btn btn-xs btn-default"
+ ng-disabled="pipeline.hasCompileError">
@@ -51,7 +53,8 @@
tooltip-append-to-body="true"
ng-click="pipeline.remove(processor)"
type="button"
- class="btn btn-xs btn-danger">
+ class="btn btn-xs btn-danger"
+ ng-disabled="pipeline.hasCompileError && !processor.error">
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_gsub.html b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_gsub.html
new file mode 100644
index 0000000000000..5ef22f7b5047b
--- /dev/null
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_gsub.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/source_data.html b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/source_data.html
index f0309b639eba6..7646cbf0ed56e 100644
--- a/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/source_data.html
+++ b/src/plugins/kibana/public/settings/sections/indices/add_data_steps/pipeline_setup/views/source_data.html
@@ -4,7 +4,8 @@
diff --git a/src/plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_error.js b/src/plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_error.js
new file mode 100644
index 0000000000000..9acae2371d5b0
--- /dev/null
+++ b/src/plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_error.js
@@ -0,0 +1,19 @@
+import processESIngestSimulateError from '../process_es_ingest_simulate_error';
+import expect from 'expect.js';
+import _ from 'lodash';
+
+describe('processESIngestSimulateError', function () {
+
+ it('result will be returned for processor that threw the error', function () {
+ const error = _.set({}, 'body.error.root_cause[0].reason', 'foobar');
+ _.set(error, 'body.error.root_cause[0].header.processor_tag', 'processor1');
+
+ const expected = [
+ { processorId: 'processor1', error: { compile: true, message: 'foobar' } }
+ ];
+ const actual = processESIngestSimulateError(error);
+
+ expect(_.isEqual(actual, expected)).to.be.ok();
+ });
+
+});
diff --git a/src/plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_response.js b/src/plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_response.js
index 0fd2f40597a4e..0ff2fd0f7b191 100644
--- a/src/plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_response.js
+++ b/src/plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_response.js
@@ -4,55 +4,21 @@ import _ from 'lodash';
describe('processESIngestSimulateResponse', function () {
- it('returns a result for each processor in the pipeline', function () {
- const processors = [ { processorId: 'processor1' }, { processorId: 'processor2' } ];
- const response = {
- docs: [ { processor_results: [] } ]
- };
-
- const results = processESIngestSimulateResponse(processors, response);
- expect(results.length).to.be(2);
- });
-
- it('each processor that does not receive a result will contain default info', function () {
- const processors = [
- { processorId: 'processor1', outputObject: 'foo' },
- { processorId: 'processor2', outputObject: 'bar' },
- { processorId: 'processor3', outputObject: 'baz' }
- ];
- const response = {
- docs: [ { processor_results: [] } ]
- };
-
- const expected = [
- { processorId: 'processor1', output: 'foo', error: undefined },
- { processorId: 'processor2', output: 'bar', error: undefined },
- { processorId: 'processor3', output: 'baz', error: undefined }
- ];
- const actual = processESIngestSimulateResponse(processors, response);
-
- expect(actual).to.eql(expected);
- });
-
it('each processor that receives a result will contain response info', function () {
- const processors = [
- { processorId: 'processor1', outputObject: 'foo' },
- { processorId: 'processor2', outputObject: 'bar' },
- { processorId: 'processor3', outputObject: 'baz' }
- ];
const response = {
docs: [ { processor_results: [
+ { tag: 'processor1', doc: { _source: 'new_foo' }, error: undefined },
{ tag: 'processor2', doc: { _source: 'new_bar' }, error: undefined },
{ tag: 'processor3', doc: { _source: 'new_baz' }, error: undefined }
] } ]
};
const expected = [
- { processorId: 'processor1', output: 'foo', error: undefined },
+ { processorId: 'processor1', output: 'new_foo', error: undefined },
{ processorId: 'processor2', output: 'new_bar', error: undefined },
{ processorId: 'processor3', output: 'new_baz', error: undefined }
];
- const actual = processESIngestSimulateResponse(processors, response);
+ const actual = processESIngestSimulateResponse(response);
expect(actual).to.eql(expected);
});
@@ -60,16 +26,11 @@ describe('processESIngestSimulateResponse', function () {
describe('processors that return an error object', function () {
it('will be the root_cause reason if one exists', function () {
- const processors = [
- { processorId: 'processor1', outputObject: 'foo' },
- { processorId: 'processor2', outputObject: 'bar' },
- { processorId: 'processor3', outputObject: 'baz' }
- ];
const response = {
docs: [ { processor_results: [
- { tag: 'processor2', doc: { _source: 'new_bar' }, error: undefined },
+ { tag: 'processor1', doc: { _source: 'new_foo' }, error: undefined },
{
- tag: 'processor3',
+ tag: 'processor2',
doc: 'dummy',
error: { root_cause: [ { reason: 'something bad happened', type: 'general exception' } ] }
}
@@ -77,21 +38,15 @@ describe('processESIngestSimulateResponse', function () {
};
const expected = [
- { processorId: 'processor1', output: 'foo', error: undefined },
- { processorId: 'processor2', output: 'new_bar', error: undefined },
- { processorId: 'processor3', output: undefined, error: { isNested: false, message: 'something bad happened'} }
+ { processorId: 'processor1', output: 'new_foo', error: undefined },
+ { processorId: 'processor2', output: undefined, error: { compile: false, message: 'something bad happened'} }
];
- const actual = processESIngestSimulateResponse(processors, response);
+ const actual = processESIngestSimulateResponse(response);
expect(actual).to.eql(expected);
});
it('will be the root_cause type if reason does not exists', function () {
- const processors = [
- { processorId: 'processor1', outputObject: 'foo' },
- { processorId: 'processor2', outputObject: 'bar' },
- { processorId: 'processor3', outputObject: 'baz' }
- ];
const response = {
docs: [ { processor_results: [
{ tag: 'processor2', doc: { _source: 'new_bar' }, error: undefined },
@@ -104,44 +59,10 @@ describe('processESIngestSimulateResponse', function () {
};
const expected = [
- { processorId: 'processor1', output: 'foo', error: undefined },
{ processorId: 'processor2', output: 'new_bar', error: undefined },
- { processorId: 'processor3', output: undefined, error: { isNested: false, message: 'something bad happened'} }
- ];
- const actual = processESIngestSimulateResponse(processors, response);
-
- expect(actual).to.eql(expected);
- });
-
- it('any processor after errored processor will be set to a nested error state', function () {
- const processors = [
- { processorId: 'processor0', outputObject: 'oof' },
- { processorId: 'processor1', outputObject: 'foo' },
- { processorId: 'processor2', outputObject: 'bar' },
- { processorId: 'processor3', outputObject: 'baz' }
- ];
- const response = {
- docs: [
- {
- processor_results: [
- { tag: 'processor0', doc: { _source: 'new_oof' }, error: undefined },
- {
- tag: 'processor1',
- doc: 'dummy',
- error: { root_cause: [ { reason: 'something bad happened' } ] }
- }
- ]
- }
- ]
- };
-
- const expected = [
- { processorId: 'processor0', output: 'new_oof', error: undefined },
- { processorId: 'processor1', output: undefined, error: { isNested: false, message: 'something bad happened' } },
- { processorId: 'processor2', output: undefined, error: { isNested: true, message: 'Invalid Parent Processor' } },
- { processorId: 'processor3', output: undefined, error: { isNested: true, message: 'Invalid Parent Processor' } }
+ { processorId: 'processor3', output: undefined, error: { compile: false, message: 'something bad happened'} }
];
- const actual = processESIngestSimulateResponse(processors, response);
+ const actual = processESIngestSimulateResponse(response);
expect(actual).to.eql(expected);
});
diff --git a/src/plugins/kibana/server/lib/converters/ingest_processor_api_kibana_to_es_converters.js b/src/plugins/kibana/server/lib/converters/ingest_processor_api_kibana_to_es_converters.js
index 08139c0e0b377..817bcc7dda246 100644
--- a/src/plugins/kibana/server/lib/converters/ingest_processor_api_kibana_to_es_converters.js
+++ b/src/plugins/kibana/server/lib/converters/ingest_processor_api_kibana_to_es_converters.js
@@ -1,3 +1,14 @@
+export function gsub(processorApiDocument) {
+ return {
+ gsub: {
+ tag: processorApiDocument.processor_id,
+ field: processorApiDocument.source_field,
+ pattern: processorApiDocument.pattern,
+ replacement: processorApiDocument.replacement
+ }
+ };
+}
+
export function set(processorApiDocument) {
return {
set: {
diff --git a/src/plugins/kibana/server/lib/process_es_ingest_simulate_error.js b/src/plugins/kibana/server/lib/process_es_ingest_simulate_error.js
new file mode 100644
index 0000000000000..10bf2dc1b9720
--- /dev/null
+++ b/src/plugins/kibana/server/lib/process_es_ingest_simulate_error.js
@@ -0,0 +1,23 @@
+const _ = require('lodash');
+
+function buildError(error) {
+ const errorMessage = _.get(error, 'body.error.root_cause[0].reason');
+ return {
+ compile: true,
+ message: errorMessage
+ };
+}
+
+export default function processESIngestSimulateError(error) {
+ const processorId = _.get(error, 'body.error.root_cause[0].header.processor_tag');
+ if (!processorId) throw error;
+
+ const results = [
+ {
+ processorId: processorId,
+ error: buildError(error)
+ }
+ ];
+
+ return results;
+}
diff --git a/src/plugins/kibana/server/lib/process_es_ingest_simulate_response.js b/src/plugins/kibana/server/lib/process_es_ingest_simulate_response.js
index c5694b6263cb5..7a7119432f7f5 100644
--- a/src/plugins/kibana/server/lib/process_es_ingest_simulate_response.js
+++ b/src/plugins/kibana/server/lib/process_es_ingest_simulate_response.js
@@ -1,41 +1,24 @@
-import _ from 'lodash';
+const _ = require('lodash');
-function translateError(esError) {
- const rootCause = _.get(esError, 'root_cause[0]');
+function buildError(error) {
+ const errorMessage = _.get(error, 'root_cause[0].reason') || _.get(error, 'root_cause[0].type');
+ if (!errorMessage) return;
- return _.get(rootCause, 'reason') || _.get(rootCause, 'type');
+ return {
+ compile: false,
+ message: errorMessage
+ };
}
-export default function processESIngestSimulateResponse(processors, resp) {
- const results = processors.map((processor) => {
+export default function processESIngestSimulateResponse(resp) {
+ const processorResults = _.get(resp, 'docs[0].processor_results');
+ const results = processorResults.map((processorResult) => {
return {
- processorId: processor.processorId,
- output: processor.outputObject,
- error: undefined
+ processorId: _.get(processorResult, 'tag'),
+ output: _.get(processorResult, 'doc._source'),
+ error: buildError(_.get(processorResult, 'error'))
};
});
- const processorResults = _.get(resp, 'docs[0].processor_results');
- processorResults.forEach((processorResult) => {
- const processorId = _.get(processorResult, 'tag');
- const output = _.get(processorResult, 'doc._source');
- const error = _.get(processorResult, 'error');
- const errorMessage = translateError(error);
- const badResult = _.find(results, { 'processorId': processorId });
-
- badResult.output = errorMessage ? undefined : output;
- badResult.error = errorMessage ? { isNested: false, message: errorMessage } : undefined;
- });
-
- const errorIndex = _.findIndex(results, (result) => { return result.error !== undefined; });
- if (errorIndex !== -1) {
- for (let i = errorIndex + 1; i < results.length; i++) {
- const badResult = results[i];
-
- badResult.output = undefined;
- badResult.error = { isNested: true, message: 'Invalid Parent Processor' };
- }
- }
-
return results;
};
diff --git a/src/plugins/kibana/server/lib/schemas/resources/ingest_processor_schemas.js b/src/plugins/kibana/server/lib/schemas/resources/ingest_processor_schemas.js
index 4a8d42fbb530f..1a255b6a48df6 100644
--- a/src/plugins/kibana/server/lib/schemas/resources/ingest_processor_schemas.js
+++ b/src/plugins/kibana/server/lib/schemas/resources/ingest_processor_schemas.js
@@ -4,6 +4,13 @@ const base = Joi.object({
processor_id: Joi.string().required()
});
+export const gsub = base.keys({
+ type_id: Joi.string().only('gsub').required(),
+ source_field: Joi.string().allow(''),
+ pattern: Joi.string().allow(''),
+ replacement: Joi.string().allow('')
+});
+
export const set = base.keys({
type_id: Joi.string().only('set').required(),
target_field: Joi.string().allow(''),
diff --git a/src/plugins/kibana/server/routes/api/ingest/register_simulate.js b/src/plugins/kibana/server/routes/api/ingest/register_simulate.js
index be7442e9abc74..c9379f9e7c47c 100644
--- a/src/plugins/kibana/server/routes/api/ingest/register_simulate.js
+++ b/src/plugins/kibana/server/routes/api/ingest/register_simulate.js
@@ -1,5 +1,7 @@
import _ from 'lodash';
-import processESIngestSimulateResponse from '../../../lib/process_es_ingest_simulate_response';
+import handleESError from '../../../lib/handle_es_error';
+import handleResponse from '../../../lib/process_es_ingest_simulate_response';
+import handleError from '../../../lib/process_es_ingest_simulate_error';
import simulateRequestSchema from '../../../lib/schemas/simulate_request_schema';
import ingestSimulateApiKibanaToEsConverter from '../../../lib/converters/ingest_simulate_api_kibana_to_es_converter';
import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../../../../common/lib/case_conversion';
@@ -24,9 +26,12 @@ export function registerSimulate(server) {
method: 'POST',
body: body
})
- .then(_.partial(processESIngestSimulateResponse, _.map(simulateApiDocument.processors, keysToCamelCaseShallow)))
+ .then(handleResponse, handleError)
.then((processors) => _.map(processors, keysToSnakeCaseShallow))
- .then(reply);
+ .then(reply)
+ .catch((error) => {
+ reply(handleESError(error));
+ });
}
});
};
diff --git a/test/unit/api/ingest/_simulate.js b/test/unit/api/ingest/_simulate.js
index 559cddd2f2ae9..7b8daf3c0fe7c 100644
--- a/test/unit/api/ingest/_simulate.js
+++ b/test/unit/api/ingest/_simulate.js
@@ -34,6 +34,14 @@ define(function (require) {
// All processors must have a processorId property and a typeId property
request.post('/kibana/ingest/simulate')
.send({input: {}, processors: [{}]})
+ .expect(400),
+
+ request.post('/kibana/ingest/simulate')
+ .send({input: {}, processors: ['foo']})
+ .expect(400),
+
+ request.post('/kibana/ingest/simulate')
+ .send({input: {}, processors: 'foo'})
.expect(400)
]);
});
@@ -44,6 +52,106 @@ define(function (require) {
.expect(200);
});
+ bdd.describe('compilation errors', function simulatePipeline() {
+ const pipeline = {
+ input: { foo: '[message]' },
+ processors: [
+ {
+ processor_id: 'processor1',
+ type_id: 'set',
+ target_field: 'foo',
+ value: 'bar'
+ },
+ {
+ processor_id: 'processor2',
+ type_id: 'gsub',
+ source_field: 'foo',
+ pattern: '[',
+ replacement: '<'
+ },
+ {
+ processor_id: 'processor3',
+ type_id: 'set',
+ target_field: 'bar',
+ value: 'baz'
+ }
+ ]
+ };
+
+ bdd.it('should return a 200 for a compile error caused by a processor', function () {
+ request.post('/kibana/ingest/simulate')
+ .send(pipeline)
+ .expect(200)
+ .then((response) => {
+ expect(response.body[0].processor_id).to.be('processor2');
+ expect(response.body[0].error.compile).to.be(true);
+ });
+ });
+
+ bdd.it('should only return a result for the processor that threw the error', function () {
+ request.post('/kibana/ingest/simulate')
+ .send(pipeline)
+ .expect(200)
+ .then((response) => {
+ expect(response.body[0].processor_id).to.be('processor2');
+ expect(response.body[0].error.compile).to.be(true);
+ expect(response.body.length).to.be(1);
+ });
+ });
+ });
+
+ bdd.describe('data errors', function simulatePipeline() {
+ const pipeline = {
+ input: { foo: '[message]' },
+ processors: [
+ {
+ processor_id: 'processor1',
+ type_id: 'set',
+ target_field: 'foo',
+ value: 'bar'
+ },
+ {
+ processor_id: 'processor2',
+ type_id: 'gsub',
+ source_field: '', //invalid source field
+ pattern: '\\[',
+ replacement: '<'
+ },
+ {
+ processor_id: 'processor3',
+ type_id: 'set',
+ target_field: 'bar',
+ value: 'baz'
+ }
+ ]
+ };
+
+ bdd.it('should return 200 with non-compile error object for a processor with an invalid source_field', () => {
+ return Promise.all([
+ request.post('/kibana/ingest/simulate')
+ .send(pipeline)
+ .expect(200)
+ .then((response) => {
+ expect(response.body[0].error).to.be(undefined);
+ expect(response.body[1].error.compile).to.be(false);
+ expect(response.body[1].processor_id).to.be('processor2');
+ })
+ ]);
+ });
+
+ bdd.it('should return results up to and including the erroring processor', () => {
+ return Promise.all([
+ request.post('/kibana/ingest/simulate')
+ .send(pipeline)
+ .expect(200)
+ .then((response) => {
+ expect(response.body.length).to.be(2);
+ })
+ ]);
+ });
+
+ });
+
});
};
});
diff --git a/test/unit/api/ingest/processors/_gsub.js b/test/unit/api/ingest/processors/_gsub.js
new file mode 100644
index 0000000000000..2d97067a911f1
--- /dev/null
+++ b/test/unit/api/ingest/processors/_gsub.js
@@ -0,0 +1,70 @@
+define(function (require) {
+ var Promise = require('bluebird');
+ var _ = require('intern/dojo/node!lodash');
+ var expect = require('intern/dojo/node!expect.js');
+
+ const testPipeline = {
+ processors: [{
+ processor_id: 'processor1',
+ type_id: 'gsub',
+ source_field: 'foo',
+ pattern: 'bar',
+ replacement: 'baz'
+ }],
+ input: { foo: 'bar' }
+ };
+
+ return function (bdd, scenarioManager, request) {
+ bdd.describe('simulate - gsub processor', () => {
+
+ bdd.it('should return 400 for an invalid payload', () => {
+ return Promise.all([
+ // GSub processor requires targetField property
+ request.post('/kibana/ingest/simulate')
+ .send({
+ input: { foo: 'bar' },
+ processors: [{
+ processor_id: 'processor1',
+ type_id: 'gsub',
+ source_field: 42,
+ pattern: 'bar',
+ replacement: 'baz'
+ }]
+ })
+ .expect(400)
+ ]);
+ });
+
+ bdd.it('should return 200 for a valid simulate request', () => {
+ return request.post('/kibana/ingest/simulate')
+ .send(testPipeline)
+ .expect(200);
+ });
+
+ bdd.it('should return a simulated output with the correct result for the given processor', () => {
+ return request.post('/kibana/ingest/simulate')
+ .send(testPipeline)
+ .expect(200)
+ .then((response) => {
+ expect(response.body[0].output.foo).to.be.equal('baz');
+ });
+ });
+
+ bdd.it('should enforce snake case', () => {
+ return request.post('/kibana/ingest/simulate')
+ .send({
+ processors: [{
+ processorId: 'processor1',
+ typeId: 'gsub',
+ sourceField: 'foo',
+ pattern: 'bar',
+ replacement: 'baz'
+ }],
+ input: { foo: 'bar' }
+ })
+ .expect(400);
+ });
+
+ });
+ };
+});
diff --git a/test/unit/api/ingest/processors/index.js b/test/unit/api/ingest/processors/index.js
index ec9182b0719f8..87d130af1d96c 100644
--- a/test/unit/api/ingest/processors/index.js
+++ b/test/unit/api/ingest/processors/index.js
@@ -1,8 +1,10 @@
define(function (require) {
var set = require('./_set');
+ var gsub = require('./_gsub');
return function processors(bdd, scenarioManager, request) {
set(bdd, scenarioManager, request);
+ gsub(bdd, scenarioManager, request);
};
});