Rewrite WB Action->Base to use @ref facility
Now all standard actions are put into top-level Barricade object in WB controller and then Base field just fetches id-s from them (and we use them same standardActions top-level property for resetting a 'Base Input' field with a list of keys corresponding to a specific standardAction. Also new unit-tests (for filters and for dictionary Merlin model) are added. Change-Id: Ieb6e9330db8fbeb83e4f0f2a64611e1b6b31006c Closes-Bug: #1467511
This commit is contained in:
parent
d5d9321eca
commit
7ccf4f0dd3
@ -20,6 +20,14 @@
|
||||
} else {
|
||||
$scope.workbook = models.Workbook.create({name: 'My Workbook'});
|
||||
}
|
||||
$scope.root = models.Root.create();
|
||||
$scope.root.set('workbook', $scope.workbook);
|
||||
|
||||
$scope.root.set('standardActions', {
|
||||
'nova.create_server': ['image', 'flavor', 'network_id'],
|
||||
'neutron.create_network': ['name', 'create_subnet'],
|
||||
'glance.create_image': ['image_url']
|
||||
});
|
||||
};
|
||||
|
||||
function getNextIDSuffix(container, regexp) {
|
||||
|
@ -108,13 +108,13 @@
|
||||
var self = fields.frozendict.create.call(this, json, parameters),
|
||||
base = self.get('base');
|
||||
base.on('change', function(operation) {
|
||||
var baseValue;
|
||||
var argsEntry, pos, entry;
|
||||
if ( operation != 'id' ) {
|
||||
baseValue = base.get();
|
||||
if ( baseValue ) {
|
||||
base.getSchema(baseValue).then(function(keys) {
|
||||
self.get('base-input').setSchema(keys);
|
||||
});
|
||||
pos = base._stdActions.getPosByID(base.get());
|
||||
if ( pos > -1 ) {
|
||||
entry = self.get('base-input');
|
||||
argsEntry = base._stdActions.get(pos);
|
||||
entry.resetKeys(argsEntry.toJSON());
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -125,44 +125,66 @@
|
||||
'@class': fields.string.extend({
|
||||
create: function(json, parameters) {
|
||||
var self = fields.string.create.call(this, json, parameters),
|
||||
schema = {},
|
||||
url = utils.getMeta(self, 'autocompletionUrl');
|
||||
stdActionsCls = Barricade.create({
|
||||
'@type': String,
|
||||
'@ref': {
|
||||
to: function() {
|
||||
return fields.StandardActions;
|
||||
},
|
||||
needs: function() {
|
||||
return models.Root;
|
||||
},
|
||||
getter: function(data) {
|
||||
return data.needed.get('standardActions');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.getSchema = function(key) {
|
||||
var deferred = $q.defer();
|
||||
if ( !(key in schema) ) {
|
||||
$http.get(url+'?key='+key).success(function(keys) {
|
||||
schema[key] = keys;
|
||||
deferred.resolve(keys);
|
||||
}).error(function() {
|
||||
deferred.reject();
|
||||
self._stdActions = stdActionsCls.create().on(
|
||||
'replace', function(newValue) {
|
||||
self._stdActions = newValue;
|
||||
self._stdActions.on('change', function() {
|
||||
self._choices = self._stdActions.getIDs();
|
||||
self.resetValues();
|
||||
});
|
||||
} else {
|
||||
deferred.resolve(schema[key]);
|
||||
}
|
||||
return deferred.promise;
|
||||
};
|
||||
self._stdActions.emit('change');
|
||||
});
|
||||
|
||||
return self;
|
||||
}
|
||||
},
|
||||
_choices: []
|
||||
}, {
|
||||
'@enum': function() {
|
||||
if ( this._stdActions.isPlaceholder() ) {
|
||||
this.emit('_resolveUp', this._stdActions);
|
||||
}
|
||||
return this._choices;
|
||||
},
|
||||
'@meta': {
|
||||
'index': 1,
|
||||
'row': 0,
|
||||
'autocompletionUrl': '/project/mistral/actions/types'
|
||||
'row': 0
|
||||
}
|
||||
})
|
||||
},
|
||||
'base-input': {
|
||||
'@class': fields.directeddictionary.extend({}, {
|
||||
'@class': fields.dictionary.extend({
|
||||
create: function(json, parameters) {
|
||||
var self = fields.dictionary.create.call(this, json, parameters);
|
||||
self.setType('frozendict');
|
||||
return self;
|
||||
}
|
||||
}, {
|
||||
'@required': false,
|
||||
'?': {
|
||||
'@class': fields.string.extend({}, {
|
||||
'@meta': {
|
||||
'row': 0
|
||||
}
|
||||
})
|
||||
},
|
||||
'@meta': {
|
||||
'index': 2,
|
||||
'title': 'Base Input'
|
||||
},
|
||||
'?': {
|
||||
'@class': fields.string.extend({}, {
|
||||
'@meta': {'row': 1}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
@ -409,11 +431,10 @@
|
||||
});
|
||||
return self;
|
||||
},
|
||||
_choices: null
|
||||
_choices: []
|
||||
}, {
|
||||
'@enum': function() {
|
||||
if (!this._choices) {
|
||||
this._choices = [];
|
||||
if ( this._actions.isPlaceholder() ) {
|
||||
this.emit('_resolveUp', this._actions);
|
||||
}
|
||||
return this._choices;
|
||||
@ -459,11 +480,10 @@
|
||||
});
|
||||
return self;
|
||||
},
|
||||
_choices: null
|
||||
_choices: []
|
||||
}, {
|
||||
'@enum': function() {
|
||||
if ( !this._choices ) {
|
||||
this._choices = [];
|
||||
if ( this._workflows.isPlaceholder() ) {
|
||||
this.emit('_resolveUp', this._workflows);
|
||||
}
|
||||
return this._choices;
|
||||
@ -548,7 +568,7 @@
|
||||
params.id = taskId;
|
||||
self.set(taskPos, TaskFactory(taskData, params));
|
||||
} else if ( op === 'taskRemove' ) {
|
||||
self.remove(arg);
|
||||
self.removeItem(arg);
|
||||
}
|
||||
});
|
||||
return self;
|
||||
@ -702,6 +722,26 @@
|
||||
}
|
||||
});
|
||||
|
||||
models.StandardActions = Barricade.create({
|
||||
'@type': Object,
|
||||
'?': {
|
||||
'@type': Array,
|
||||
'*': {
|
||||
'@type': String
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
models.Root = Barricade.ImmutableObject.extend({}, {
|
||||
'@type': Object,
|
||||
'standardActions': {
|
||||
'@class': models.StandardActions
|
||||
},
|
||||
'workbook': {
|
||||
'@class': models.Workbook
|
||||
}
|
||||
});
|
||||
|
||||
return models;
|
||||
}])
|
||||
})();
|
||||
|
@ -208,8 +208,8 @@
|
||||
var regexp = new RegExp('(' + baseKey + ')([0-9]+)'),
|
||||
newValue;
|
||||
newID = newID || baseKey + utils.getNextIDSuffix(self, regexp);
|
||||
if (_elClass.instanceof(Barricade.ImmutableObject)) {
|
||||
if ('name' in _elClass._schema) {
|
||||
if ( _elClass.instanceof(Barricade.ImmutableObject) ) {
|
||||
if ( 'name' in _elClass._schema ) {
|
||||
var nameNum = utils.getNextIDSuffix(self, regexp);
|
||||
newValue = {name: baseName + nameNum};
|
||||
} else {
|
||||
@ -229,40 +229,33 @@
|
||||
}
|
||||
return _items;
|
||||
};
|
||||
self.empty = function() {
|
||||
for ( var i = this._data.length; i > 0; i-- ) {
|
||||
self.remove(i-1);
|
||||
}
|
||||
_items = [];
|
||||
};
|
||||
self.resetKeys = function(keys) {
|
||||
self.empty();
|
||||
keys.forEach(function(key) {
|
||||
self.push(undefined, {id: key});
|
||||
});
|
||||
};
|
||||
self._getContents = function() {
|
||||
return self.toArray();
|
||||
};
|
||||
self.remove = function(key) {
|
||||
self.removeItem = function(key) {
|
||||
var pos = self.getPosByID(key);
|
||||
Barricade.MutableObject.remove.call(self, pos);
|
||||
self.remove(self.getPosByID(key));
|
||||
_items.splice(pos, 1);
|
||||
};
|
||||
meldGroup.call(self);
|
||||
// initialize cache with starting values
|
||||
self.getValues();
|
||||
return self;
|
||||
}
|
||||
}, {'@type': Object});
|
||||
|
||||
var directedDictionaryModel = dictionaryModel.extend({
|
||||
create: function(json, parameters) {
|
||||
var self = dictionaryModel.create.call(this, json, parameters);
|
||||
self.setType('frozendict');
|
||||
return self;
|
||||
},
|
||||
setSchema: function(keys) {
|
||||
var self = this;
|
||||
if ( keys !== undefined && keys !== null ) {
|
||||
self.getIDs().forEach(function(oldKey) {
|
||||
self.remove(oldKey);
|
||||
});
|
||||
keys.forEach(function(newKey) {
|
||||
self.add(newKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, {
|
||||
'?': {'@type': String}
|
||||
});
|
||||
|
||||
return {
|
||||
string: stringModel,
|
||||
text: textModel,
|
||||
@ -270,7 +263,6 @@
|
||||
list: listModel,
|
||||
dictionary: dictionaryModel,
|
||||
frozendict: frozendictModel,
|
||||
directeddictionary: directedDictionaryModel,
|
||||
autocompletionmixin: autoCompletionMixin,
|
||||
wildcard: wildcardMixin // use for most general type-checks
|
||||
};
|
||||
|
@ -52,8 +52,9 @@
|
||||
}
|
||||
},
|
||||
remove: function() {
|
||||
var container = this._barricadeContainer;
|
||||
container.remove.call(container, this._barricadeId);
|
||||
var container = this._barricadeContainer,
|
||||
pos = container.getPosByID(this._barricadeId);
|
||||
container.remove(pos);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
ng-model="subvalue.value" ng-model-options="{ getterSetter: true }">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default fa fa-minus-circle" type="button"
|
||||
ng-click="value.remove(subvalue.keyValue())"></button>
|
||||
ng-click="value.removeItem(subvalue.keyValue())"></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,11 @@
|
||||
<collapsible-group content="value">
|
||||
<div ng-repeat="row in value | extractRows track by row.id">
|
||||
<div ng-class="{'three-columns': row.index !== undefined}">
|
||||
<div ng-repeat="item in row | extractItems track by item.id"
|
||||
<div ng-repeat="item in row | extractItems track by item.uid()"
|
||||
ng-class="{'right-column': item.isAtomic() && $odd, 'left-column': item.isAtomic() && $even}">
|
||||
<div class="form-group">
|
||||
<label for="elem-{$ $id $}.{$ item.getID() $}">{$ item.title() $}</label>
|
||||
<input type="text" class="form-control" id="elem-{$ $id $}.{$ item.getID() $}" ng-model="item.value"
|
||||
<label for="elem-{$ $id $}.{$ item.uid() $}">{$ item.title() $}</label>
|
||||
<input type="text" class="form-control" id="elem-{$ $id $}.{$ item.uid() $}" ng-model="item.value"
|
||||
ng-model-options="{getterSetter: true}">
|
||||
</div>
|
||||
<div class="clearfix" ng-if="$odd"></div>
|
||||
|
@ -159,27 +159,46 @@ describe('merlin filters', function() {
|
||||
});
|
||||
|
||||
it('are given a separate panel for each MutableObject entry', function() {
|
||||
var panels;
|
||||
topLevelObj.set('key2', {
|
||||
'id1': {'name': 'String1'},
|
||||
'id2': {'name': 'String2'}
|
||||
});
|
||||
var panels = extractPanels(topLevelObj);
|
||||
panels = extractPanels(topLevelObj);
|
||||
|
||||
expect(panels.length).toBe(2);
|
||||
});
|
||||
|
||||
it('have their title exposed via .title() which mirrors their id', function() {
|
||||
describe('', function() {
|
||||
var panels;
|
||||
topLevelObj.set('key2', {'id1': {'name': 'some name'}});
|
||||
panels = extractPanels(topLevelObj);
|
||||
expect(panels[0].title()).toBe('id1');
|
||||
});
|
||||
|
||||
it('are removable (thus are not permanent)', function() {
|
||||
var panels;
|
||||
topLevelObj.set('key2', {'id1': {'name': 'String1'}});
|
||||
panels = extractPanels(topLevelObj);
|
||||
beforeEach(function() {
|
||||
topLevelObj.set('key2', {'id1': {'name': 'some name'}});
|
||||
panels = extractPanels(topLevelObj);
|
||||
});
|
||||
|
||||
it('have their title exposed via .title() which mirrors their id', function() {
|
||||
expect(panels[0].title()).toBe('id1');
|
||||
});
|
||||
|
||||
it("panel's title() acts also as a setter of the underlying object id", function() {
|
||||
panels[0].title('id2');
|
||||
|
||||
expect(panels[0].title()).toBe('id2');
|
||||
expect(topLevelObj.get('key2').getByID('id2')).toBeDefined();
|
||||
});
|
||||
|
||||
it('are removable (thus are not permanent)', function() {
|
||||
expect(panels[0].removable).toBe(true);
|
||||
});
|
||||
|
||||
it('remove() function actually removes a panel', function() {
|
||||
panels[0].remove();
|
||||
panels = extractPanels(topLevelObj);
|
||||
|
||||
expect(panels.length).toBe(0);
|
||||
});
|
||||
|
||||
expect(panels[0].removable).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
@ -360,7 +379,7 @@ describe('merlin filters', function() {
|
||||
immutableObj.set('key2', {'id_1': {key1: 'String_1'}});
|
||||
panels1 = extractPanels(immutableObj);
|
||||
|
||||
immutableObj.get('key2').remove('id_1');
|
||||
immutableObj.get('key2').removeItem('id_1');
|
||||
immutableObj.set('key2', {'id_1': {key1: 'String_1'}});
|
||||
panels2 = extractPanels(immutableObj);
|
||||
|
||||
@ -645,7 +664,7 @@ describe('merlin filters', function() {
|
||||
rows1 = extractRows(mutableObj),
|
||||
rows2;
|
||||
|
||||
mutableObj.remove('id1');
|
||||
mutableObj.removeItem('id1');
|
||||
mutableObj.push('string1', {id: 'id1'});
|
||||
rows2 = extractRows(mutableObj);
|
||||
|
||||
|
144
merlin/test/js/modelsSpec.js
Normal file
144
merlin/test/js/modelsSpec.js
Normal file
@ -0,0 +1,144 @@
|
||||
|
||||
/* Copyright (c) 2015 Mirantis, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
not use this file except in compliance with the License. You may obtain
|
||||
a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations
|
||||
under the License.
|
||||
*/
|
||||
describe('merlin models:', function() {
|
||||
'use strict';
|
||||
|
||||
var fields;
|
||||
|
||||
beforeEach(function() {
|
||||
module('merlin');
|
||||
|
||||
inject(function($injector) {
|
||||
fields = $injector.get('merlin.field.models');
|
||||
})
|
||||
});
|
||||
|
||||
describe('dictionary field', function() {
|
||||
var dictObj;
|
||||
|
||||
beforeEach(function() {
|
||||
dictObj = fields.dictionary.extend({}, {
|
||||
'?': {
|
||||
'@class': fields.string
|
||||
}
|
||||
}).create({'id1': 'string1', 'id2': 'string2'});
|
||||
});
|
||||
|
||||
function getValueFromCache(id) {
|
||||
var value = undefined;
|
||||
dictObj.getValues().forEach(function(item) {
|
||||
if ( item.getID() === id ) {
|
||||
value = item;
|
||||
}
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
function getCacheIDs() {
|
||||
return dictObj.getValues().map(function(item) {
|
||||
return item.getID();
|
||||
});
|
||||
}
|
||||
|
||||
describe('getValues() method', function() {
|
||||
it('caching works from the very beginning', function() {
|
||||
expect(getCacheIDs()).toEqual(['id1', 'id2']);
|
||||
});
|
||||
|
||||
it('keyValue() getter/setter can be used from the start', function() {
|
||||
var value = getValueFromCache('id1');
|
||||
|
||||
expect(value.keyValue()).toBe('id1');
|
||||
|
||||
value.keyValue('id3');
|
||||
expect(value.keyValue()).toBe('id3');
|
||||
expect(dictObj.getByID('id3')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('add() method', function() {
|
||||
it('adds an empty value with given key', function() {
|
||||
dictObj.add('id3');
|
||||
|
||||
expect(dictObj.getByID('id3').get()).toBe('');
|
||||
expect(getCacheIDs()).toEqual(['id1', 'id2', 'id3']);
|
||||
});
|
||||
|
||||
it('keyValue() getter/setter can be used for added values', function() {
|
||||
var value;
|
||||
|
||||
dictObj.add('id3');
|
||||
value = getValueFromCache('id3');
|
||||
|
||||
expect(value.keyValue()).toBe('id3');
|
||||
|
||||
value.keyValue('id4');
|
||||
expect(value.keyValue()).toBe('id4');
|
||||
expect(dictObj.getByID('id4')).toBeDefined();
|
||||
});
|
||||
|
||||
it('updates name automatically if baseName and baseKey are provided', function() {
|
||||
var nestedDictObj = fields.dictionary.extend({}, {
|
||||
'?': {
|
||||
'@class': fields.frozendict.extend({}, {
|
||||
'name': {
|
||||
'@class': fields.string
|
||||
},
|
||||
'@meta': {
|
||||
'baseName': 'Action ',
|
||||
'baseKey': 'action'
|
||||
}
|
||||
})
|
||||
}
|
||||
}).create({'action1': {'name': "Action 1"}});
|
||||
|
||||
nestedDictObj.add('action2');
|
||||
|
||||
expect(nestedDictObj.getByID('action2').get('name').get()).toEqual('Action 2');
|
||||
})
|
||||
});
|
||||
|
||||
describe('empty() method', function() {
|
||||
it('removes all entries in model and in cache', function() {
|
||||
dictObj.empty();
|
||||
|
||||
expect(dictObj.getIDs().length).toBe(0);
|
||||
expect(dictObj.getValues().length).toBe(0);
|
||||
})
|
||||
});
|
||||
|
||||
describe('resetKeys() method', function() {
|
||||
it('re-sets dictionary contents to given keys, cache included', function() {
|
||||
dictObj.resetKeys(['key1', 'key2']);
|
||||
|
||||
expect(dictObj.getIDs()).toEqual(['key1', 'key2']);
|
||||
expect(dictObj.getByID('key1').get()).toBe('');
|
||||
expect(dictObj.getByID('key2').get()).toBe('');
|
||||
expect(getCacheIDs()).toEqual(['key1', 'key2']);
|
||||
})
|
||||
});
|
||||
|
||||
describe('removeItem() method', function() {
|
||||
it('removes dictionary entry by key from model and cache', function() {
|
||||
dictObj.removeItem('id1');
|
||||
|
||||
expect(dictObj.getByID('id1')).toBeUndefined();
|
||||
expect(getCacheIDs()).toEqual(['id2']);
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user