Framework for building UIs for OpenStack projects dealing with complex input data
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mistral.workbook.models.js 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. /**
  2. * Created by tsufiev on 2/24/15.
  3. */
  4. (function() {
  5. 'use strict';
  6. angular.module('mistral')
  7. .factory('mistral.workbook.models',
  8. ['merlin.field.models', 'merlin.panel.models', 'merlin.utils', '$http', '$q',
  9. function(fields, panel, utils, $http, $q) {
  10. var models = {};
  11. function varlistValueFactory(json, parameters) {
  12. var type = Barricade.getType(json);
  13. if ( json === undefined || type === String ) {
  14. return fields.string.create(json, parameters);
  15. } else if ( type === Array ) {
  16. return fields.list.extend({}, {
  17. '*': {'@class': fields.string}
  18. }).create(json, parameters);
  19. } else if ( type === Object ) {
  20. return fields.dictionary.extend({}, {
  21. '?': {'@class': fields.string}
  22. }).create(json, parameters);
  23. }
  24. }
  25. models.varlist = fields.list.extend({
  26. create: function(json, parameters) {
  27. var self = fields.list.create.call(this, json, parameters);
  28. self.setType('varlist');
  29. self.on('childChange', function(child, op) {
  30. if ( op == 'empty' ) {
  31. self.each(function(index, item) {
  32. if ( child === item ) {
  33. self.remove(index);
  34. }
  35. })
  36. }
  37. });
  38. return self;
  39. }
  40. }, {
  41. '*': {
  42. '@class': fields.frozendict.extend({
  43. create: function(json, parameters) {
  44. var self = fields.frozendict.create.call(this, json, parameters);
  45. self.on('childChange', function(child) {
  46. if ( child.instanceof(Barricade.Enumerated) ) { // type change
  47. var value = self.get('value');
  48. switch ( child.get() ) {
  49. case 'string':
  50. self.set('value', varlistValueFactory(''));
  51. break;
  52. case 'list':
  53. self.set('value', varlistValueFactory(['']));
  54. break;
  55. case 'dictionary':
  56. self.set('value', varlistValueFactory({'key1': ''}));
  57. break;
  58. }
  59. } else if ( child.instanceof(Barricade.Arraylike) && !child.length() ) {
  60. self.emit('change', 'empty');
  61. }
  62. });
  63. return self;
  64. },
  65. _getPrettyJSON: function() {
  66. var json = fields.frozendict._getPrettyJSON.apply(this, arguments);
  67. return json.value;
  68. }
  69. }, {
  70. 'type': {
  71. '@class': fields.string.extend({}, {
  72. '@enum': ['string', 'list', 'dictionary'],
  73. '@default': 'string'
  74. })
  75. },
  76. 'value': {
  77. '@class': fields.wildcard,
  78. '@factory': varlistValueFactory
  79. }
  80. })
  81. }
  82. });
  83. models.yaqllist = fields.list.extend({
  84. create: function(json, parameters) {
  85. var self = fields.list.create.call(this, json, parameters);
  86. self.setType('yaqllist');
  87. return self;
  88. }
  89. }, {
  90. '*': {
  91. '@class': fields.frozendict.extend({}, {
  92. 'yaql': {
  93. '@class': fields.string
  94. },
  95. 'action': {
  96. '@class': fields.string
  97. }
  98. })
  99. }
  100. });
  101. models.Action = fields.frozendict.extend({
  102. create: function(json, parameters) {
  103. var self = fields.frozendict.create.call(this, json, parameters),
  104. base = self.get('base');
  105. base.on('change', function(operation) {
  106. var baseValue;
  107. if ( operation != 'id' ) {
  108. baseValue = base.get();
  109. if ( baseValue ) {
  110. base.getSchema(baseValue).then(function(keys) {
  111. self.get('base-input').setSchema(keys);
  112. });
  113. }
  114. }
  115. });
  116. return self;
  117. }
  118. }, {
  119. 'base': {
  120. '@class': fields.string.extend({
  121. create: function(json, parameters) {
  122. var self = fields.string.create.call(this, json, parameters),
  123. schema = {},
  124. url = utils.getMeta(self, 'autocompletionUrl');
  125. self.getSchema = function(key) {
  126. var deferred = $q.defer();
  127. if ( !(key in schema) ) {
  128. $http.get(url+'?key='+key).success(function(keys) {
  129. schema[key] = keys;
  130. deferred.resolve(keys);
  131. }).error(function() {
  132. deferred.reject();
  133. });
  134. } else {
  135. deferred.resolve(schema[key]);
  136. }
  137. return deferred.promise;
  138. };
  139. return self;
  140. }
  141. }, {
  142. '@meta': {
  143. 'index': 1,
  144. 'row': 0,
  145. 'autocompletionUrl': '/project/mistral/actions/types'
  146. }
  147. })
  148. },
  149. 'base-input': {
  150. '@class': fields.directeddictionary.extend({}, {
  151. '@required': false,
  152. '@meta': {
  153. 'index': 2,
  154. 'title': 'Base Input'
  155. },
  156. '?': {
  157. '@class': fields.string.extend({}, {
  158. '@meta': {'row': 1}
  159. })
  160. }
  161. })
  162. },
  163. 'input': {
  164. '@class': fields.list.extend({}, {
  165. '@meta': {
  166. 'index': 3
  167. },
  168. '*': {'@class': fields.string}
  169. })
  170. },
  171. 'output': {
  172. '@class': models.varlist.extend({}, {
  173. '@meta': {
  174. 'index': 4
  175. }
  176. })
  177. }
  178. });
  179. models.Task = fields.frozendict.extend({
  180. create: function(json, parameters) {
  181. var self = fields.frozendict.create.call(this, json, parameters);
  182. self.on('childChange', function(child, op) {
  183. if ( child === self.get('type') && op !== 'id' ) {
  184. self.emit('change', 'taskType');
  185. }
  186. });
  187. return self;
  188. },
  189. remove: function() {
  190. this.emit('change', 'taskRemove', this.getID());
  191. },
  192. _getPrettyJSON: function() {
  193. var json = fields.frozendict._getPrettyJSON.apply(this, arguments);
  194. delete json.type;
  195. return json;
  196. }
  197. }, {
  198. '@meta': {
  199. 'baseKey': 'task',
  200. 'baseName': 'Task ',
  201. 'group': true,
  202. 'additive': false,
  203. 'removable': true
  204. },
  205. 'type': {
  206. '@class': fields.string.extend({}, {
  207. '@enum': [{
  208. value: 'action', label: 'Action-based'
  209. }, {
  210. value: 'workflow', label: 'Workflow-based'
  211. }],
  212. '@default': 'action',
  213. '@meta': {
  214. 'index': 0,
  215. 'row': 0
  216. }
  217. })
  218. },
  219. 'description': {
  220. '@class': fields.text.extend({}, {
  221. '@meta': {
  222. 'index': 2,
  223. 'row': 1
  224. }
  225. })
  226. },
  227. 'input': {
  228. '@class': fields.dictionary.extend({}, {
  229. '@meta': {
  230. 'index': 4
  231. },
  232. '?': {
  233. '@class': fields.string
  234. }
  235. })
  236. },
  237. 'publish': {
  238. '@class': fields.dictionary.extend({}, {
  239. '@meta': {
  240. 'index': 5
  241. },
  242. '?': {
  243. '@class': fields.string
  244. }
  245. })
  246. },
  247. 'policies': {
  248. '@class': fields.frozendict.extend({
  249. _getPrettyJSON: function() {
  250. var json = fields.frozendict._getPrettyJSON.apply(this, arguments);
  251. json.retry = {
  252. count: utils.pop(json, 'retry-count'),
  253. delay: utils.pop(json, 'retry-delay'),
  254. 'break-on': utils.pop(json, 'retry-break-on')
  255. };
  256. return json;
  257. }
  258. }, {
  259. '@meta': {
  260. 'index': 9
  261. },
  262. '@required': false,
  263. 'wait-before': {
  264. '@class': fields.number.extend({}, {
  265. '@required': false,
  266. '@meta': {
  267. 'index': 0,
  268. 'row': 0,
  269. 'title': 'Wait before'
  270. }
  271. })
  272. },
  273. 'wait-after': {
  274. '@class': fields.number.extend({}, {
  275. '@required': false,
  276. '@meta': {
  277. 'index': 1,
  278. 'row': 0,
  279. 'title': 'Wait after'
  280. }
  281. })
  282. },
  283. 'timeout': {
  284. '@class': fields.number.extend({}, {
  285. '@required': false,
  286. '@meta': {
  287. 'index': 2,
  288. 'row': 1
  289. }
  290. })
  291. },
  292. 'retry-count': {
  293. '@class': fields.number.extend({}, {
  294. '@required': false,
  295. '@meta': {
  296. 'index': 3,
  297. 'row': 2,
  298. 'title': 'Retry count'
  299. }
  300. })
  301. },
  302. 'retry-delay': {
  303. '@class': fields.number.extend({}, {
  304. '@required': false,
  305. '@meta': {
  306. 'index': 4,
  307. 'row': 2,
  308. 'title': 'Retry delay'
  309. }
  310. })
  311. },
  312. 'retry-break-on': {
  313. '@class': fields.number.extend({}, {
  314. '@required': false,
  315. '@meta': {
  316. 'index': 5,
  317. 'row': 3,
  318. 'title': 'Retry break on'
  319. }
  320. })
  321. }
  322. })
  323. }
  324. });
  325. models.ReverseWFTask = models.Task.extend({}, {
  326. 'requires': {
  327. '@class': fields.string.extend({}, {
  328. '@meta': {
  329. 'row': 2,
  330. 'index': 3
  331. }
  332. })
  333. }
  334. });
  335. models.DirectWFTask = models.Task.extend({}, {
  336. 'on-error': {
  337. '@class': fields.list.extend({}, {
  338. '@meta': {
  339. 'title': 'On error',
  340. 'index': 6
  341. },
  342. '*': {
  343. '@class': fields.string
  344. }
  345. })
  346. },
  347. 'on-success': {
  348. '@class': fields.list.extend({}, {
  349. '@meta': {
  350. 'title': 'On success',
  351. 'index': 7
  352. },
  353. '*': {
  354. '@class': fields.string
  355. }
  356. })
  357. },
  358. 'on-complete': {
  359. '@class': fields.list.extend({}, {
  360. '@meta': {
  361. 'title': 'On complete',
  362. 'index': 8
  363. },
  364. '*': {
  365. '@class': fields.string
  366. }
  367. })
  368. }
  369. });
  370. models.ActionTaskMixin = Barricade.Blueprint.create(function() {
  371. return this.extend({}, {
  372. 'action': {
  373. '@class': fields.string.extend({
  374. create: function(json, parameters) {
  375. var self = fields.string.create.call(this, json, parameters),
  376. actionsCls = Barricade.create({
  377. '@type': String,
  378. '@ref': {
  379. to: function() {
  380. return models.Actions;
  381. },
  382. needs: function() {
  383. return models.Workbook;
  384. },
  385. getter: function(data) {
  386. return data.needed.get('actions');
  387. }
  388. }
  389. });
  390. self._actions = actionsCls.create().on(
  391. 'replace', function(newValue) {
  392. self._actions = newValue;
  393. self._actions.on('change', function() {
  394. self._choices = self._actions.getIDs();
  395. self.resetValues();
  396. });
  397. self._actions.emit('change');
  398. });
  399. return self;
  400. },
  401. _choices: null
  402. }, {
  403. '@enum': function() {
  404. if (!this._choices) {
  405. this._choices = [];
  406. this.emit('_resolveUp', this._actions);
  407. }
  408. return this._choices;
  409. },
  410. '@meta': {
  411. 'row': 0,
  412. 'index': 1
  413. }
  414. })
  415. }
  416. })
  417. });
  418. models.WorkflowTaskMixin = Barricade.Blueprint.create(function() {
  419. return this.extend({}, {
  420. 'workflow': {
  421. '@class': fields.string.extend({
  422. create: function(json, parameters) {
  423. var self = fields.string.create.call(this, json, parameters),
  424. workflowsCls = Barricade.create({
  425. '@type': String,
  426. '@ref': {
  427. to: function() {
  428. return models.Workflows;
  429. },
  430. needs: function() {
  431. return models.Workbook;
  432. },
  433. getter: function(data) {
  434. return data.needed.get('workflows');
  435. }
  436. }
  437. });
  438. self._workflows = workflowsCls.create().on(
  439. 'replace', function(newValue) {
  440. self._workflows = newValue;
  441. self._workflows.on('change', function() {
  442. self._choices = self._workflows.getIDs();
  443. self.resetValues();
  444. });
  445. self._workflows.emit('change');
  446. });
  447. return self;
  448. },
  449. _choices: null
  450. }, {
  451. '@enum': function() {
  452. if ( !this._choices ) {
  453. this._choices = [];
  454. this.emit('_resolveUp', this._workflows);
  455. }
  456. return this._choices;
  457. },
  458. '@meta': {
  459. 'row': 0,
  460. 'index': 1
  461. }
  462. })
  463. }
  464. });
  465. });
  466. var taskTypes = {
  467. 'direct': models.DirectWFTask,
  468. 'reverse': models.ReverseWFTask,
  469. 'action': models.ActionTaskMixin,
  470. 'workflow': models.WorkflowTaskMixin
  471. };
  472. function TaskFactory(json, parameters) {
  473. var type = json.type || 'action',
  474. baseClass = taskTypes[parameters.wfType],
  475. mixinClass = taskTypes[type],
  476. taskClass = mixinClass.call(baseClass);
  477. return taskClass.create(json, parameters);
  478. }
  479. models.Workflow = fields.frozendict.extend({
  480. create: function(json, parameters) {
  481. var self = fields.frozendict.create.call(this, json, parameters);
  482. self.on('childChange', function(child, op) {
  483. if ( child === self.get('type') && op !== 'id' ) {
  484. self.emit('change', 'workflowType');
  485. }
  486. });
  487. return self;
  488. }
  489. }, {
  490. 'type': {
  491. '@class': fields.string.extend({}, {
  492. '@enum': ['reverse', 'direct'],
  493. '@default': 'direct',
  494. '@meta': {
  495. 'index': 1,
  496. 'row': 0
  497. }
  498. })
  499. },
  500. 'input': {
  501. '@class': fields.list.extend({}, {
  502. '@required': false,
  503. '@meta': {
  504. 'index': 2
  505. },
  506. '*': {
  507. '@class': fields.string
  508. }
  509. })
  510. },
  511. 'output': {
  512. '@class': fields.list.extend({}, {
  513. '@required': false,
  514. '@meta': {
  515. 'index': 3
  516. },
  517. '*': {
  518. '@class': fields.string
  519. }
  520. })
  521. },
  522. 'tasks': {
  523. '@class': fields.dictionary.extend({
  524. create: function(json, parameters) {
  525. var self = fields.dictionary.create.call(this, json, parameters);
  526. self.on('childChange', function(child, op, arg) {
  527. if ( op === 'taskType' ) {
  528. var taskId = child.getID(),
  529. params = child._parameters,
  530. taskPos = self.getPosByID(taskId),
  531. taskData = child.toJSON();
  532. params.id = taskId;
  533. self.set(taskPos, TaskFactory(taskData, params));
  534. } else if ( op === 'taskRemove' ) {
  535. self.remove(arg);
  536. }
  537. });
  538. return self;
  539. }
  540. }, {
  541. '@meta': {
  542. 'index': 5,
  543. 'group': true
  544. },
  545. '?': {
  546. '@class': models.Task,
  547. '@factory': TaskFactory
  548. }
  549. })
  550. }
  551. });
  552. models.ReverseWorkflow = models.Workflow.extend({});
  553. models.DirectWorkflow = models.Workflow.extend({}, {
  554. 'task-defaults': {
  555. '@class': fields.frozendict.extend({}, {
  556. '@required': false,
  557. '@meta': {
  558. 'index': 4,
  559. 'group': true,
  560. 'additive': false
  561. },
  562. 'on-error': {
  563. '@class': models.yaqllist.extend({}, {
  564. '@meta': {
  565. 'title': 'On error',
  566. 'index': 0
  567. }
  568. })
  569. },
  570. 'on-success': {
  571. '@class': models.yaqllist.extend({}, {
  572. '@meta': {
  573. 'title': 'On success',
  574. 'index': 1
  575. }
  576. })
  577. },
  578. 'on-complete': {
  579. '@class': models.yaqllist.extend({}, {
  580. '@meta': {
  581. 'title': 'On complete',
  582. 'index': 2
  583. }
  584. })
  585. }
  586. })
  587. }
  588. });
  589. var workflowTypes = {
  590. 'direct': models.DirectWorkflow,
  591. 'reverse': models.ReverseWorkflow
  592. };
  593. function workflowFactory(json, parameters) {
  594. var type = json.type || 'direct';
  595. parameters.wfType = type;
  596. return workflowTypes[type].create(json, parameters);
  597. }
  598. models.Actions = fields.dictionary.extend({}, {
  599. '@required': false,
  600. '@meta': {
  601. 'index': 3,
  602. 'panelIndex': 1
  603. },
  604. '?': {
  605. '@class': models.Action
  606. }
  607. });
  608. models.Workflows = fields.dictionary.extend({
  609. create: function(json, parameters) {
  610. var self = fields.dictionary.create.call(this, json, parameters);
  611. self.on('childChange', function(child, op) {
  612. if ( op === 'workflowType' ) {
  613. var workflowId = child.getID(),
  614. workflowPos = self.getPosByID(workflowId),
  615. params = child._parameters,
  616. workflowData = child.toJSON();
  617. params.wfType = child.type;
  618. params.id = workflowId;
  619. self.set(workflowPos, workflowFactory(workflowData, params));
  620. }
  621. });
  622. return self;
  623. }
  624. }, {
  625. '@meta': {
  626. 'index': 4,
  627. 'panelIndex': 2
  628. },
  629. '?': {
  630. '@class': models.Workflow,
  631. '@factory': workflowFactory
  632. }
  633. });
  634. models.Workbook = fields.frozendict.extend({
  635. toYAML: function() {
  636. return jsyaml.dump(this.toJSON({pretty: true}));
  637. }
  638. }, {
  639. 'version': {
  640. '@class': fields.string.extend({}, {
  641. '@enum': ['2.0'],
  642. '@meta': {
  643. 'index': 2,
  644. 'panelIndex': 0,
  645. 'row': 1
  646. },
  647. '@default': '2.0'
  648. })
  649. },
  650. 'name': {
  651. '@class': fields.string.extend({}, {
  652. '@meta': {
  653. 'index': 0,
  654. 'panelIndex': 0,
  655. 'row': 0
  656. },
  657. '@constraints': [
  658. function(value) {
  659. return value !== 'workbook1' ? true : 'The sample validation failure.';
  660. }
  661. ]
  662. })
  663. },
  664. 'description': {
  665. '@class': fields.text.extend({}, {
  666. '@meta': {
  667. 'index': 1,
  668. 'panelIndex': 0,
  669. 'row': 0
  670. },
  671. '@required': false
  672. })
  673. },
  674. 'actions': {
  675. '@class': models.Actions
  676. },
  677. 'workflows': {
  678. '@class': models.Workflows
  679. }
  680. });
  681. return models;
  682. }])
  683. })();