Fix changing Action/Workflow/Task id

To make it happen <collapsible-group> directive had to undergo the
same transformation that was applied to <panel> directive: instead of
passing just a '@title' reference, the whole '=content' reference is
now passed to the <collapsible-group>'s scope. This allows to use
<editable> directive inside it with 'ng-model=group.title' as
getter/setter method. Yet <collapsible-group>'s scope wasn't remade as
radically as <panel>'s was - '&on-add' and '&on-remove' are still
there and left for future refactoring.

Change-Id: I4de7a542f282efee6deb34d4957a5873d617ad64
Closes-Bug: #1446171
Closes-Bug: #1446226
This commit is contained in:
Timur Sufiev 2015-04-28 17:45:07 +03:00
parent f219a63e21
commit 531dc56c64
21 changed files with 172 additions and 102 deletions

View File

@ -5,10 +5,12 @@
'use strict';
angular.module('mistral')
.value('baseActionID', 'action')
.value('baseWorkflowID', 'workflow')
.controller('workbookCtrl',
['$scope', 'mistral.workbook.models', function($scope, models) {
var workbook = models.Workbook.create({name: 'My Workbook'});
$scope.workbook = workbook;
['$scope', 'mistral.workbook.models', 'baseActionID', 'baseWorkflowID',
function($scope, models, baseActionId, baseWorkflowId) {
$scope.workbook = models.Workbook.create({name: 'My Workbook'});
function getNextIDSuffix(container, regexp) {
var max = Math.max.apply(Math, container.getIDs().map(function(id) {
@ -28,8 +30,6 @@
return getNextIDSuffix(container, regexp);
}
var baseActionId = 'action', baseWorkflowId = 'workflow';
$scope.addAction = function() {
var nextSuffix = getWorkbookNextIDSuffix(baseActionId),
newID = baseActionId + nextSuffix;

View File

@ -119,21 +119,8 @@
}
});
return self;
},
_getPrettyJSON: function() {
var json = fields.frozendict._getPrettyJSON.apply(this, arguments);
delete json.name;
return json;
}
}, {
'name': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 0,
'row': 0
}
})
},
'base': {
'@class': fields.string.extend({
create: function(json, parameters) {
@ -209,11 +196,6 @@
remove: function() {
this.emit('change', 'taskRemove', this.getID());
},
_getPrettyJSON: function() {
var json = fields.frozendict._getPrettyJSON.apply(this, arguments);
delete json.name;
return json;
}
}, {
'@meta': {
'baseKey': 'task',
@ -222,14 +204,6 @@
'additive': false,
'removable': true
},
'name': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 0,
'row': 0
}
})
},
'type': {
'@class': fields.string.extend({}, {
'@enum': [{
@ -239,7 +213,7 @@
}],
'@default': 'action',
'@meta': {
'index': 1,
'index': 0,
'row': 0
}
})
@ -247,7 +221,7 @@
'description': {
'@class': fields.text.extend({}, {
'@meta': {
'index': 1,
'index': 2,
'row': 1
}
})
@ -403,8 +377,8 @@
'action': {
'@class': fields.string.extend({}, {
'@meta': {
'row': 1,
'index': 2
'row': 0,
'index': 1
}
})
}
@ -416,8 +390,8 @@
'workflow': {
'@class': fields.string.extend({}, {
'@meta': {
'row': 1,
'index': 2
'row': 0,
'index': 1
}
})
}
@ -448,21 +422,8 @@
}
});
return self;
},
_getPrettyJSON: function() {
var json = fields.frozendict._getPrettyJSON.apply(this, arguments);
delete json.name;
return json;
}
}, {
'name': {
'@class': fields.string.extend({}, {
'@meta': {
'index': 0,
'row': 0
}
})
},
'type': {
'@class': fields.string.extend({}, {
'@enum': ['reverse', 'direct'],

View File

@ -1,4 +1,4 @@
<collapsible-group title="{$ title $}"
<collapsible-group content="value"
on-add="value.add()">
<div class="three-columns" ng-repeat="subItem in value.getValues() track by $index"
ng-class="subItem.get('type').get()">

View File

@ -1,7 +1,6 @@
<collapsible-group title="{$ title $}" on-add="value.add()">
<collapsible-group content="value" on-add="value.add()">
<div class="three-columns"
ng-repeat="subItem in value.getValues() track by $index">
<pre>{$ subItem $}</pre>
<div class="left-column" ng-show="subItem.showYaql">
<div class="form-group">
<textarea class="form-control" ng-model="subItem.get('yaql').value"

View File

@ -68,7 +68,7 @@
<div ng-class="{'two-columns': row.index !== undefined }">
<div ng-repeat="item in row | extractItems track by item.id"
ng-class="{'right-column': item.isAtomic() && $odd, 'left-column': item.isAtomic() && $even}">
<typed-field title="{$ item.getTitle() $}" value="item" type="{$ item.getType() $}"></typed-field>
<typed-field value="item" type="{$ item.getType() $}"></typed-field>
<div class="clearfix" ng-if="$odd"></div>
</div>
</div>

View File

@ -74,15 +74,37 @@ describe('workbook model logic', function() {
workbook.get('workflows').push({name: 'Workflow 1'}, {id: workflowID});
});
it('a task deletion works in conjunction with tasks logic', function() {
var workflow = getWorkflow(workflowID),
params = utils.extend(workflow._parameters, {id: taskID});
describe('', function() {
beforeEach(function() {
var workflow = getWorkflow(workflowID),
params = workflow._parameters;
workflow.get('tasks').push({name: 'Task 1'}, utils.extend(params, {id: taskID}));
});
workflow.get('tasks').push({name: 'Task 1'}, params);
expect(getTask(taskID)).toBeDefined();
it("corresponding JSON has the right key for the Task", function() {
var json = workbook.toJSON({pretty: true});
expect(json.workflows[workflowID].tasks[taskID]).toBeDefined();
});
it("once the task ID is changed, it's reflected in JSON", function() {
var newID = 'task10',
json;
getTask(taskID).setID(newID);
json = workbook.toJSON({pretty: true});
expect(json.workflows[workflowID].tasks[taskID]).toBeUndefined();
expect(json.workflows[workflowID].tasks[newID]).toBeDefined();
});
it('a task deletion works in conjunction with tasks logic', function() {
expect(getTask(taskID)).toBeDefined();
getTask(taskID).remove();
expect(getTask(taskID)).toBeUndefined();
});
getTask(taskID).remove();
expect(getTask(taskID)).toBeUndefined();
});
describe("which start with the 'direct' workflow:", function() {
@ -200,15 +222,39 @@ describe('workbook model logic', function() {
it('creates action with predefined name', function() {
$scope.addAction();
expect(workbook.get('actions').get(0).get('name').get()).toBeGreaterThan('');
expect(workbook.get('actions').get(0).getID()).toBeGreaterThan('');
});
describe('', function() {
var actionID;
beforeEach(inject(function(baseActionID) {
actionID = baseActionID + '1';
}));
it("corresponding JSON has the right key for the Action", function() {
$scope.addAction();
expect(workbook.toJSON({pretty: true}).actions[actionID]).toBeDefined();
});
it("once the Action ID is changed, it's reflected in JSON", function() {
var newID = 'action10';
$scope.addAction();
workbook.get('actions').getByID(actionID).setID(newID);
expect(workbook.toJSON({pretty: true}).actions[actionID]).toBeUndefined();
expect(workbook.toJSON({pretty: true}).actions[newID]).toBeDefined();
});
});
it('creates actions with different names on 2 successive calls', function() {
$scope.addAction();
$scope.addAction();
expect(workbook.get('actions').get(0).get('name').get()).not.toEqual(
workbook.get('actions').get(1).get('name').get())
expect(workbook.get('actions').get(0).getID()).not.toEqual(
workbook.get('actions').get(1).getID())
});
});
@ -219,21 +265,45 @@ describe('workbook model logic', function() {
expect(workbook.get('workflows').get(0)).toBeDefined();
});
describe('', function() {
var workflowID;
beforeEach(inject(function(baseWorkflowID) {
workflowID = baseWorkflowID + '1';
}));
it("corresponding JSON has the right key for the Workflow", function() {
$scope.addWorkflow();
expect(workbook.toJSON({pretty: true}).workflows[workflowID]).toBeDefined();
});
it("once the workflow ID is changed, it's reflected in JSON", function() {
var newID = 'workflow10';
$scope.addWorkflow();
workbook.get('workflows').getByID(workflowID).setID(newID);
expect(workbook.toJSON({pretty: true}).workflows[workflowID]).toBeUndefined();
expect(workbook.toJSON({pretty: true}).workflows[newID]).toBeDefined();
});
});
it('creates workflow with predefined name', function() {
$scope.addWorkflow();
expect(workbook.get('workflows').get(0).get('name').get()).toBeGreaterThan('');
expect(workbook.get('workflows').get(0).getID()).toBeGreaterThan('');
});
it('creates workflows with different names on 2 successive calls', function() {
$scope.addWorkflow();
$scope.addWorkflow();
expect(workbook.get('workflows').get(0).get('name').get()).not.toEqual(
workbook.get('workflows').get(1).get('name').get())
expect(workbook.get('workflows').get(0).getID()).not.toEqual(
workbook.get('workflows').get(1).getID())
});
});
})
});
});

View File

@ -96,7 +96,7 @@
templateUrl: '/static/merlin/templates/collapsible-group.html',
transclude: true,
scope: {
title: '@',
group: '=content',
onAdd: '&',
onRemove: '&'
},
@ -117,7 +117,6 @@
return {
restrict: 'E',
scope: {
title: '@',
value: '=',
type: '@'
},

View File

@ -52,7 +52,7 @@
this.isAtomic = function() {
return ['number', 'string', 'text', 'choices'].indexOf(this.getType()) > -1;
};
this.getTitle = function() {
this.title = function() {
var title = utils.getMeta(this, 'title');
if ( !title ) {
if ( this.instanceof(Barricade.ImmutableObject) ) {

View File

@ -37,13 +37,14 @@
return this;
},
title: function() {
var entity;
var newID;
if ( this._barricadeContainer ) {
entity = this._barricadeContainer.getByID(this._barricadeId).get('name');
if ( arguments.length ) {
entity.set(arguments[0]);
newID = arguments[0];
this._barricadeContainer.getByID(this._barricadeId).setID(newID);
this._barricadeId = newID;
} else {
return entity.get();
return this._barricadeId;
}
}
},

View File

@ -26,6 +26,16 @@
return removable;
};
if ( removable ) { // conditionally override common .title()
self.title = function() {
if ( arguments.length ) {
self.setID(arguments[0]);
} else {
return self.getID();
}
}
}
self.setType('group');
return self;

View File

@ -47,8 +47,10 @@
}
function getMeta(item, key) {
var meta = item._schema['@meta'];
return meta && meta[key];
if ( item ) {
var meta = item._schema['@meta'];
return meta && meta[key];
}
}
function makeTitle(str) {

View File

@ -3,13 +3,16 @@
<div class="both-columns">
<h5><a ng-click="isCollapsed = !isCollapsed" class="collapse-entries" href="#">
<i class="fa" ng-class="isCollapsed ? 'fa-plus-square-o' : 'fa-minus-square-o'"></i></a>
{$ title $}</h5>
<editable ng-if="removable" ng-model="group.title"
ng-model-options="{getterSetter: true}"></editable>
<span ng-if="!removable">{$ group.title() $}</span>
</h5>
</div>
<div ng-show="additive" class="add-btn button-column add-entry">
<div ng-if="additive" class="add-btn button-column add-entry">
<button class="btn btn-default btn-sm pull-right" ng-click="onAdd()">
<i class="fa fa-plus"></i></button>
</div>
<div ng-show="removable" class="add-btn button-column remove-entry">
<div ng-if="removable" class="add-btn button-column remove-entry">
<a href="#" ng-click="onRemove()">
<i class="fa fa-times-circle pull-right"></i></a>
</div>

View File

@ -1,5 +1,5 @@
<div class="form-group">
<label for="elem-{$ $id $}.$index">{$ title $}</label>
<label for="elem-{$ $id $}.$index">{$ value.title() $}</label>
<select id="elem-{$ $id $}.$index" class="form-control"
ng-model="value.value" ng-model-options="{getterSetter: true}">
<option ng-repeat="option in value.getValues()"

View File

@ -1,4 +1,4 @@
<collapsible-group title="{$ title $}" on-add="value.add()">
<collapsible-group content="value" on-add="value.add()">
<div class="three-columns" ng-repeat="(key, subvalue) in value.getValues() track by key">
<div class="left-column">
<div class="form-group">

View File

@ -1,10 +1,10 @@
<collapsible-group title="{$ title $}">
<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"
ng-class="{'right-column': item.isAtomic() && $odd, 'left-column': item.isAtomic() && $even}">
<div class="form-group">
<label for="elem-{$ $id $}.{$ item.getID() $}">{$ item.getTitle() $}</label>
<label for="elem-{$ $id $}.{$ item.getID() $}">{$ item.title() $}</label>
<input type="text" class="form-control" id="elem-{$ $id $}.{$ item.getID() $}" ng-model="item.value"
ng-model-options="{getterSetter: true}">
</div>

View File

@ -1,11 +1,11 @@
<collapsible-group
title="{$ title $}" additive="{$ value.isAdditive() $}" on-add="value.add()"
removable="{$ value.isRemovable() $}" on-remove="value.remove()">
<collapsible-group content="value" additive="{$ value.isAdditive() $}"
on-add="value.add()"
removable="{$ value.isRemovable() $}" on-remove="value.remove()">
<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"
ng-class="{'right-column': item.isAtomic() && $odd, 'left-column': item.isAtomic() && $even}">
<typed-field title="{$ item.getTitle() $}" value="item" type="{$ item.getType() $}"></typed-field>
<typed-field value="item" type="{$ item.getType() $}"></typed-field>
<div class="clearfix" ng-if="$odd"></div>
</div>
</div>

View File

@ -1,4 +1,4 @@
<collapsible-group title="{$ title $}" on-add="value.add()">
<collapsible-group content="value" on-add="value.add()">
<div class="three-columns">
<div class="left-column">
<div class="form-group" ng-repeat="subItem in value.getValues() track by $index">

View File

@ -1,5 +1,6 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ title $}</label>
<pre>{$ value $}, {$ value.title() $}</pre>
<label for="elem-{$ $id $}">{$ value.title() $}</label>
<input type="number" class="form-control" id="elem-{$ $id $}" ng-model="value.value"
ng-model-options="{ getterSetter: true }">
</div>

View File

@ -1,5 +1,5 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ title $}</label>
<label for="elem-{$ $id $}">{$ value.title() $}</label>
<input ng-if="!value.getSuggestions"
type="text" class="form-control" id="elem-{$ $id $}" ng-model="value.value"
ng-model-options="{ getterSetter: true }">

View File

@ -1,5 +1,5 @@
<div class="form-group">
<label for="elem-{$ $id $}">{$ title $}</label>
<label for="elem-{$ $id $}">{$ value.title() $}</label>
<textarea class="form-control" id="elem-{$ $id $}" ng-model="value.value"
ng-model-options="{ getterSetter: true }"></textarea>
</div>

View File

@ -137,17 +137,15 @@ describe('merlin directives', function() {
}
function getGroupRemoveBtn(groupElem) {
var div = groupElem.children().children().eq(0).children().eq(2);
return div.hasClass('remove-entry') && div;
return groupElem.find('.remove-entry');
}
function getGroupAddBtn(groupElem) {
var div = groupElem.children().children().eq(0).children().eq(1);
return div.hasClass('add-entry') && div;
return groupElem.find('.add-entry');
}
function getCollapseBtn(groupElem) {
return groupElem.children().children().eq(0).children().eq(0).find('a');
return groupElem.find('.collapse-entries');
}
function makeGroupElement(contents) {
@ -193,7 +191,7 @@ describe('merlin directives', function() {
element1 = makeGroupElement('');
element2 = makeGroupElement('on-remove="remove()"');
expect(getGroupRemoveBtn(element1).hasClass('ng-hide')).toBe(true);
expect(getGroupRemoveBtn(element1).length).toBe(0);
expect(getGroupRemoveBtn(element2).hasClass('ng-hide')).toBe(false);
});
@ -202,7 +200,7 @@ describe('merlin directives', function() {
$scope.remove = function() {};
element = makeGroupElement('on-remove="remove()" removable="false"');
expect(getGroupRemoveBtn(element).hasClass('ng-hide')).toBe(true);
expect(getGroupRemoveBtn(element).length).toBe(0);
});
it('requires to specify `on-add` to make group additive', function() {
@ -211,7 +209,7 @@ describe('merlin directives', function() {
element1 = makeGroupElement('');
element2 = makeGroupElement('on-add="add()"');
expect(getGroupAddBtn(element1).hasClass('ng-hide')).toBe(true);
expect(getGroupAddBtn(element1).length).toBe(0);
expect(getGroupAddBtn(element2).hasClass('ng-hide')).toBe(false);
});
@ -220,7 +218,7 @@ describe('merlin directives', function() {
$scope.add = function() {};
element = makeGroupElement('on-add="add()" additive="false"');
expect(getGroupAddBtn(element).hasClass('ng-hide')).toBe(true);
expect(getGroupAddBtn(element).length).toBe(0);
});
it('contents are inserted into div.collapse tag', function() {
@ -258,6 +256,32 @@ describe('merlin directives', function() {
$httpBackend.flush();
expect(element.html()).toContain('<textarea');
});
describe('various types', function() {
describe('.title() of every field except group', function() {
it("tries to extract title from '@meta' key", function() {
});
it("when no title found in '@meta', takes value of 'name' subfield given it's ImmutableObj", function() {
});
it("when no title found both in '@meta' and in 'name' subfield, uses capitalized field ID", function() {
});
});
describe('.title() of group field', function() {
it('if the field is not removable, uses the conventional .title()', function() {
});
it('if the field is removable, uses .title() as a wrapper around .getID()/.setID()', function() {
});
})
})
});
@ -323,4 +347,4 @@ describe('merlin directives', function() {
});
});
});
});