workflow.models.js 18 KB

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