Move Mistral schemas to a separate file
Enable work with 2 types of Task: Action-based and Workflow-based ones. Move to refactored Barricade.js lib (reference resolving was greatly simplified, pleasure to work with it now!). Change-Id: I15173a5f3bbfc22b6d19b8fece150bb70fc39a4e
This commit is contained in:
parent
caf3d9e995
commit
af984c18d3
@ -5,6 +5,7 @@
|
|||||||
<title>Merlin Project</title>
|
<title>Merlin Project</title>
|
||||||
<script src="js/lib/jquery-1.11.1.js"></script>
|
<script src="js/lib/jquery-1.11.1.js"></script>
|
||||||
<script src="js/lib/barricade.js"></script>
|
<script src="js/lib/barricade.js"></script>
|
||||||
|
<script src="js/schema.js"></script>
|
||||||
<script src="js/merlin.js"></script>
|
<script src="js/merlin.js"></script>
|
||||||
<script src="js/lib/js-yaml.js"></script>
|
<script src="js/lib/js-yaml.js"></script>
|
||||||
<link rel="stylesheet" href="css/merlin.css">
|
<link rel="stylesheet" href="css/merlin.css">
|
||||||
|
@ -26,13 +26,109 @@ Barricade = (function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
f.apply(this, arguments);
|
return f.apply(this, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var Extendable = Blueprint.create(function () {
|
||||||
|
function forInKeys(obj) {
|
||||||
|
var key,
|
||||||
|
keys = [];
|
||||||
|
|
||||||
|
for (key in obj) {
|
||||||
|
keys.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainObject(obj) {
|
||||||
|
return getType(obj) === Object &&
|
||||||
|
Object.getPrototypeOf(Object.getPrototypeOf(obj)) === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extend(extension) {
|
||||||
|
function addProperty(object, prop) {
|
||||||
|
return Object.defineProperty(object, prop, {
|
||||||
|
enumerable: true,
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
value: extension[prop]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// add properties to extended object
|
||||||
|
return Object.keys(extension).reduce(addProperty,
|
||||||
|
Object.create(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepClone(object) {
|
||||||
|
if (isPlainObject(object)) {
|
||||||
|
return forInKeys(object).reduce(function (clone, key) {
|
||||||
|
clone[key] = deepClone(object[key]);
|
||||||
|
return clone;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge(target, source) {
|
||||||
|
forInKeys(source).forEach(function (key) {
|
||||||
|
if (target.hasOwnProperty(key) &&
|
||||||
|
isPlainObject(target[key]) &&
|
||||||
|
isPlainObject(source[key])) {
|
||||||
|
merge(target[key], source[key]);
|
||||||
|
} else {
|
||||||
|
target[key] = deepClone(source[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.defineProperty(this, 'extend', {
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
value: function (extension, schema) {
|
||||||
|
if (schema) {
|
||||||
|
extension._schema = '_schema' in this ?
|
||||||
|
deepClone(this._schema) : {};
|
||||||
|
merge(extension._schema, schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
return extend.call(this, extension);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var InstanceofMixin = Blueprint.create(function () {
|
||||||
|
return Object.defineProperty(this, 'instanceof', {
|
||||||
|
enumerable: false,
|
||||||
|
value: function (proto) {
|
||||||
|
var _instanceof = this.instanceof,
|
||||||
|
subject = this;
|
||||||
|
|
||||||
|
function hasMixin(obj, mixin) {
|
||||||
|
return obj.hasOwnProperty('_parents') &&
|
||||||
|
obj._parents.some(function (_parent) {
|
||||||
|
return _instanceof.call(_parent, mixin);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (subject === proto ||
|
||||||
|
hasMixin(subject, proto)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
subject = Object.getPrototypeOf(subject);
|
||||||
|
} while (subject);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
var Identifiable = Blueprint.create(function (id) {
|
var Identifiable = Blueprint.create(function (id) {
|
||||||
this.getID = function () {
|
this.getID = function () {
|
||||||
return id;
|
return id;
|
||||||
@ -45,12 +141,6 @@ Barricade = (function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var Omittable = Blueprint.create(function (isUsed) {
|
var Omittable = Blueprint.create(function (isUsed) {
|
||||||
var defaultValue = this._schema['@default'];
|
|
||||||
|
|
||||||
if ( defaultValue ) {
|
|
||||||
this._setData(defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isUsed = function () {
|
this.isUsed = function () {
|
||||||
// If required, it has to be used.
|
// If required, it has to be used.
|
||||||
return this.isRequired() || isUsed;
|
return this.isRequired() || isUsed;
|
||||||
@ -78,20 +168,31 @@ Barricade = (function () {
|
|||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasDependency() {
|
if (schema.hasOwnProperty('@ref')) {
|
||||||
return schema.hasOwnProperty('@ref');
|
deferred = Deferred.create(schema['@ref'].needs, resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasDependency = hasDependency;
|
this.resolveWith = function (obj) {
|
||||||
|
var allResolved = true;
|
||||||
|
|
||||||
if (hasDependency()) {
|
if (deferred && !deferred.isResolved()) {
|
||||||
this.getDeferred = function () {
|
if (deferred.needs(obj)) {
|
||||||
return deferred;
|
this.emit('replace', deferred.resolve(obj));
|
||||||
|
} else {
|
||||||
|
allResolved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.instanceof(Container)) {
|
||||||
|
this.each(function (index, value) {
|
||||||
|
if (!value.resolveWith(obj)) {
|
||||||
|
allResolved = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return allResolved;
|
||||||
};
|
};
|
||||||
|
|
||||||
deferred = Deferred.create(schema['@ref'].needs,
|
|
||||||
resolver);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var Validatable = Blueprint.create(function (schema) {
|
var Validatable = Blueprint.create(function (schema) {
|
||||||
@ -196,143 +297,35 @@ Barricade = (function () {
|
|||||||
|
|
||||||
var Deferred = {
|
var Deferred = {
|
||||||
create: function (classGetter, onResolve) {
|
create: function (classGetter, onResolve) {
|
||||||
var self = Object.create(this),
|
var self = Object.create(this);
|
||||||
callbacks = [],
|
self._isResolved = false;
|
||||||
isResolved = false;
|
self._classGetter = classGetter;
|
||||||
|
self._onResolve = onResolve;
|
||||||
self.getClass = function () {
|
return self;
|
||||||
return classGetter();
|
},
|
||||||
};
|
resolve: function (obj) {
|
||||||
|
|
||||||
self.resolve = function (obj) {
|
|
||||||
var ref;
|
var ref;
|
||||||
|
|
||||||
if (isResolved) {
|
if (this._isResolved) {
|
||||||
throw new Error('Deferred already resolved');
|
throw new Error('Deferred already resolved');
|
||||||
}
|
}
|
||||||
|
|
||||||
ref = onResolve(obj);
|
ref = this._onResolve(obj);
|
||||||
|
|
||||||
if (ref === undefined) {
|
|
||||||
logError('Could not resolve reference');
|
|
||||||
} else {
|
|
||||||
isResolved = true;
|
|
||||||
callbacks.forEach(function (callback) {
|
|
||||||
callback(ref);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (ref !== undefined) {
|
||||||
|
this._isResolved = true;
|
||||||
return ref;
|
return ref;
|
||||||
};
|
}
|
||||||
|
},
|
||||||
self.isResolved = function () {
|
isResolved: function () {
|
||||||
return isResolved;
|
return this._isResolved;
|
||||||
};
|
},
|
||||||
|
needs: function (obj) {
|
||||||
self.addCallback = function (callback) {
|
return obj.instanceof(this._classGetter());
|
||||||
callbacks.push(callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var Base = (function () {
|
var Base = Extendable.call(InstanceofMixin.call({
|
||||||
var base = {};
|
|
||||||
|
|
||||||
function forInKeys(obj) {
|
|
||||||
var key,
|
|
||||||
keys = [];
|
|
||||||
|
|
||||||
for (key in obj) {
|
|
||||||
keys.push(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPlainObject(obj) {
|
|
||||||
return getType(obj) === Object &&
|
|
||||||
Object.getPrototypeOf(Object.getPrototypeOf(obj)) === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function extend(extension) {
|
|
||||||
function addProperty(object, prop) {
|
|
||||||
return Object.defineProperty(object, prop, {
|
|
||||||
enumerable: true,
|
|
||||||
writable: true,
|
|
||||||
configurable: true,
|
|
||||||
value: extension[prop]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add properties to extended object
|
|
||||||
return Object.keys(extension).reduce(addProperty,
|
|
||||||
Object.create(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
function deepClone(object) {
|
|
||||||
if (isPlainObject(object)) {
|
|
||||||
return forInKeys(object).reduce(function (clone, key) {
|
|
||||||
clone[key] = deepClone(object[key]);
|
|
||||||
return clone;
|
|
||||||
}, {});
|
|
||||||
}
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge(target, source) {
|
|
||||||
forInKeys(source).forEach(function (key) {
|
|
||||||
if (target.hasOwnProperty(key) &&
|
|
||||||
isPlainObject(target[key]) &&
|
|
||||||
isPlainObject(source[key])) {
|
|
||||||
merge(target[key], source[key]);
|
|
||||||
} else {
|
|
||||||
target[key] = deepClone(source[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(base, 'extend', {
|
|
||||||
enumerable: false,
|
|
||||||
writable: false,
|
|
||||||
value: function (extension, schema) {
|
|
||||||
if (schema) {
|
|
||||||
extension._schema = '_schema' in this ?
|
|
||||||
deepClone(this._schema) : {};
|
|
||||||
merge(extension._schema, schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
return extend.call(this, extension);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(base, 'instanceof', {
|
|
||||||
enumerable: false,
|
|
||||||
value: function (proto) {
|
|
||||||
var _instanceof = this.instanceof,
|
|
||||||
subject = this;
|
|
||||||
|
|
||||||
function hasMixin(obj, mixin) {
|
|
||||||
return obj.hasOwnProperty('_parents') &&
|
|
||||||
obj._parents.some(function (_parent) {
|
|
||||||
return _instanceof.call(_parent, mixin);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (subject === proto ||
|
|
||||||
hasMixin(subject, proto)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
subject = Object.getPrototypeOf(subject);
|
|
||||||
} while (subject);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return base.extend({
|
|
||||||
create: function (json, parameters) {
|
create: function (json, parameters) {
|
||||||
var self = this.extend({}),
|
var self = this.extend({}),
|
||||||
schema = self._schema,
|
schema = self._schema,
|
||||||
@ -378,12 +371,19 @@ Barricade = (function () {
|
|||||||
isUsed = false;
|
isUsed = false;
|
||||||
}
|
}
|
||||||
// Replace bad type (does not change original)
|
// Replace bad type (does not change original)
|
||||||
json = type();
|
json = this._getDefaultValue();
|
||||||
}
|
}
|
||||||
this._data = this._sift(json, this._parameters);
|
this._data = this._sift(json, this._parameters);
|
||||||
|
|
||||||
return isUsed;
|
return isUsed;
|
||||||
},
|
},
|
||||||
|
_getDefaultValue: function () {
|
||||||
|
return this._schema.hasOwnProperty('@default')
|
||||||
|
? typeof this._schema['@default'] === 'function'
|
||||||
|
? this._schema['@default']()
|
||||||
|
: this._schema['@default']
|
||||||
|
: this._schema['@type']();
|
||||||
|
},
|
||||||
_sift: function () {
|
_sift: function () {
|
||||||
throw new Error("sift() must be overridden in subclass");
|
throw new Error("sift() must be overridden in subclass");
|
||||||
},
|
},
|
||||||
@ -401,8 +401,7 @@ Barricade = (function () {
|
|||||||
isEmpty: function () {
|
isEmpty: function () {
|
||||||
throw new Error('Subclass should override isEmpty()');
|
throw new Error('Subclass should override isEmpty()');
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}());
|
|
||||||
|
|
||||||
var Container = Base.extend({
|
var Container = Base.extend({
|
||||||
create: function (json, parameters) {
|
create: function (json, parameters) {
|
||||||
@ -413,173 +412,54 @@ Barricade = (function () {
|
|||||||
self._attachListeners(key);
|
self._attachListeners(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOnResolve(key) {
|
self.on('_addedElement', function (key) {
|
||||||
return function (resolvedValue) {
|
attachListeners(key);
|
||||||
self.set(key, resolvedValue);
|
self._tryResolveOn(self.get(key));
|
||||||
|
});
|
||||||
|
|
||||||
if (resolvedValue.hasDependency()) {
|
|
||||||
allDeferred.push(resolvedValue.getDeferred());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('getAllDeferred' in resolvedValue) {
|
|
||||||
allDeferred = allDeferred.concat(
|
|
||||||
resolvedValue.getAllDeferred());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function attachDeferredCallback(key, value) {
|
|
||||||
if (value.hasDependency()) {
|
|
||||||
value.getDeferred().addCallback(getOnResolve(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deferredClassMatches(deferred) {
|
|
||||||
return self.instanceof(deferred.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
function addDeferredToList(obj) {
|
|
||||||
if (obj.hasDependency()) {
|
|
||||||
allDeferred.push(obj.getDeferred());
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('getAllDeferred' in obj) {
|
|
||||||
allDeferred = allDeferred.concat(
|
|
||||||
obj.getAllDeferred());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveDeferreds() {
|
|
||||||
var curDeferred,
|
|
||||||
unresolvedDeferreds = [];
|
|
||||||
|
|
||||||
// New deferreds can be added to allDeferred as others are
|
|
||||||
// resolved. Iterating this way is safe regardless of how
|
|
||||||
// new elements are added.
|
|
||||||
while (allDeferred.length > 0) {
|
|
||||||
curDeferred = allDeferred.shift();
|
|
||||||
|
|
||||||
if (!curDeferred.isResolved()) {
|
|
||||||
if (deferredClassMatches(curDeferred)) {
|
|
||||||
curDeferred.addCallback(addDeferredToList);
|
|
||||||
curDeferred.resolve(self);
|
|
||||||
} else {
|
|
||||||
unresolvedDeferreds.push(curDeferred);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allDeferred = unresolvedDeferreds;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.on('_addedElement', attachListeners);
|
|
||||||
self.each(attachListeners);
|
self.each(attachListeners);
|
||||||
|
|
||||||
self.on('_addedElement', function(index) {
|
self.each(function (index, value) {
|
||||||
var element = self.get(index);
|
value.resolveWith(self);
|
||||||
|
|
||||||
attachDeferredCallback(index, element);
|
|
||||||
addDeferredToList(element);
|
|
||||||
|
|
||||||
resolveDeferreds.call(self);
|
|
||||||
if ( (element.instanceof(Barricade.Container) &&
|
|
||||||
element.getAllDeferred().length ) ||
|
|
||||||
(element.instanceof(Barricade.Primitive) &&
|
|
||||||
element.hasDependency() &&
|
|
||||||
!element.getDeferred().isResolved()) ) {
|
|
||||||
element.emit('_resolveUp');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function mergeBubblingDeferreds(deferreds) {
|
|
||||||
var currentIds = {},
|
|
||||||
bubblingDeferreds = {},
|
|
||||||
id;
|
|
||||||
if ( deferreds && deferreds.length ) {
|
|
||||||
allDeferred.forEach(function(deferred) {
|
|
||||||
if (!(deferred.id in currentIds)) {
|
|
||||||
currentIds[deferred.id] = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
deferreds.forEach(function(deferred) {
|
|
||||||
if (!(deferred.id in bubblingDeferreds)) {
|
|
||||||
bubblingDeferreds[deferred.id] = deferred;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for ( id in bubblingDeferreds ) {
|
|
||||||
if (!(id in currentIds)) {
|
|
||||||
allDeferred.push(bubblingDeferreds[id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.on('_resolveUp', function(deferreds) {
|
|
||||||
mergeBubblingDeferreds(deferreds);
|
|
||||||
resolveDeferreds.call(self);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.each(function (key, value) {
|
|
||||||
attachDeferredCallback(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (self.hasDependency()) {
|
|
||||||
allDeferred.push(self.getDeferred());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.each(function (key, value) {
|
|
||||||
addDeferredToList(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
resolveDeferreds.call(self);
|
|
||||||
|
|
||||||
self.getAllDeferred = function () {
|
|
||||||
return allDeferred;
|
|
||||||
};
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
},
|
},
|
||||||
|
_tryResolveOn: function (value) {
|
||||||
|
if (!value.resolveWith(this)) {
|
||||||
|
this.emit('_resolveUp', value);
|
||||||
|
}
|
||||||
|
},
|
||||||
_attachListeners: function (key) {
|
_attachListeners: function (key) {
|
||||||
var self = this,
|
var self = this,
|
||||||
element = this.get(key);
|
element = this.get(key),
|
||||||
|
events = {
|
||||||
function onChildChange(child) {
|
'childChange': function (child) {
|
||||||
self.emit('childChange', child);
|
self.emit('childChange', child);
|
||||||
}
|
|
||||||
|
|
||||||
function onDirectChildChange() {
|
|
||||||
onChildChange(this); // 'this' is set to callee, not typo
|
|
||||||
}
|
|
||||||
|
|
||||||
function onReplace(newValue) {
|
|
||||||
self.set(key, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
element.on('_resolveUp', function() {
|
|
||||||
if ( element.instanceof(Barricade.Container) ) {
|
|
||||||
self.emit('_resolveUp', element.getAllDeferred());
|
|
||||||
} else {
|
|
||||||
self.emit('_resolveUp', [element.getDeferred()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
element.on('childChange', onChildChange);
|
|
||||||
element.on('change', onDirectChildChange);
|
|
||||||
element.on('replace', onReplace);
|
|
||||||
|
|
||||||
element.on('removeFrom', function (container) {
|
|
||||||
if (container === self) {
|
|
||||||
element.off('childChange', onChildChange);
|
|
||||||
element.off('change', onDirectChildChange);
|
|
||||||
element.off('replace', onReplace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
set: function (key, value) {
|
'change': function () {
|
||||||
this.get(key).emit('removeFrom', this);
|
// 'this' is set to callee, no typo
|
||||||
this._doSet(key, value);
|
events.childChange(this);
|
||||||
this._attachListeners(key);
|
},
|
||||||
|
'replace': function (newValue) {
|
||||||
|
self.set(key, newValue);
|
||||||
|
self._tryResolveOn(newValue);
|
||||||
|
},
|
||||||
|
'_resolveUp': function (value) {
|
||||||
|
self._tryResolveOn(value);
|
||||||
|
},
|
||||||
|
'removeFrom': function (container) {
|
||||||
|
if (container === self) {
|
||||||
|
Object.keys(events).forEach(function (eName) {
|
||||||
|
element.on(eName, events[eName]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(events).forEach(function (eName) {
|
||||||
|
element.on(eName, events[eName]);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
_getKeyClass: function (key) {
|
_getKeyClass: function (key) {
|
||||||
return this._schema[key].hasOwnProperty('@class')
|
return this._schema[key].hasOwnProperty('@class')
|
||||||
@ -607,6 +487,11 @@ Barricade = (function () {
|
|||||||
|
|
||||||
return this._safeInstanceof(instance, class_) ||
|
return this._safeInstanceof(instance, class_) ||
|
||||||
(class_._schema.hasOwnProperty('@ref') && isRefTo());
|
(class_._schema.hasOwnProperty('@ref') && isRefTo());
|
||||||
|
},
|
||||||
|
set: function (key, value) {
|
||||||
|
this.get(key).emit('removeFrom', this);
|
||||||
|
this._doSet(key, value);
|
||||||
|
this._attachListeners(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
244
js/merlin.js
244
js/merlin.js
@ -13,208 +13,6 @@
|
|||||||
under the License.
|
under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var types = {
|
|
||||||
Mistral: {
|
|
||||||
actions: {}
|
|
||||||
},
|
|
||||||
HOT: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
types.Mistral.Version = Barricade.create({
|
|
||||||
'@type': Number,
|
|
||||||
'@default': 2
|
|
||||||
});
|
|
||||||
|
|
||||||
types.Mistral.Action = Barricade.create({
|
|
||||||
'@type': Object,
|
|
||||||
|
|
||||||
'version': {
|
|
||||||
'@type': Number,
|
|
||||||
'@ref': {
|
|
||||||
to: types.Mistral.Version,
|
|
||||||
needs: function () {
|
|
||||||
return types.Mistral.Workbook;
|
|
||||||
},
|
|
||||||
resolver: function(json, parentObj) {
|
|
||||||
return parentObj.get('version')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'name': {'@type': String},
|
|
||||||
'base': {
|
|
||||||
'@type': String,
|
|
||||||
'@enum': function() {
|
|
||||||
// TODO: obtain list of predefined actions from Mistral server-side
|
|
||||||
var predefinedActions = ['createInstance', 'terminateInstance'],
|
|
||||||
actions = workbook.get('actions'),
|
|
||||||
currentItemIndex = actions.length() - 1;
|
|
||||||
actions.each(function(index, actionItem) {
|
|
||||||
var name = actionItem.get('name');
|
|
||||||
if ( index < currentItemIndex && !name.isEmpty() ) {
|
|
||||||
predefinedActions = predefinedActions.concat(name.get())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return predefinedActions;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'base-parameters': {
|
|
||||||
'@type': Object,
|
|
||||||
'@required': false,
|
|
||||||
'?': {'@type': String}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
types.Mistral.Policy = Barricade.create({
|
|
||||||
'@type': Object,
|
|
||||||
|
|
||||||
'wait-before': {
|
|
||||||
'@type': Number,
|
|
||||||
'@required': false
|
|
||||||
},
|
|
||||||
'wait-after': {
|
|
||||||
'@type': Number,
|
|
||||||
'@required': false
|
|
||||||
},
|
|
||||||
'retry': {
|
|
||||||
'@type': Object,
|
|
||||||
'@required': false,
|
|
||||||
'count': {'@type': Number},
|
|
||||||
'delay': {'@type': Number},
|
|
||||||
'break-on': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
types.Mistral.Task = Barricade.create({
|
|
||||||
'@type': Object,
|
|
||||||
|
|
||||||
'version': {
|
|
||||||
'@type': Number,
|
|
||||||
'@ref': {
|
|
||||||
to: types.Mistral.Version,
|
|
||||||
needs: function () {
|
|
||||||
return types.Mistral.Workbook;
|
|
||||||
},
|
|
||||||
resolver: function(json, parentObj) {
|
|
||||||
return parentObj.get('version')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'name': {'@type': String},
|
|
||||||
'parameters': {
|
|
||||||
'@type': Object,
|
|
||||||
'@required': false,
|
|
||||||
'?': {'@type': String}
|
|
||||||
},
|
|
||||||
'publish': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
},
|
|
||||||
'policies': {
|
|
||||||
'@class': types.Mistral.Policy,
|
|
||||||
'@required': false
|
|
||||||
},
|
|
||||||
'requires': { // array of Task-s existing in the same workflow
|
|
||||||
'@type': Array,
|
|
||||||
'@required': false,
|
|
||||||
'*': {'@type': String}
|
|
||||||
},
|
|
||||||
'on-complete': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
},
|
|
||||||
'on-success': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
},
|
|
||||||
'on-error': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
types.Mistral.WorkflowTask = types.Mistral.Task.extend({},
|
|
||||||
{
|
|
||||||
'workflow': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
types.Mistral.ActionTask = types.Mistral.Task.extend({},
|
|
||||||
{
|
|
||||||
'action': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
types.Mistral.Workflow = Barricade.create({
|
|
||||||
'@type': Object,
|
|
||||||
|
|
||||||
'version': {
|
|
||||||
'@type': Number,
|
|
||||||
'@ref': {
|
|
||||||
to: types.Mistral.Version,
|
|
||||||
needs: function () {
|
|
||||||
return types.Mistral.Workbook;
|
|
||||||
},
|
|
||||||
resolver: function(json, parentObj) {
|
|
||||||
return parentObj.get('version')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'name': {'@type': String},
|
|
||||||
'type': {
|
|
||||||
'@type': String,
|
|
||||||
'@enum': ['reverse', 'direct'],
|
|
||||||
'@default': 'direct'
|
|
||||||
},
|
|
||||||
'parameters': {
|
|
||||||
'@type': Object,
|
|
||||||
'@required': false,
|
|
||||||
'?': {'@type': String}
|
|
||||||
},
|
|
||||||
'output': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
},
|
|
||||||
'tasks': {
|
|
||||||
'@type': Object,
|
|
||||||
'?': {'@class': types.Mistral.Task}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
types.Mistral.Workbook = Barricade.create({
|
|
||||||
'@type': Object,
|
|
||||||
|
|
||||||
'version': {
|
|
||||||
'@class': types.Mistral.Version
|
|
||||||
},
|
|
||||||
'description': {
|
|
||||||
'@type': String,
|
|
||||||
'@required': false
|
|
||||||
},
|
|
||||||
'actions': {
|
|
||||||
'@type': Array,
|
|
||||||
'@required': false,
|
|
||||||
'*': {
|
|
||||||
'@class': types.Mistral.Action
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'workflows': {
|
|
||||||
'@type': Array,
|
|
||||||
'*': {
|
|
||||||
'@class': types.Mistral.Workflow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var workbook,
|
var workbook,
|
||||||
counter = 0;
|
counter = 0;
|
||||||
$(function() {
|
$(function() {
|
||||||
@ -234,23 +32,25 @@ $(function() {
|
|||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSelectNode(label, item) {
|
function drawSelectElement(labels, values, selected) {
|
||||||
var $item = $('<div></div>'),
|
var $input = $('<select>');
|
||||||
$label = $('<label></label>').text(label),
|
|
||||||
$input = $('<select>'),
|
|
||||||
labels = item.getEnumLabels(),
|
|
||||||
values = item.getEnumValues(),
|
|
||||||
$set = $('<button>').text('Set');
|
|
||||||
$set.click(function() {
|
|
||||||
item.set($input.val());
|
|
||||||
});
|
|
||||||
values.forEach(function(value, index) {
|
values.forEach(function(value, index) {
|
||||||
var $opt = $('<option></option>').val(value).text(labels[index]);
|
var $opt = $('<option></option>').val(value).text(labels[index]);
|
||||||
$input.append($opt);
|
$input.append($opt);
|
||||||
});
|
});
|
||||||
$input.val(item.get());
|
$input.val(selected);
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSelectNode(label, item) {
|
||||||
|
var $item = $('<div></div>'),
|
||||||
|
$label = $('<label></label>').text(label),
|
||||||
|
$set = $('<button>').text('Set');
|
||||||
|
$set.click(function() {
|
||||||
|
item.set($input.val());
|
||||||
|
});
|
||||||
$item.append($label);
|
$item.append($label);
|
||||||
$item.append($input);
|
$item.append(drawSelectElement(item.getEnumLabels(), item.getEnumValues(), item.get()));
|
||||||
$item.append($set);
|
$item.append($set);
|
||||||
return $item;
|
return $item;
|
||||||
|
|
||||||
@ -322,10 +122,16 @@ $(function() {
|
|||||||
$label = $('<label></label>').attr('id', labelId).text(label).toggleClass('expandable'),
|
$label = $('<label></label>').attr('id', labelId).text(label).toggleClass('expandable'),
|
||||||
$keyName = $('<input>'),
|
$keyName = $('<input>'),
|
||||||
$addAction = $('<button>').text('Add').attr('disabled', true),
|
$addAction = $('<button>').text('Add').attr('disabled', true),
|
||||||
$container = $('<div></div>').attr('id', containerId).hide();
|
$container = $('<div></div>').attr('id', containerId).hide(),
|
||||||
|
$typeSelector;
|
||||||
counter++;
|
counter++;
|
||||||
$item.append($label);
|
$item.append($label);
|
||||||
$item.append($keyName);
|
$item.append($keyName);
|
||||||
|
if ( item.instanceof(types.base.AcceptsMixin) ) {
|
||||||
|
var labels = item.getLabels();
|
||||||
|
$typeSelector = drawSelectElement(labels, labels, labels[0]);
|
||||||
|
$item.append($typeSelector);
|
||||||
|
}
|
||||||
$item.append($addAction);
|
$item.append($addAction);
|
||||||
drawContainer($container, item);
|
drawContainer($container, item);
|
||||||
$item.append($container);
|
$item.append($container);
|
||||||
@ -337,8 +143,12 @@ $(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$addAction.click(function() {
|
$addAction.click(function() {
|
||||||
var key = $keyName.val();
|
var key = $keyName.val(), child, cls;
|
||||||
item.push(undefined, {id: key});
|
if ( item.instanceof(types.base.AcceptsMixin) ) {
|
||||||
|
cls = item.getValue($typeSelector.val());
|
||||||
|
child = cls.create(undefined, {id: key});
|
||||||
|
}
|
||||||
|
item.push(child, {id: key});
|
||||||
drawTypedNode($container, key, item.getByID(key));
|
drawTypedNode($container, key, item.getByID(key));
|
||||||
});
|
});
|
||||||
$label.click(function() {
|
$label.click(function() {
|
||||||
|
254
js/schema.js
Normal file
254
js/schema.js
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
|
||||||
|
/* Copyright (c) 2014 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var types = {
|
||||||
|
Mistral: {
|
||||||
|
actions: {}
|
||||||
|
},
|
||||||
|
base: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
types.base.AcceptsMixin = Barricade.Blueprint.create(function (acceptsList) {
|
||||||
|
acceptsList = acceptsList || [];
|
||||||
|
|
||||||
|
this.getLabels = function() {
|
||||||
|
return acceptsList.map(function(item) {
|
||||||
|
return item.label;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getValue = function(label) {
|
||||||
|
for ( var i = 0; i < acceptsList.length; i++ ) {
|
||||||
|
if ( acceptsList[i].label === label ) {
|
||||||
|
return acceptsList[i].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.Version = Barricade.create({
|
||||||
|
'@type': Number,
|
||||||
|
'@default': 2
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.Action = Barricade.create({
|
||||||
|
'@type': Object,
|
||||||
|
|
||||||
|
'version': {
|
||||||
|
'@type': Number,
|
||||||
|
'@ref': {
|
||||||
|
to: types.Mistral.Version,
|
||||||
|
needs: function () {
|
||||||
|
return types.Mistral.Workbook;
|
||||||
|
},
|
||||||
|
resolver: function(json, parentObj) {
|
||||||
|
return parentObj.get('version')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'name': {'@type': String},
|
||||||
|
'base': {
|
||||||
|
'@type': String,
|
||||||
|
'@enum': function() {
|
||||||
|
// TODO: obtain list of predefined actions from Mistral server-side
|
||||||
|
var predefinedActions = ['createInstance', 'terminateInstance'],
|
||||||
|
actions = workbook.get('actions'),
|
||||||
|
currentItemIndex = actions.length() - 1;
|
||||||
|
actions.each(function(index, actionItem) {
|
||||||
|
var name = actionItem.get('name');
|
||||||
|
if ( index < currentItemIndex && !name.isEmpty() ) {
|
||||||
|
predefinedActions = predefinedActions.concat(name.get())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return predefinedActions;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'base-parameters': {
|
||||||
|
'@type': Object,
|
||||||
|
'@required': false,
|
||||||
|
'?': {'@type': String}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.Policy = Barricade.create({
|
||||||
|
'@type': Object,
|
||||||
|
|
||||||
|
'wait-before': {
|
||||||
|
'@type': Number,
|
||||||
|
'@required': false
|
||||||
|
},
|
||||||
|
'wait-after': {
|
||||||
|
'@type': Number,
|
||||||
|
'@required': false
|
||||||
|
},
|
||||||
|
'retry': {
|
||||||
|
'@type': Object,
|
||||||
|
'@required': false,
|
||||||
|
'count': {'@type': Number},
|
||||||
|
'delay': {'@type': Number},
|
||||||
|
'break-on': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.Task = Barricade.create({
|
||||||
|
'@type': Object,
|
||||||
|
|
||||||
|
'version': {
|
||||||
|
'@type': Number,
|
||||||
|
'@ref': {
|
||||||
|
to: types.Mistral.Version,
|
||||||
|
needs: function () {
|
||||||
|
return types.Mistral.Workbook;
|
||||||
|
},
|
||||||
|
resolver: function(json, parentObj) {
|
||||||
|
return parentObj.get('version')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'name': {'@type': String},
|
||||||
|
'parameters': {
|
||||||
|
'@type': Object,
|
||||||
|
'@required': false,
|
||||||
|
'?': {'@type': String}
|
||||||
|
},
|
||||||
|
'publish': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
},
|
||||||
|
'policies': {
|
||||||
|
'@class': types.Mistral.Policy,
|
||||||
|
'@required': false
|
||||||
|
},
|
||||||
|
'requires': { // array of Task-s existing in the same workflow
|
||||||
|
'@type': Array,
|
||||||
|
'@required': false,
|
||||||
|
'*': {'@type': String}
|
||||||
|
},
|
||||||
|
'on-complete': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
},
|
||||||
|
'on-success': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
},
|
||||||
|
'on-error': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.Tasks = Barricade.MutableObject.extend({
|
||||||
|
create: function(json, parameters) {
|
||||||
|
var self = Barricade.MutableObject.create.call(this);
|
||||||
|
|
||||||
|
types.base.AcceptsMixin.call(self, [
|
||||||
|
{
|
||||||
|
label: 'Action-based',
|
||||||
|
value: types.Mistral.ActionTask
|
||||||
|
}, {
|
||||||
|
label: 'Workflow-based',
|
||||||
|
value: types.Mistral.WorkflowTask
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'@type': Object,
|
||||||
|
'?': {'@class': types.Mistral.Task}
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.WorkflowTask = types.Mistral.Task.extend({},
|
||||||
|
{
|
||||||
|
'workflow': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.ActionTask = types.Mistral.Task.extend({},
|
||||||
|
{
|
||||||
|
'action': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.Workflow = Barricade.create({
|
||||||
|
'@type': Object,
|
||||||
|
|
||||||
|
'version': {
|
||||||
|
'@type': Number,
|
||||||
|
'@ref': {
|
||||||
|
to: types.Mistral.Version,
|
||||||
|
needs: function () {
|
||||||
|
return types.Mistral.Workbook;
|
||||||
|
},
|
||||||
|
resolver: function(json, parentObj) {
|
||||||
|
return parentObj.get('version')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'name': {'@type': String},
|
||||||
|
'type': {
|
||||||
|
'@type': String,
|
||||||
|
'@enum': ['reverse', 'direct'],
|
||||||
|
'@default': 'direct'
|
||||||
|
},
|
||||||
|
'parameters': {
|
||||||
|
'@type': Object,
|
||||||
|
'@required': false,
|
||||||
|
'?': {'@type': String}
|
||||||
|
},
|
||||||
|
'output': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
},
|
||||||
|
'tasks': {
|
||||||
|
'@class': types.Mistral.Tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
types.Mistral.Workbook = Barricade.create({
|
||||||
|
'@type': Object,
|
||||||
|
|
||||||
|
'version': {
|
||||||
|
'@class': types.Mistral.Version
|
||||||
|
},
|
||||||
|
'description': {
|
||||||
|
'@type': String,
|
||||||
|
'@required': false
|
||||||
|
},
|
||||||
|
'actions': {
|
||||||
|
'@type': Array,
|
||||||
|
'@required': false,
|
||||||
|
'*': {
|
||||||
|
'@class': types.Mistral.Action
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'workflows': {
|
||||||
|
'@type': Array,
|
||||||
|
'*': {
|
||||||
|
'@class': types.Mistral.Workflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user