workflow.models.js 18 KB

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