workflow.models.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. // Licensed to Cloudera, Inc. under one
  2. // or more contributor license agreements. See the NOTICE file
  3. // distributed with this work for additional information
  4. // regarding copyright ownership. Cloudera, Inc. licenses this file
  5. // to you under the Apache License, Version 2.0 (the
  6. // "License"); you may not use this file except in compliance
  7. // with the License. You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. // Since knockout maps arrays without calling "update" nor "create"
  17. // Provide a JSON string that will be parsed in custom 'create' and 'update' functions.
  18. // These serialized values are also stored in the backend.
  19. var MODEL_FIELDS_JSON = ['parameters', 'job_properties', 'files', 'archives', 'prepares', 'params',
  20. 'deletes', 'mkdirs', 'moves', 'chmods', 'touchzs'];
  21. var DEFAULT_SLA = [
  22. {'key': 'enabled', 'value': false},
  23. {'key': 'nominal-time', 'value': ''},
  24. {'key': 'should-start', 'value': ''},
  25. {'key': 'should-end', 'value': ''},
  26. {'key': 'max-duration', 'value': ''},
  27. {'key': 'alert-events', 'value': ''},
  28. {'key': 'alert-contact', 'value': ''},
  29. {'key': 'notification-msg', 'value': ''},
  30. {'key': 'upstream-apps', 'value': ''}
  31. ];
  32. function getDefaultData() {
  33. return {'sla': DEFAULT_SLA.slice(0)};
  34. }
  35. function normalize_model_fields(node_model) {
  36. $.each(MODEL_FIELDS_JSON, function(index, field) {
  37. if (field in node_model && $.isArray(node_model[field])) {
  38. node_model[field] = JSON.stringify(node_model[field]);
  39. }
  40. });
  41. return node_model;
  42. }
  43. // Parse JSON if it is JSON and appropriately map.
  44. // Apply subscriber to each mapping.
  45. var map_params = function(options, subscribe) {
  46. options.data = ($.type(options.data) == "string") ? $.parseJSON(options.data) : options.data;
  47. if ($.isArray(options.data)) {
  48. var mapping = ko.mapping.fromJS(options.data);
  49. $.each(mapping(), function(index, value) {
  50. subscribe(value);
  51. });
  52. return mapping;
  53. } else {
  54. var mapping = ko.mapping.fromJS(options.data, {});
  55. subscribe(mapping);
  56. return mapping;
  57. }
  58. };
  59. // Find all members of the data and apply appropriate mapping.
  60. // Arrays might contain literals or plain objects.
  61. // Plain objects should have their members mapped and literals should
  62. // be replaced with observables.
  63. // Plain object members will notify their containing arrays when they update.
  64. // Literals will notify their containing arrays when they've changed.
  65. var map_data = function(options) {
  66. var data = {};
  67. options.data = ($.type(options.data) == "string") ? $.parseJSON(options.data) : options.data;
  68. $.each(options.data, function(member, value) {
  69. // @TODO: Should we support unstructureed data as children?
  70. if ($.isArray(value)) {
  71. // @TODO: Support more than {'member': 'value',...} and 'value'.
  72. data[member] = ko.observableArray();
  73. $.each(value, function(index, object_or_literal) {
  74. if ($.isPlainObject(object_or_literal)) {
  75. var obj = {};
  76. $.each(object_or_literal, function(key, literal) {
  77. obj[key] = ko.mapping.fromJS(literal);
  78. obj[key].subscribe(function() {
  79. data[member].valueHasMutated();
  80. });
  81. });
  82. data[member].push(obj);
  83. } else {
  84. var literal = ko.mapping.fromJS(object_or_literal);
  85. data[member].push(literal);
  86. literal.subscribe(function() {
  87. data[member].valueHasMutated();
  88. });
  89. }
  90. });
  91. } else {
  92. data[member] = ko.mapping.fromJS(value);
  93. }
  94. });
  95. return data;
  96. };
  97. // Maps JSON strings to fields in the view model.
  98. var MAPPING_OPTIONS = {
  99. ignore: ['initialize', 'toString', 'copy'], // Do not support cancel edit on data
  100. job_properties: {
  101. create: function(options) {
  102. var parent = options.parent;
  103. var subscribe = function(mapping) {
  104. mapping.name.subscribe(function(value) {
  105. parent.job_properties.valueHasMutated();
  106. });
  107. mapping.value.subscribe(function(value) {
  108. parent.job_properties.valueHasMutated();
  109. });
  110. };
  111. return map_params(options, subscribe);
  112. },
  113. update: function(options) {
  114. var parent = options.parent;
  115. var subscribe = function(mapping) {
  116. mapping.name.subscribe(function(value) {
  117. parent.job_properties.valueHasMutated();
  118. });
  119. mapping.value.subscribe(function(value) {
  120. parent.job_properties.valueHasMutated();
  121. });
  122. };
  123. return map_params(options, subscribe);
  124. }
  125. },
  126. files: {
  127. create: function(options) {
  128. return map_params(options, function() {});
  129. },
  130. update: function(options) {
  131. return map_params(options, function() {});
  132. }
  133. },
  134. archives: {
  135. create: function(options) {
  136. var parent = options.parent;
  137. var subscribe = function(mapping) {
  138. mapping.name.subscribe(function(value) {
  139. parent.archives.valueHasMutated();
  140. });
  141. mapping.dummy.subscribe(function(value) {
  142. parent.archives.valueHasMutated();
  143. });
  144. };
  145. return map_params(options, subscribe);
  146. },
  147. update: function(options) {
  148. var parent = options.parent;
  149. var subscribe = function(mapping) {
  150. mapping.name.subscribe(function(value) {
  151. parent.archives.valueHasMutated();
  152. });
  153. mapping.dummy.subscribe(function(value) {
  154. parent.archives.valueHasMutated();
  155. });
  156. };
  157. return map_params(options, subscribe);
  158. }
  159. },
  160. params: {
  161. create: function(options) {
  162. var parent = options.parent;
  163. var subscribe = function(mapping) {
  164. mapping.value.subscribe(function(value) {
  165. parent.params.valueHasMutated();
  166. });
  167. mapping.type.subscribe(function(value) {
  168. parent.params.valueHasMutated();
  169. });
  170. };
  171. return map_params(options, subscribe);
  172. },
  173. update: function(options) {
  174. var parent = options.parent;
  175. var subscribe = function(mapping) {
  176. mapping.value.subscribe(function(value) {
  177. parent.params.valueHasMutated();
  178. });
  179. mapping.type.subscribe(function(value) {
  180. parent.params.valueHasMutated();
  181. });
  182. };
  183. return map_params(options, subscribe);
  184. }
  185. },
  186. prepares: {
  187. create: function(options) {
  188. var parent = options.parent;
  189. var subscribe = function(mapping) {
  190. mapping.value.subscribe(function(value) {
  191. parent.prepares.valueHasMutated();
  192. });
  193. mapping.type.subscribe(function(value) {
  194. parent.prepares.valueHasMutated();
  195. });
  196. };
  197. return map_params(options, subscribe);
  198. },
  199. update: function(options) {
  200. var parent = options.parent;
  201. var subscribe = function(mapping) {
  202. mapping.value.subscribe(function(value) {
  203. parent.prepares.valueHasMutated();
  204. });
  205. mapping.type.subscribe(function(value) {
  206. parent.prepares.valueHasMutated();
  207. });
  208. };
  209. return map_params(options, subscribe);
  210. }
  211. },
  212. deletes: {
  213. create: function(options) {
  214. var parent = options.parent;
  215. var subscribe = function(mapping) {
  216. mapping.name.subscribe(function(value) {
  217. parent.deletes.valueHasMutated();
  218. });
  219. };
  220. return map_params(options, subscribe);
  221. },
  222. update: function(options) {
  223. var parent = options.parent;
  224. var subscribe = function(mapping) {
  225. mapping.name.subscribe(function(value) {
  226. parent.deletes.valueHasMutated();
  227. });
  228. };
  229. return map_params(options, subscribe);
  230. },
  231. },
  232. mkdirs: {
  233. create: function(options) {
  234. var parent = options.parent;
  235. var subscribe = function(mapping) {
  236. mapping.name.subscribe(function(value) {
  237. parent.mkdirs.valueHasMutated();
  238. });
  239. };
  240. return map_params(options, subscribe);
  241. },
  242. update: function(options) {
  243. var parent = options.parent;
  244. var subscribe = function(mapping) {
  245. mapping.name.subscribe(function(value) {
  246. parent.mkdirs.valueHasMutated();
  247. });
  248. };
  249. return map_params(options, subscribe);
  250. },
  251. },
  252. moves: {
  253. create: function(options) {
  254. var parent = options.parent;
  255. var subscribe = function(mapping) {
  256. mapping.source.subscribe(function(value) {
  257. parent.moves.valueHasMutated();
  258. });
  259. mapping.destination.subscribe(function(value) {
  260. parent.moves.valueHasMutated();
  261. });
  262. };
  263. return map_params(options, subscribe);
  264. },
  265. update: function(options) {
  266. var parent = options.parent;
  267. var subscribe = function(mapping) {
  268. mapping.source.subscribe(function(value) {
  269. parent.moves.valueHasMutated();
  270. });
  271. mapping.destination.subscribe(function(value) {
  272. parent.moves.valueHasMutated();
  273. });
  274. };
  275. return map_params(options, subscribe);
  276. }
  277. },
  278. chmods: {
  279. create: function(options) {
  280. var parent = options.parent;
  281. var subscribe = function(mapping) {
  282. mapping.path.subscribe(function(value) {
  283. parent.chmods.valueHasMutated();
  284. });
  285. mapping.permissions.subscribe(function(value) {
  286. parent.chmods.valueHasMutated();
  287. });
  288. mapping.recursive.subscribe(function(value) {
  289. parent.chmods.valueHasMutated();
  290. });
  291. };
  292. return map_params(options, subscribe);
  293. },
  294. update: function(options) {
  295. var parent = options.parent;
  296. var subscribe = function(mapping) {
  297. mapping.path.subscribe(function(value) {
  298. parent.chmods.valueHasMutated();
  299. });
  300. mapping.permissions.subscribe(function(value) {
  301. parent.chmods.valueHasMutated();
  302. });
  303. mapping.recursive.subscribe(function(value) {
  304. parent.chmods.valueHasMutated();
  305. });
  306. };
  307. return map_params(options, subscribe);
  308. },
  309. },
  310. touchzs: {
  311. create: function(options) {
  312. var parent = options.parent;
  313. var subscribe = function(mapping) {
  314. mapping.name.subscribe(function(value) {
  315. parent.touchzs.valueHasMutated();
  316. });
  317. };
  318. return map_params(options, subscribe);
  319. },
  320. update: function(options) {
  321. var parent = options.parent;
  322. var subscribe = function(mapping) {
  323. mapping.name.subscribe(function(value) {
  324. parent.touchzs.valueHasMutated();
  325. });
  326. };
  327. return map_params(options, subscribe);
  328. }
  329. },
  330. data: {
  331. create: function(options) {
  332. return map_data(options);
  333. },
  334. update: function(options) {
  335. return map_data(options);
  336. }
  337. }
  338. };
  339. var ModelModule = function($) {
  340. var module = function(attrs) {
  341. var self = this;
  342. $.extend(self, attrs);
  343. module.prototype.initialize.apply(self, arguments);
  344. return self;
  345. };
  346. $.extend(module.prototype, {
  347. // Normal stuff
  348. initialize: function(){},
  349. toString: function() {
  350. var self = this;
  351. return JSON.stringify(self, null, '\t');
  352. },
  353. copy: function() {
  354. var self = this;
  355. var model = $.extend(true, {}, self);
  356. $.each(MODEL_FIELDS_JSON, function(i, field) {
  357. if (field in model && $.type(model[field]) != "string") {
  358. model[field] = JSON.stringify(model[field]);
  359. }
  360. });
  361. return model;
  362. }
  363. });
  364. return module;
  365. };
  366. function initializeData() {
  367. var self = this;
  368. self.data = ($.type(self.data) == "string") ? $.parseJSON(self.data) : self.data;
  369. if (!('sla' in self.data)) {
  370. self.data['sla'] = DEFAULT_SLA.slice(0);
  371. }
  372. }
  373. var WorkflowModel = ModelModule($);
  374. $.extend(WorkflowModel.prototype, {
  375. id: 0,
  376. name: '',
  377. description: '',
  378. start: 0,
  379. end: 0,
  380. schema_version: 0.4,
  381. deployment_dir: '',
  382. is_shared: true,
  383. parameters: '[]',
  384. job_xml: '',
  385. data: getDefaultData(),
  386. initialize: initializeData
  387. });
  388. var NodeModel = ModelModule($);
  389. $.extend(NodeModel.prototype, {
  390. id: 0,
  391. name: '',
  392. description: '',
  393. node_type: '',
  394. workflow: 0,
  395. child_links: []
  396. });
  397. var ForkModel = ModelModule($);
  398. $.extend(ForkModel.prototype, {
  399. id: 0,
  400. name: '',
  401. description: '',
  402. node_type: 'fork',
  403. workflow: 0,
  404. child_links: []
  405. });
  406. var DecisionModel = ModelModule($);
  407. $.extend(DecisionModel.prototype, {
  408. id: 0,
  409. name: '',
  410. description: '',
  411. node_type: 'decision',
  412. workflow: 0,
  413. child_links: []
  414. });
  415. var DistCPModel = ModelModule($);
  416. $.extend(DistCPModel.prototype, {
  417. id: 0,
  418. name: '',
  419. description: '',
  420. node_type: 'distcp',
  421. workflow: 0,
  422. job_properties: '[]',
  423. prepares: '[]',
  424. job_xml: '',
  425. params: '[]',
  426. child_links: [],
  427. data: getDefaultData(),
  428. initialize: initializeData
  429. });
  430. var MapReduceModel = ModelModule($);
  431. $.extend(MapReduceModel.prototype, {
  432. id: 0,
  433. name: '',
  434. description: '',
  435. node_type: 'mapreduce',
  436. workflow: 0,
  437. files: '[]',
  438. archives: '[]',
  439. job_properties: '[]',
  440. jar_path: '',
  441. prepares: '[]',
  442. job_xml: '',
  443. child_links: [],
  444. data: getDefaultData(),
  445. initialize: initializeData
  446. });
  447. var StreamingModel = ModelModule($);
  448. $.extend(StreamingModel.prototype, {
  449. id: 0,
  450. name: '',
  451. description: '',
  452. node_type: 'streaming',
  453. workflow: 0,
  454. files: '[]',
  455. archives: '[]',
  456. job_properties: '[]',
  457. mapper: '',
  458. reducer: '',
  459. child_links: [],
  460. data: getDefaultData(),
  461. initialize: initializeData
  462. });
  463. var JavaModel = ModelModule($);
  464. $.extend(JavaModel.prototype, {
  465. id: 0,
  466. name: '',
  467. description: '',
  468. node_type: 'java',
  469. workflow: 0,
  470. files: '[]',
  471. archives: '[]',
  472. job_properties: '[]',
  473. jar_path: '',
  474. prepares: '[]',
  475. job_xml: '',
  476. main_class: '',
  477. args: '',
  478. java_opts: '',
  479. capture_output: false,
  480. child_links: [],
  481. data: getDefaultData(),
  482. initialize: initializeData
  483. });
  484. var PigModel = ModelModule($);
  485. $.extend(PigModel.prototype, {
  486. id: 0,
  487. name: '',
  488. description: '',
  489. node_type: 'pig',
  490. workflow: 0,
  491. files: '[]',
  492. archives: '[]',
  493. job_properties: '[]',
  494. prepares: '[]',
  495. job_xml: '',
  496. params: '[]',
  497. script_path: '',
  498. child_links: [],
  499. data: getDefaultData(),
  500. initialize: initializeData
  501. });
  502. var HiveModel = ModelModule($);
  503. $.extend(HiveModel.prototype, {
  504. id: 0,
  505. name: '',
  506. description: '',
  507. node_type: 'hive',
  508. workflow: 0,
  509. files: '[]',
  510. archives: '[]',
  511. job_properties: '[]',
  512. prepares: '[]',
  513. job_xml: '',
  514. params: '[]',
  515. script_path: '',
  516. child_links: [],
  517. data: getDefaultData(),
  518. initialize: initializeData
  519. });
  520. var SqoopModel = ModelModule($);
  521. $.extend(SqoopModel.prototype, {
  522. id: 0,
  523. name: '',
  524. description: '',
  525. node_type: 'sqoop',
  526. workflow: 0,
  527. files: '[]',
  528. archives: '[]',
  529. job_properties: '[]',
  530. prepares: '[]',
  531. job_xml: '',
  532. params: '[]',
  533. script_path: '',
  534. child_links: [],
  535. data: getDefaultData(),
  536. initialize: initializeData
  537. });
  538. var ShellModel = ModelModule($);
  539. $.extend(ShellModel.prototype, {
  540. id: 0,
  541. name: '',
  542. description: '',
  543. node_type: 'shell',
  544. workflow: 0,
  545. files: '[]',
  546. archives: '[]',
  547. job_properties: '[]',
  548. prepares: '[]',
  549. job_xml: '',
  550. params: '[]',
  551. command: '',
  552. capture_output: false,
  553. child_links: [],
  554. data: getDefaultData(),
  555. initialize: initializeData
  556. });
  557. var SshModel = ModelModule($);
  558. $.extend(SshModel.prototype, {
  559. id: 0,
  560. name: '',
  561. description: '',
  562. node_type: 'ssh',
  563. workflow: 0,
  564. user: '',
  565. host: '',
  566. params: '[]',
  567. command: '',
  568. capture_output: false,
  569. child_links: [],
  570. data: getDefaultData(),
  571. initialize: initializeData
  572. });
  573. var FsModel = ModelModule($);
  574. $.extend(FsModel.prototype, {
  575. id: 0,
  576. name: '',
  577. description: '',
  578. node_type: 'fs',
  579. workflow: 0,
  580. deletes: '[]',
  581. mkdirs: '[]',
  582. moves: '[]',
  583. chmods: '[]',
  584. touchzs: '[]',
  585. child_links: [],
  586. data: getDefaultData(),
  587. initialize: initializeData
  588. });
  589. var EmailModel = ModelModule($);
  590. $.extend(EmailModel.prototype, {
  591. id: 0,
  592. name: '',
  593. description: '',
  594. node_type: 'email',
  595. workflow: 0,
  596. to: '',
  597. cc: '',
  598. subject: '',
  599. body: '',
  600. child_links: [],
  601. data: getDefaultData(),
  602. initialize: initializeData
  603. });
  604. var SubWorkflowModel = ModelModule($);
  605. $.extend(SubWorkflowModel.prototype, {
  606. id: 0,
  607. name: '',
  608. description: '',
  609. node_type: 'subworkflow',
  610. workflow: 0,
  611. sub_workflow: 0,
  612. propagate_configuration: true,
  613. job_properties: '[]',
  614. child_links: [],
  615. data: getDefaultData(),
  616. initialize: initializeData
  617. });
  618. var GenericModel = ModelModule($);
  619. $.extend(GenericModel.prototype, {
  620. id: 0,
  621. name: '',
  622. description: '',
  623. node_type: 'generic',
  624. workflow: 0,
  625. xml: '',
  626. child_links: []
  627. });
  628. function nodeModelChooser(node_type) {
  629. switch(node_type) {
  630. case 'mapreduce':
  631. return MapReduceModel;
  632. case 'streaming':
  633. return StreamingModel;
  634. case 'java':
  635. return JavaModel;
  636. case 'pig':
  637. return PigModel;
  638. case 'hive':
  639. return HiveModel;
  640. case 'sqoop':
  641. return SqoopModel;
  642. case 'shell':
  643. return ShellModel;
  644. case 'ssh':
  645. return SshModel;
  646. case 'distcp':
  647. return DistCPModel;
  648. case 'fs':
  649. return FsModel;
  650. case 'email':
  651. return EmailModel;
  652. case 'subworkflow':
  653. return SubWorkflowModel;
  654. case 'generic':
  655. return GenericModel;
  656. case 'fork':
  657. return ForkModel;
  658. case 'decision':
  659. return DecisionModel;
  660. default:
  661. return NodeModel;
  662. }
  663. }