[fieldEditor] added some tests

also fixed a bug here and bug there. No biggy
This commit is contained in:
Spencer Alger 2015-04-24 18:40:37 -07:00
parent 0977e26e82
commit a3bf70a85c
9 changed files with 220 additions and 51 deletions

View file

@ -2,18 +2,18 @@
<div ng-if="editor.creating" class="form-group">
<label>Name</label>
<input
ng-model="editor.fieldSpec.name"
ng-model="editor.fieldProps.name"
required
placeholder="New Scripted Field"
input-focus
class="form-control"
>
</div>
<div ng-if="editor.creating && editor.indexPattern.fields.byName[editor.fieldSpec.name]" class="hintbox">
<div ng-if="editor.creating && editor.indexPattern.fields.byName[editor.fieldProps.name]" class="hintbox">
<p>
<i class="fa fa-danger text-danger"></i>
<strong>Mapping Conflict:</strong>
You already have a field with the name {{ editor.fieldSpec.name }}. Naming your scripted
You already have a field with the name {{ editor.fieldProps.name }}. Naming your scripted
field with the same name means you won't be able to query both fields at the same time.
</p>
</div>
@ -88,6 +88,7 @@
<div class="form-group">
<button
type="button"
ng-click="editor.cancel()"
aria-label="Cancel"
class="btn btn-primary">

View file

@ -4,11 +4,14 @@ define(function (require) {
require('modules')
.get('kibana')
.directive('fieldEditor', function (Private) {
.directive('fieldEditor', function (Private, $sce) {
var _ = require('lodash');
var fieldFormats = Private(require('registry/field_formats'));
var Field = Private(require('components/index_patterns/_field'));
var scriptingInfo = $sce.trustAsHtml(require('text!components/field_editor/scripting_info.html'));
var scriptingWarning = $sce.trustAsHtml(require('text!components/field_editor/scripting_warning.html'));
return {
restrict: 'E',
template: require('text!components/field_editor/field_editor.html'),
@ -17,21 +20,17 @@ define(function (require) {
getField: '&field'
},
controllerAs: 'editor',
controller: function ($sce, $scope, Notifier, kbnUrl) {
controller: function ($scope, Notifier, kbnUrl) {
var self = this;
var notify = new Notifier({ location: 'Field Editor' });
self.indexPattern = $scope.getIndexPattern();
self.fieldSpec = Object.create($scope.getField().$$spec);
self.field = mutatedField();
self.selectedFormatId = _.get(self.indexPattern, ['fieldFormatMap', self.field.name, 'type', 'id']);
self.formatParams = self.field.format.params();
self.defFormatType = initDefaultFormat();
self.fieldFormatTypes = [self.defFormatType].concat(fieldFormats.byFieldType[self.field.type] || []);
self.creating = !self.indexPattern.fields.byName[self.field.name];
self.scriptingInfo = scriptingInfo;
self.scriptingWarning = scriptingWarning;
self.scriptingInfo = $sce.trustAsHtml(require('text!components/field_editor/scripting_info.html'));
self.scriptingWarning = $sce.trustAsHtml(require('text!components/field_editor/scripting_warning.html'));
self.indexPattern = $scope.getIndexPattern();
self.fieldProps = Object.create($scope.getField().$$spec);
createField();
self.formatParams = self.field.format.params();
self.cancel = function () {
kbnUrl.change(self.indexPattern.editRoute);
@ -48,7 +47,7 @@ define(function (require) {
if (!self.selectedFormatId) {
delete indexPattern.fieldFormatMap[field.name];
} else {
indexPattern.fieldFormatMap[field.name] = field.format;
indexPattern.fieldFormatMap[field.name] = self.format;
}
return indexPattern.save()
@ -73,26 +72,52 @@ define(function (require) {
$scope.$watchMulti([
'editor.selectedFormatId',
'=editor.formatParams',
'=editor.fieldSpec'
'=editor.fieldProps'
], function (cur, prev) {
var formatId = cur[0];
var updatedFormat = cur[0] !== prev[0];
var changedFormat = cur[0] !== prev[0];
var missingFormat = cur[0] && (!self.format || self.format.type.id !== cur[0]);
var changedParams = cur[1] !== prev[1];
var FieldFormat = getFieldFormatType();
if (updatedFormat) {
var FieldFormat = fieldFormats.byId[formatId];
if (FieldFormat) {
self.formatParams = _.cloneDeep(FieldFormat.paramDefaults || {});
self.fieldSpec.format = new FieldFormat(self.formatParams);
if (changedFormat) {
// the old params are no longer valid
self.formatParams = {};
}
if (!changedParams && (changedFormat || missingFormat)) {
self.formatParams = _.defaults(self.formatParams || {}, FieldFormat.paramDefaults);
if (!_.isEqual(self.formatParams, cur[1])) return;
}
if (changedParams || changedFormat || missingFormat) {
if (self.selectedFormatId) {
self.format = new FieldFormat(self.formatParams);
self.formatParams = self.format.params();
} else {
self.formatParams = self.fieldSpec.format = undefined;
self.format = undefined;
}
}
self.field = mutatedField();
createField();
});
function mutatedField() {
return new Field(self.indexPattern, self.fieldSpec);
function createField() {
var first = !self.field;
var spec = _.assign(Object.create(self.fieldProps), { format: self.format });
self.field = new Field(self.indexPattern, spec);
if (!first) return;
// only init on first create
self.creating = !self.indexPattern.fields.byName[self.field.name];
self.selectedFormatId = _.get(self.indexPattern, ['fieldFormatMap', self.field.name, 'type', 'id']);
if (self.selectedFormatId) self.format = self.field.format;
self.defFormatType = initDefaultFormat();
self.fieldFormatTypes = [self.defFormatType].concat(fieldFormats.byFieldType[self.field.type] || []);
}
function getFieldFormatType() {
if (self.selectedFormatId) return fieldFormats.getType(self.selectedFormatId);
else return fieldFormats.getDefaultType(self.field.type);
}
function initDefaultFormat() {

View file

@ -102,7 +102,7 @@ define(function (require) {
FieldFormat.prototype.toJSON = function () {
var type = this.type;
var defaults = type.paramDefaults;
var defaults = this._paramDefaults;
var params = _.transform(this._params, function (uniqParams, val, param) {
if (val !== defaults[param]) {

View file

@ -3,6 +3,7 @@ define(function (require) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format'));
var StringFormat = Private(require('components/stringify/types/String'));
require('components/stringify/pattern/pattern');
_(Url).inherits(FieldFormat);
@ -13,7 +14,7 @@ define(function (require) {
Url.id = 'url';
Url.title = 'Url';
Url.fieldType = 'string';
Url.fieldType = StringFormat.fieldType; // anything that can be serialized to a string is g2g!
Url.editor = {
template: require('text!components/stringify/editors/url.html'),
controllerAs: 'url',

View file

@ -16,7 +16,7 @@ define(function (require) {
kbnSetup(),
config.init(),
courier.SearchSource.ready,
$rootScope.kibana.ready
$rootScope.kibana && $rootScope.kibana.ready
])
.then(function () {
var path = $route.current.$$route.originalPath;

View file

@ -8,7 +8,6 @@
<script src="/node_modules/mocha/mocha.js"></script>
<script src="/src/kibana/bower_components/requirejs/require.js"></script>
<script src="/src/kibana/require.config.js"></script>
<script>(function () {
var COVERAGE = !!(/coverage/i.test(window.location.search));
var SAUCELABS = !!(/saucelabs/i.test(window.location.search));
@ -43,6 +42,7 @@
],
exports: 'angular'
},
'angular-route': ['angular'],
'sinon/sinon': {
deps: [
'sinon/sinon-timers-1.8.2'

View file

@ -0,0 +1,147 @@
define(function (require) {
describe('FieldEditor directive', function () {
var _ = require('lodash');
var $ = require('jquery');
var Field;
var StringFormat;
var $rootScope;
var compile;
var $scope;
var $el;
// just some properties of field that we can compare
var fieldProps = [
'name', 'type', 'count', 'scripted', 'script', 'lang',
'indexed', 'analyzed', 'doc_values', 'format', 'sortable',
'bucketable', 'filterable', 'indexPattern', 'displayName',
'editRoute'
];
var fieldToPrimativeVals = function (field) {
return _(field).pick(fieldProps).omit(function (v) {
return v && (typeof v === 'object' || typeof v === 'function');
}).value();
};
beforeEach(module('kibana'));
beforeEach(inject(function ($compile, $injector, Private) {
$rootScope = $injector.get('$rootScope');
Field = Private(require('components/index_patterns/_field'));
StringFormat = Private(require('registry/field_formats')).getType('string');
$rootScope.indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
$rootScope.field = $rootScope.indexPattern.fields.byName.time;
// set the field format for this field
$rootScope.indexPattern.fieldFormatMap[$rootScope.field.name] = new StringFormat({ foo: 1, bar: 2 });
compile = function () {
$el = $compile($('<field-editor field="field" index-pattern="indexPattern">'))($rootScope);
$scope = $el.data('$isolateScope');
};
}));
describe('$scope', function () {
it('is isolated', function () {
compile();
expect($scope.parent == null).to.be.ok();
expect($scope).to.not.be($rootScope);
});
it('exposes $scope.editor, a controller for the editor', function () {
compile();
var editor = $scope.editor;
expect(editor).to.be.an('object');
});
});
describe('$scope.editor', function () {
var editor;
beforeEach(function () {
compile();
editor = $scope.editor;
});
it('exposes editor.indexPattern', function () {
expect(editor.indexPattern).to.be($rootScope.indexPattern);
});
it('exposes editor.fieldProps', function () {
expect(editor.fieldProps).to.be.an('object');
});
describe('editor.fieldProps', function () {
it('is a shadow copy of the index patterns field spec', function () {
var actual = $rootScope.field;
var spec = editor.fieldProps;
expect(spec).to.not.be(actual.$$spec);
expect(actual.$$spec.isPrototypeOf(spec)).to.be(true);
});
});
it('exposes editor.field', function () {
expect(editor.field).to.be.an('object');
});
describe('editor.field', function () {
it('looks like the field from the index pattern, but isn\'t', function () {
var field = editor.field;
var actual = $rootScope.field;
expect(field).to.not.be(actual);
expect(field).to.be.a(Field);
expect(fieldToPrimativeVals(field)).to.eql(fieldToPrimativeVals(actual));
});
it('is built to match the editor.fieldProps', function () {
expect(editor.field).to.have.property('name', editor.fieldProps.name);
expect(editor.field).to.have.property('type', editor.fieldProps.type);
editor.fieldProps.name = 'smith';
$rootScope.$apply();
expect(editor.field.name).to.be('smith');
});
it('is rebuilt when the fieldProps changes', function () {
expect(editor.field.name).to.be(editor.fieldProps.name);
var newName = editor.fieldProps.name = editor.fieldProps.name + 'foo';
expect(editor.field.name).to.not.be(newName); // rebuilt after $digest
$rootScope.$apply();
expect(editor.field.name).to.be(newName);
expect(editor.fieldProps.name).to.be(newName);
});
});
it('exposes editor.formatParams', function () {
expect(editor).to.have.property('formatParams');
expect(editor.field.format.params()).to.eql(editor.formatParams);
});
describe('editor.formatParams', function () {
it('initializes with all of the formats current params', function () {
// rebuild the editor
compile();
editor = $scope.editor;
expect(editor.formatParams).to.have.property('foo', 1);
expect(editor.formatParams).to.have.property('bar', 2);
});
it('updates the fields format when changed', function () {
$rootScope.$apply(); // initial apply in order to pick up change
editor.formatParams.foo = 200;
$rootScope.$apply();
expect(editor.field.format.param('foo')).to.be(200);
});
});
});
});
});

View file

@ -49,7 +49,7 @@ define(function (require) {
expect(results[2].meta).to.have.property('key', 'query');
expect(results[2].meta).to.have.property('value', 'foo:bar');
expect(results[3].meta).to.have.property('key', 'bytes');
expect(results[3].meta).to.have.property('value', '1024 to 2048');
expect(results[3].meta).to.have.property('value', '1,024 to 2,048');
expect(results[4].meta).to.have.property('key', '_type');
expect(results[4].meta).to.have.property('value', 'apache');
done();

View file

@ -3,35 +3,30 @@ define(function (require) {
var _ = require('lodash');
var sinon = require('sinon/sinon');
var IndexedArray = require('utils/indexed_array/index');
var fieldFormats = Private(require('registry/field_formats'));
var flattenHit = require('components/index_patterns/_flatten_hit');
var getComputedFields = require('components/index_patterns/_get_computed_fields');
var fieldFormats = Private(require('registry/field_formats'));
var Field = Private(require('components/index_patterns/_field'));
function StubIndexPattern(pattern, timeField, fields) {
this.id = pattern;
this.popularizeField = sinon.spy();
this.timeFieldName = timeField;
this.fields = new IndexedArray({
index: ['name'],
group: ['type'],
initialSet: fields.map(function (field) {
field.count = field.count || 0;
// non-enumerable type so that it does not get included in the JSON
Object.defineProperty(field, 'format', {
enumerable: false,
get: function () {
return fieldFormats.getDefaultInstance(field.type);
}
});
return field;
})
});
this.getFields = sinon.spy();
this.toIndexList = _.constant([pattern]);
this.getComputedFields = getComputedFields;
this.flattenHit = _.partial(flattenHit, this);
this.metaFields = ['_id', '_type', '_source'];
this.fieldFormatMap = {};
this.fields = new IndexedArray({
index: ['name'],
group: ['type'],
initialSet: fields.map(function (field) {
return new Field(this, field);
}, this)
});
}
return StubIndexPattern;