workflow.node.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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. /**
  17. * Node
  18. * Displays node in a graph and handles graph manipulation.
  19. * The majority of nodes require similar logic.
  20. * This modules takes advantage of that fact.
  21. */
  22. var NodeModule = function($, IdGeneratorTable, NodeFields) {
  23. var META_LINKS = ['related', 'default', 'error'];
  24. var linkTypeChooser = function(parent, child) {
  25. if (child.node_type() == 'kill') {
  26. return 'error';
  27. }
  28. switch(parent.node_type()) {
  29. case 'start':
  30. return (child.node_type() == 'end') ? 'related' : 'to';
  31. case 'fork':
  32. return (child.node_type() == 'join') ? 'related' : 'start';
  33. case 'decision':
  34. return (child.node_type() == 'decisionend') ? 'related' : 'start';
  35. case 'join':
  36. case 'decisionend':
  37. return 'to';
  38. default:
  39. return 'ok';
  40. };
  41. };
  42. var module = function(workflow, model, registry) {
  43. var self = this;
  44. self.map(model);
  45. self.links = ko.computed(function() {
  46. var links = self.child_links().filter(function(element, index, arr) {
  47. return $.inArray(element.name(), META_LINKS) == -1;
  48. });
  49. return links;
  50. });
  51. self.meta_links = ko.computed(function() {
  52. var links = self.child_links().filter(function(element, index, arr) {
  53. return $.inArray(element.name(), META_LINKS) != -1;
  54. });
  55. return links;
  56. });
  57. self._workflow = workflow;
  58. self.registry = registry;
  59. self.children = ko.observableArray([]);
  60. self.model = model;
  61. var errors = {};
  62. for(var key in model) {
  63. switch(key) {
  64. case 'child_links':
  65. case 'node_ptr':
  66. case 'initialize':
  67. case 'toString':
  68. break;
  69. default:
  70. errors[key] = [];
  71. break;
  72. }
  73. }
  74. self.errors = ko.mapping.fromJS(errors);
  75. self.edit_template = model.node_type + 'EditTemplate';
  76. switch(model.node_type) {
  77. case 'start':
  78. case 'end':
  79. self.view_template = ko.observable('disabledNodeTemplate');
  80. break;
  81. case 'kill':
  82. self.view_template = ko.observable('emptyTemplate');
  83. break;
  84. case 'fork':
  85. self.view_template = ko.observable('forkTemplate');
  86. break;
  87. case 'join':
  88. self.view_template = ko.observable('joinTemplate');
  89. break;
  90. case 'decision':
  91. self.view_template = ko.observable('decisionTemplate');
  92. break;
  93. case 'decisionend':
  94. self.view_template = ko.observable('decisionEndTemplate');
  95. break;
  96. default:
  97. self.view_template = ko.observable('nodeTemplate');
  98. break;
  99. }
  100. // Data manipulation
  101. if ('files' in model) {
  102. //// WARNING: The following order should be preserved!
  103. // Need to represent files as some thing else for knockout mappings.
  104. // The KO idiom "value" requires a named parameter.
  105. self._files = self.files;
  106. self.files = ko.observableArray([]);
  107. // ['file', ...] => [{'name': 'file', 'dummy': ''}, ...].
  108. $.each(self._files(), function(index, filename) {
  109. var prop = { name: ko.observable(filename), dummy: ko.observable("") };
  110. prop.name.subscribe(function(value) {
  111. self.files.valueHasMutated();
  112. });
  113. prop.dummy.subscribe(function(value) {
  114. self.files.valueHasMutated();
  115. });
  116. self.files.push(prop);
  117. });
  118. // [{'name': 'file', 'dummy': ''}, ...] => ['file', ...].
  119. self.files.subscribe(function(value) {
  120. self._files.removeAll();
  121. $.each(self.files(), function(index, file) {
  122. self._files.push(file.name);
  123. });
  124. });
  125. self.addFile = function() {
  126. var prop = { name: ko.observable(""), dummy: ko.observable("") };
  127. prop.name.subscribe(function(value) {
  128. self.files.valueHasMutated();
  129. });
  130. prop.dummy.subscribe(function(value) {
  131. self.files.valueHasMutated();
  132. });
  133. self.files.push(prop);
  134. };
  135. self.removeFile = function(val) {
  136. self.files.remove(val);
  137. };
  138. }
  139. self.initialize.apply(self, arguments);
  140. return self;
  141. };
  142. $.extend(true, module.prototype, NodeFields, {
  143. // Data.
  144. children: null,
  145. model: null,
  146. // Normal stuff
  147. /**
  148. * Called when creating a new node
  149. */
  150. initialize: function(workflow, model, registry) {},
  151. toString: function() {
  152. return '';
  153. },
  154. /**
  155. * Fetches registry
  156. */
  157. getRegistry: function() {
  158. return registry;
  159. },
  160. /**
  161. * Maps a model to self
  162. * Called when creating a new node before any thing else
  163. */
  164. map: function(model) {
  165. var self = this;
  166. // @see http://knockoutjs.com/documentation/plugins-mapping.html
  167. // MAPPING_OPTIONS comes from /oozie/static/js/models.js
  168. var mapping = ko.mapping.fromJS(model, MAPPING_OPTIONS);
  169. $.extend(self, mapping);
  170. $.each(mapping, function(key, value) {
  171. var key = key;
  172. if (ko.isObservable(self[key])) {
  173. self[key].subscribe(function(value) {
  174. model[key] = ko.mapping.toJS(value);
  175. });
  176. }
  177. });
  178. $.each(self.child_links(), function(index, link) {
  179. var $index = index;
  180. link.comment.subscribe(function(value) {
  181. self.model.child_links[$index].comment = value;
  182. });
  183. link.child.subscribe(function(value) {
  184. self.model.child_links[$index].child = value;
  185. });
  186. });
  187. },
  188. validate: function( ) {
  189. var self = this;
  190. var options = {};
  191. data = $.extend(true, {}, self.model);
  192. var success = false;
  193. var request = $.extend({
  194. url: '/oozie/workflows/' + self._workflow.id() + '/nodes/' + self.node_type() + '/validate',
  195. type: 'POST',
  196. data: { node: JSON.stringify(data) },
  197. success: function(data) {
  198. ko.mapping.fromJS(data.data, self.errors);
  199. success = data.status == 0;
  200. },
  201. async: false
  202. }, options);
  203. $.ajax(request);
  204. return success;
  205. },
  206. // Hierarchy manipulation.
  207. /**
  208. * Append node to self
  209. * Does not support multiple children.
  210. * Ensures single child.
  211. * Ensures no cycles.
  212. * 1. Finds all children and attaches them to node (cleans node first).
  213. * 2. Remove all children from self.
  214. * 3. Attach node to self.
  215. */
  216. append: function(node) {
  217. var self = this;
  218. // Not fork nor decision nor self
  219. if ($.inArray(self.node_type(), ['fork', 'decision']) == -1 && node.id() != self.id() && !self.isChild(node)) {
  220. node.removeAllChildren();
  221. $.each(self.links(), function(index, link) {
  222. node.addChild(self.registry.get(link.child()));
  223. });
  224. self.removeAllChildren();
  225. self.addChild(node);
  226. }
  227. },
  228. /**
  229. * Find all parents of current node
  230. */
  231. findParents: function() {
  232. var self = this;
  233. var parents = [];
  234. $.each(self.registry.nodes, function(id, node) {
  235. $.each(node.links(), function(index, link) {
  236. if (link.child() == self.id()) {
  237. parents.push(node);
  238. }
  239. });
  240. });
  241. return parents;
  242. },
  243. /**
  244. * Find all children of current node
  245. */
  246. findChildren: function() {
  247. var self = this;
  248. var children = [];
  249. $.each(self.links(), function(index, link) {
  250. children.push(self.registry.get(link.child()));
  251. });
  252. return children;
  253. },
  254. /**
  255. * Detach current node from the graph
  256. * 1. Takes children of self node, removes them from self node, and adds them to each parent of self node.
  257. * 2. The self node is then removed from every parent.
  258. * 3. Does not support multiple children since we do not automatically fork.
  259. */
  260. detach: function() {
  261. var self = this;
  262. $.each(self.findParents(), function(index, parent) {
  263. $.each(self.links(), function(index, link) {
  264. var node = self.registry.get(link.child());
  265. parent.replaceChild(self, node);
  266. });
  267. });
  268. $(self).trigger('detached');
  269. self.removeAllChildren();
  270. },
  271. /**
  272. * Add child
  273. * Update child links for this node.
  274. */
  275. addChild: function(node) {
  276. var self = this;
  277. var link = {
  278. parent: ko.observable(self.id()),
  279. child: ko.observable(node.id()),
  280. name: ko.observable(linkTypeChooser(self, node)),
  281. comment: ko.observable('')
  282. };
  283. self.child_links.unshift(link);
  284. },
  285. /**
  286. * Remove child node
  287. * 1. Find child node link
  288. * 2. Remove child node link
  289. */
  290. removeChild: function(node) {
  291. var self = this;
  292. var spliceIndex = -1;
  293. $.each(self.child_links(), function(index, link) {
  294. if (link.child() == node.id()) {
  295. spliceIndex = index;
  296. }
  297. });
  298. if (spliceIndex > -1) {
  299. self.child_links.splice(spliceIndex, 1);
  300. }
  301. return spliceIndex != -1;
  302. },
  303. /**
  304. * Remove all children
  305. * Removes all children except for related, default, and error links
  306. * Note: we hold on to related, default, and error links because
  307. * we have to.
  308. */
  309. removeAllChildren: function() {
  310. var self = this;
  311. var keep_links = [];
  312. $.each(self.child_links(), function(index, link) {
  313. if ($.inArray(link.name(), META_LINKS) > -1) {
  314. keep_links.push(link);
  315. }
  316. });
  317. self.child_links.removeAll();
  318. $.each(keep_links, function(index, link) {
  319. self.child_links.push(link);
  320. });
  321. },
  322. /**
  323. * Replace child node with another node in the following way:
  324. * 1. Find child index
  325. * 2. Remove child index
  326. * 3. Remove and remember every element after child
  327. * 4. Add replacement node
  328. * 5. Add every child that was remembered
  329. */
  330. replaceChild: function(child, replacement) {
  331. var self = this;
  332. var index = -1;
  333. $.each(self.child_links(), function(i, link) {
  334. if (link.child() == child.id()) {
  335. index = i;
  336. }
  337. });
  338. if (index > -1) {
  339. self.child_links.splice(index, 1);
  340. var links = self.child_links.splice(index);
  341. var link = {
  342. parent: ko.observable(self.id()),
  343. child: ko.observable(replacement.id()),
  344. name: ko.observable(linkTypeChooser(self, replacement)),
  345. comment: ko.observable('')
  346. };
  347. self.child_links.push(link);
  348. $.each(links, function(index, link) {
  349. self.child_links.push(link);
  350. });
  351. }
  352. return index != -1;
  353. },
  354. isChild: function(node) {
  355. var self = this;
  356. var res = false;
  357. $.each(self.links(), function(index, link) {
  358. if (link.child() == node.id()) {
  359. res = true;
  360. }
  361. });
  362. return res;
  363. },
  364. erase: function() {
  365. var self = this;
  366. self.registry.remove(self.id());
  367. }
  368. });
  369. return module;
  370. };