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

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