-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ingest UI Pattern Review Screen #5892
Changes from all commits
7dbef2f
d4aff77
27e402b
e5ff5ce
027e807
da6183c
5fcb4cb
a47fc05
3bad81d
9e4362b
4df54be
b94631b
d13a6ca
7ae9d3a
3f02157
41e7303
3d94e1a
d769ebf
11fe9aa
caa7a0f
6dd73b3
00ff3d7
0111071
bd45e77
ac0974b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,32 @@ | ||
<h2>Pattern review step</h2> | ||
<div class="wizard-step-title"> | ||
<h3>Review the index pattern</h3> | ||
Here we'll define how and where to store your parsed events. We've made some intellient guesses for you, but most | ||
fields can be changed if we got it wrong! | ||
</div> | ||
|
||
<div> | ||
Docs: {{sampleDocs}} | ||
<div class="pattern-review"> | ||
<div> | ||
<label>Index name or pattern</label> | ||
<kbn-info info="Patterns allow you to define dynamic index names using * as a wildcard. Example: filebeat-*"></kbn-info> | ||
</div> | ||
<input ng-model="reviewStep.indexPattern.id" class="pattern-input"/> | ||
<label> | ||
<input ng-model="reviewStep.isTimeBased" type="checkbox"/> | ||
time based | ||
</label> | ||
<label ng-if="reviewStep.isTimeBased" class="time-field-input"> | ||
Time Field | ||
<select ng-model="reviewStep.indexPattern.timeFieldName" name="time_field_name"> | ||
<option ng-repeat="field in reviewStep.dateFields" value="{{field}}"> | ||
{{field}} | ||
</option> | ||
</select> | ||
</label> | ||
</div> | ||
|
||
<button ng-click="indexPattern = {id: 'logstash-*', title: 'myFirstIndexPattern'}">Create an index pattern</button> | ||
<div> | ||
<paginated-table | ||
columns="reviewStep.columns" | ||
rows="reviewStep.rows"> | ||
</paginated-table> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,65 @@ | ||
var modules = require('ui/modules'); | ||
var template = require('plugins/kibana/settings/sections/indices/add_data_steps/pattern_review_step.html'); | ||
const modules = require('ui/modules'); | ||
const template = require('plugins/kibana/settings/sections/indices/add_data_steps/pattern_review_step.html'); | ||
const _ = require('lodash'); | ||
const editFieldTypeHTML = require('plugins/kibana/settings/sections/indices/partials/_edit_field_type.html'); | ||
|
||
const testData = { | ||
message: '11/24/2015 ip=1.1.1.1 bytes=1234', | ||
clientip: '1.1.1.1', | ||
bytes: 1234, | ||
geoip: { | ||
lat: 37.3894, | ||
lon: 122.0819 | ||
}, | ||
location: { | ||
lat: 37.3894, | ||
lon: 122.0819 | ||
}, | ||
'@timestamp': '2015-11-24T00:00:00.000Z', | ||
otherdate: '2015-11-24T00:00:00.000Z', | ||
codes: [1, 2, 3, 4] | ||
}; | ||
|
||
const testPipeline = [ | ||
{ | ||
grok: { | ||
match_field: 'message', | ||
match_pattern: 'foo' | ||
} | ||
}, | ||
{ | ||
geoip: { | ||
source_field: 'ip' | ||
} | ||
}, | ||
{ | ||
geoip: { | ||
source_field: 'ip', | ||
target_field: 'location' | ||
} | ||
}, | ||
{ | ||
date: { | ||
match_field: 'initialDate', | ||
match_formats: ['dd/MM/yyyy hh:mm:ss'] | ||
} | ||
}, | ||
{ | ||
date: { | ||
match_field: 'initialDate', | ||
match_formats: ['dd/MM/yyyy hh:mm:ss'], | ||
target_field: 'otherdate' | ||
} | ||
} | ||
]; | ||
|
||
function pickDefaultTimeFieldName(dateFields) { | ||
if (_.isEmpty(dateFields)) { | ||
return undefined; | ||
} | ||
|
||
return _.includes(dateFields, '@timestamp') ? '@timestamp' : dateFields[0]; | ||
} | ||
|
||
modules.get('apps/settings') | ||
.directive('patternReviewStep', function () { | ||
|
@@ -9,6 +69,83 @@ modules.get('apps/settings') | |
sampleDocs: '=', | ||
indexPattern: '=', | ||
pipeline: '=' | ||
}, | ||
controllerAs: 'reviewStep', | ||
bindToController: true, | ||
controller: function ($scope, Private) { | ||
this.sampleDocs = testData; | ||
this.pipeline = testPipeline; | ||
|
||
if (_.isUndefined(this.indexPattern)) { | ||
this.indexPattern = {}; | ||
} | ||
|
||
const knownFieldTypes = {}; | ||
this.dateFields = []; | ||
this.pipeline.forEach((processor) => { | ||
if (processor.geoip) { | ||
const field = processor.geoip.target_field || 'geoip'; | ||
knownFieldTypes[field] = 'geo_point'; | ||
} | ||
else if (processor.date) { | ||
const field = processor.date.target_field || '@timestamp'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should there be any target_field conflict checking on the front end? do you know if this is handled on the server? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ES ingest API simply overwrites the target_field if it already exists, so I don't think we have to worry about conflicts. |
||
knownFieldTypes[field] = 'date'; | ||
this.dateFields.push(field); | ||
} | ||
}); | ||
|
||
_.defaults(this.indexPattern, { | ||
id: 'filebeat-*', | ||
title: 'filebeat-*', | ||
timeFieldName: pickDefaultTimeFieldName(this.dateFields), | ||
fields: _.map(this.sampleDocs, (value, key) => { | ||
let type = knownFieldTypes[key] || typeof value; | ||
if (type === 'object' && _.isArray(value) && !_.isEmpty(value)) { | ||
type = typeof value[0]; | ||
} | ||
return {name: key, type: type}; | ||
}) | ||
}); | ||
|
||
this.isTimeBased = !!this.indexPattern.timeFieldName; | ||
|
||
$scope.$watch('reviewStep.indexPattern.id', (value) => { | ||
this.indexPattern.title = value; | ||
}); | ||
$scope.$watch('reviewStep.isTimeBased', (value) => { | ||
if (value) { | ||
this.indexPattern.timeFieldName = pickDefaultTimeFieldName(this.dateFields); | ||
} | ||
else { | ||
delete this.indexPattern.timeFieldName; | ||
} | ||
}); | ||
$scope.$watch('reviewStep.indexPattern.fields', (fields) => { | ||
this.dateFields = _.map(_.filter(fields, {type: 'date'}), 'name'); | ||
}, true); | ||
|
||
const buildRows = () => { | ||
this.rows = _.map(this.indexPattern.fields, (field) => { | ||
const sampleValue = this.sampleDocs[field.name]; | ||
return [ | ||
_.escape(field.name), | ||
{ | ||
markup: editFieldTypeHTML, | ||
scope: _.assign($scope.$new(), {field: field, knownFieldTypes: knownFieldTypes, buildRows: buildRows}), | ||
value: field.type | ||
}, | ||
typeof sampleValue === 'object' ? _.escape(JSON.stringify(sampleValue)) : _.escape(sampleValue) | ||
]; | ||
}); | ||
}; | ||
|
||
this.columns = [ | ||
{title: 'Field'}, | ||
{title: 'Type'}, | ||
{title: 'Example', sortable: false} | ||
]; | ||
|
||
buildRows(); | ||
} | ||
}; | ||
}); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<select ng-if="knownFieldTypes[field.name] !== 'geo_point'" name="field_type" ng-model="field.type" ng-change="buildRows()"> | ||
<option value="string">string</option> | ||
<option value="number">number</option> | ||
<option value="boolean">boolean</option> | ||
<option value="date">date</option> | ||
<option value="geo_point">geo_point</option> | ||
<option value="geo_shape">geo_shape</option> | ||
<option value="ip">ip</option> | ||
</select> | ||
|
||
<span ng-if="knownFieldTypes[field.name] === 'geo_point'"> | ||
geo_point | ||
</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a label, and disabled the save button if the pattern is empty. Spencer brought up the idea of using Angular's form validation as well, when reviewing the wizard shell PR. I think it's a good idea but I'm going to tackle it in another pull so that I can convert the entire wizard into a form, with each step being a sub-form, which will be a part of a larger effort to make the wizard itself more reusable.