diff --git a/extensions/mistral/static/mistral/js/mistral.workbook.controllers.js b/extensions/mistral/static/mistral/js/mistral.workbook.controllers.js index 6159623..77d15e9 100644 --- a/extensions/mistral/static/mistral/js/mistral.workbook.controllers.js +++ b/extensions/mistral/static/mistral/js/mistral.workbook.controllers.js @@ -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) { diff --git a/extensions/mistral/static/mistral/js/mistral.workbook.models.js b/extensions/mistral/static/mistral/js/mistral.workbook.models.js index b711933..d4b3a95 100644 --- a/extensions/mistral/static/mistral/js/mistral.workbook.models.js +++ b/extensions/mistral/static/mistral/js/mistral.workbook.models.js @@ -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; }]) })(); diff --git a/merlin/static/merlin/js/merlin.field.models.js b/merlin/static/merlin/js/merlin.field.models.js index 289f515..6be62f3 100644 --- a/merlin/static/merlin/js/merlin.field.models.js +++ b/merlin/static/merlin/js/merlin.field.models.js @@ -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 }; diff --git a/merlin/static/merlin/js/merlin.filters.js b/merlin/static/merlin/js/merlin.filters.js index aa26486..106ff7d 100644 --- a/merlin/static/merlin/js/merlin.filters.js +++ b/merlin/static/merlin/js/merlin.filters.js @@ -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); } }; diff --git a/merlin/static/merlin/templates/fields/dictionary.html b/merlin/static/merlin/templates/fields/dictionary.html index 529f5d5..6d50bb0 100644 --- a/merlin/static/merlin/templates/fields/dictionary.html +++ b/merlin/static/merlin/templates/fields/dictionary.html @@ -10,7 +10,7 @@ ng-model="subvalue.value" ng-model-options="{ getterSetter: true }"> + ng-click="value.removeItem(subvalue.keyValue())"> diff --git a/merlin/static/merlin/templates/fields/frozendict.html b/merlin/static/merlin/templates/fields/frozendict.html index 671ce25..a4553c9 100644 --- a/merlin/static/merlin/templates/fields/frozendict.html +++ b/merlin/static/merlin/templates/fields/frozendict.html @@ -1,11 +1,11 @@
-
- - {$ item.title() $} +
diff --git a/merlin/test/js/filtersSpec.js b/merlin/test/js/filtersSpec.js index a6ab45f..91102f9 100644 --- a/merlin/test/js/filtersSpec.js +++ b/merlin/test/js/filtersSpec.js @@ -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); diff --git a/merlin/test/js/modelsSpec.js b/merlin/test/js/modelsSpec.js new file mode 100644 index 0000000..cd0cbc0 --- /dev/null +++ b/merlin/test/js/modelsSpec.js @@ -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']); + }) + }); + + }); +}); \ No newline at end of file