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 18KB

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