| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- // Licensed to Cloudera, Inc. under one
- // or more contributor license agreements. See the NOTICE file
- // distributed with this work for additional information
- // regarding copyright ownership. Cloudera, Inc. licenses this file
- // to you under the Apache License, Version 2.0 (the
- // "License"); you may not use this file except in compliance
- // with the License. You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- function format_errors_mapping(model) {
- var errors = {};
- for(var key in model) {
- switch(key) {
- case 'child_links':
- case 'node_ptr':
- case 'initialize':
- case 'toString':
- break;
- default:
- errors[key] = [];
- break;
- }
- }
- return errors;
- }
- /**
- * Node
- * Displays node in a graph and handles graph manipulation.
- * The majority of nodes require similar logic.
- * This modules takes advantage of that fact.
- */
- var NodeModule = function($, IdGeneratorTable, NodeFields) {
- var META_LINKS = ['related', 'default', 'error'];
- var linkTypeChooser = function(parent, child) {
- if (child.node_type() == 'kill') {
- return 'error';
- }
- switch(parent.node_type()) {
- case 'start':
- return (child.node_type() == 'end') ? 'related' : 'to';
- case 'fork':
- return (child.node_type() == 'join') ? 'related' : 'start';
- case 'decision':
- return (child.node_type() == 'decisionend') ? 'related' : 'start';
- case 'join':
- case 'decisionend':
- return 'to';
- default:
- return 'ok';
- };
- };
- var module = function(workflow, model, registry) {
- var self = this;
- self.map(model);
- self.links = ko.computed(function() {
- var links = self.child_links().filter(function(element, index, arr) {
- return $.inArray(element.name(), META_LINKS) == -1;
- });
- return links;
- });
- self.meta_links = ko.computed(function() {
- var links = self.child_links().filter(function(element, index, arr) {
- return $.inArray(element.name(), META_LINKS) != -1;
- });
- return links;
- });
- self.non_error_links = ko.computed(function() {
- var links = self.child_links().filter(function(element, index, arr) {
- return element.name() != 'error';
- });
- return links;
- });
- self._workflow = workflow;
- self.registry = registry;
- self.children = ko.observableArray([]);
- self.model = model;
- self.errors = ko.mapping.fromJS(format_errors_mapping(model));
- self.edit_template = model.node_type + 'EditTemplate';
- switch(model.node_type) {
- case 'start':
- case 'end':
- self.view_template = ko.observable('disabledNodeTemplate');
- break;
- case 'kill':
- self.view_template = ko.observable('emptyTemplate');
- break;
- case 'fork':
- self.view_template = ko.observable('forkTemplate');
- break;
- case 'join':
- self.view_template = ko.observable('joinTemplate');
- break;
- case 'decision':
- self.view_template = ko.observable('decisionTemplate');
- break;
- case 'decisionend':
- self.view_template = ko.observable('decisionEndTemplate');
- break;
- default:
- self.view_template = ko.observable('nodeTemplate');
- break;
- }
- // Data manipulation
- if (self.data && self.data.sla) {
- self.sla = ko.computed(function() {
- return self.data.sla();
- });
- }
- if ('files' in model) {
- //// WARNING: The following order should be preserved!
- // Need to represent files as some thing else for knockout mappings.
- // The KO idiom "value" requires a named parameter.
- self._files = self.files;
- self.files = ko.observableArray([]);
- // ['file', ...] => [{'name': 'file', 'dummy': ''}, ...].
- $.each(self._files(), function(index, filename) {
- var prop = { name: ko.observable(filename), dummy: ko.observable("") };
- prop.name.subscribe(function(value) {
- self.files.valueHasMutated();
- });
- prop.dummy.subscribe(function(value) {
- self.files.valueHasMutated();
- });
- self.files.push(prop);
- });
- // [{'name': 'file', 'dummy': ''}, ...] => ['file', ...].
- self.files.subscribe(function(value) {
- self._files.removeAll();
- $.each(self.files(), function(index, file) {
- self._files.push(file.name);
- });
- });
- self.addFile = function() {
- var prop = { name: ko.observable(""), dummy: ko.observable("") };
- prop.name.subscribe(function(value) {
- self.files.valueHasMutated();
- });
- prop.dummy.subscribe(function(value) {
- self.files.valueHasMutated();
- });
- self.files.push(prop);
- };
- self.removeFile = function(val) {
- self.files.remove(val);
- };
- }
- self.initialize.apply(self, arguments);
- return self;
- };
- $.extend(true, module.prototype, NodeFields, {
- children: null,
- model: null,
- // Normal stuff
- /**
- * Called when creating a new node
- */
- initialize: function(workflow, model, registry) {},
- toString: function() {
- return '';
- },
- /**
- * Fetches registry
- */
- getRegistry: function() {
- return registry;
- },
- /**
- * Maps a model to self
- * Called when creating a new node before any thing else
- */
- map: function(model) {
- var self = this;
- // @see http://knockoutjs.com/documentation/plugins-mapping.html
- // MAPPING_OPTIONS comes from /oozie/static/js/workflow.models.js
- var mapping = ko.mapping.fromJS(model, MAPPING_OPTIONS);
- $.extend(self, mapping);
- $.each(mapping, function(key, value) {
- var key = key;
- if (ko.isObservable(self[key])) {
- self[key].subscribe(function(value) {
- model[key] = ko.mapping.toJS(value);
- });
- }
- });
- $.each(self.child_links(), function(index, link) {
- var $index = index;
- link.comment.subscribe(function(value) {
- self.model.child_links[$index].comment = value;
- });
- link.child.subscribe(function(value) {
- self.model.child_links[$index].child = value;
- });
- });
- },
- validate: function( ) {
- var self = this;
- var options = {};
- data = $.extend(true, {}, self.model);
- var success = false;
- var request = $.extend({
- url: '/oozie/workflows/' + self._workflow.id() + '/nodes/' + self.node_type() + '/validate',
- type: 'POST',
- data: { node: JSON.stringify(data) },
- success: function(data) {
- ko.mapping.fromJS(data.data, self.errors);
- success = data.status == 0;
- },
- async: false
- }, options);
- $.ajax(request);
- return success;
- },
- // Hierarchy manipulation.
- /**
- * Append node to self
- * Does not support multiple children.
- * Ensures single child.
- * Ensures no cycles.
- * 1. Finds all children and attaches them to node (cleans node first).
- * 2. Remove all children from self.
- * 3. Attach node to self.
- */
- append: function(node) {
- var self = this;
- // Not fork nor decision nor self
- if ($.inArray(self.node_type(), ['fork', 'decision']) == -1 && node.id() != self.id() && !self.isChild(node)) {
- node.removeAllChildren();
- $.each(self.links(), function(index, link) {
- node.addChild(self.registry.get(link.child()));
- });
- self.removeAllChildren();
- self.addChild(node);
- }
- },
- /**
- * Find all parents of current node
- */
- findParents: function() {
- var self = this;
- var parents = [];
- $.each(self.registry.nodes, function(id, node) {
- $.each(node.links(), function(index, link) {
- if (link.child() == self.id()) {
- parents.push(node);
- }
- });
- });
- return parents;
- },
- findErrorParents: function() {
- var self = this;
- var parents = [];
- $.each(self.registry.nodes, function(id, node) {
- $.each(node.meta_links(), function(index, link) {
- if (link.child() == self.id()) {
- parents.push(node);
- }
- });
- });
- return parents;
- },
- /**
- * Find all children of current node
- */
- findChildren: function() {
- var self = this;
- var children = [];
- $.each(self.links(), function(index, link) {
- children.push(self.registry.get(link.child()));
- });
- return children;
- },
- /**
- * Detach current node from the graph
- * 1. Takes children of self node, removes them from self node, and adds them to each parent of self node.
- * 2. The self node is then removed from every parent.
- * 3. Does not support multiple children since we do not automatically fork.
- */
- detach: function() {
- var self = this;
- $.each(self.findParents(), function(index, parent) {
- $.each(self.links(), function(index, link) {
- var node = self.registry.get(link.child());
- parent.replaceChild(self, node);
- });
- });
- // Error links of parents reset to kill node.
- $.each(self.findErrorParents(), function(index, parent) {
- parent.putErrorChild(self._workflow.kill);
- });
- $(self).trigger('detached');
- self.removeAllChildren();
- },
- /**
- * Add child
- * Update child links for this node.
- */
- addChild: function(node, link_type) {
- var self = this;
- var link_type = link_type || linkTypeChooser(self, node);
- var link = {
- parent: ko.observable(self.id()),
- child: ko.observable(node.id()),
- name: ko.observable(link_type),
- comment: ko.observable('')
- };
- self.child_links.unshift(link);
- },
- /**
- * Remove child node
- * 1. Find child node link
- * 2. Remove child node link
- */
- removeChild: function(node) {
- var self = this;
- var spliceIndex = -1;
- $.each(self.child_links(), function(index, link) {
- if (link.child() == node.id()) {
- spliceIndex = index;
- }
- });
- if (spliceIndex > -1) {
- self.child_links.splice(spliceIndex, 1);
- }
- return spliceIndex != -1;
- },
- /**
- * Remove error child
- * 1. Find child node link
- * 2. Remove child node link
- */
- removeErrorChildren: function() {
- var self = this;
- var spliceIndexes = [];
- $.each(self.child_links(), function(index, link) {
- if (link.name() == 'error') {
- spliceIndexes.push(index);
- }
- });
- var spliceCount = 0;
- if (spliceIndexes.length > 0) {
- $.each(spliceIndexes, function(index, spliceIndex) {
- self.child_links.splice(spliceIndex - spliceCount++, 1);
- });
- }
- return spliceIndexes.length > 0;
- },
- /**
- * Remove all children
- * Removes all children except for related, default, and error links
- * Note: we hold on to related, default, and error links because
- * we have to.
- */
- removeAllChildren: function() {
- var self = this;
- var keep_links = [];
- $.each(self.child_links(), function(index, link) {
- if ($.inArray(link.name(), META_LINKS) > -1) {
- keep_links.push(link);
- }
- });
- self.child_links.removeAll();
- $.each(keep_links, function(index, link) {
- self.child_links.push(link);
- });
- },
- /**
- * Replace child node with another node in the following way:
- * 1. Find child index
- * 2. Remove child index
- * 3. Remove and remember every element after child
- * 4. Add replacement node
- * 5. Add every child that was remembered
- */
- replaceChild: function(child, replacement) {
- var self = this;
- var index = -1;
- $.each(self.non_error_links(), function(i, link) {
- if (link.child() == child.id()) {
- index = i;
- }
- });
- if (index > -1) {
- self.child_links.splice(index, 1);
- var links = self.child_links.splice(index);
- var link = {
- parent: ko.observable(self.id()),
- child: ko.observable(replacement.id()),
- name: ko.observable(linkTypeChooser(self, replacement)),
- comment: ko.observable('')
- };
- self.child_links.push(link);
- $.each(links, function(index, link) {
- self.child_links.push(link);
- });
- }
- return index != -1;
- },
- /**
- * Replace or add error node with another node in the following way:
- * 1. Find child index
- * 2. Remove child index
- * 3. Remove and remember every element after child
- * 4. Add replacement node
- * 5. Add every child that was remembered
- */
- putErrorChild: function(node) {
- var self = this;
- var index = -1;
- $.each(self.child_links(), function(i, link) {
- if (link.name() == 'error') {
- index = i;
- }
- });
- var link = {
- parent: ko.observable(self.id()),
- child: ko.observable(node.id()),
- name: ko.observable('error'),
- comment: ko.observable('')
- };
- if (index > -1) {
- var child_links = self.child_links();
- child_links.splice(index, 1);
- var links = child_links.splice(index);
- child_links.push(link);
- $.each(links, function(index, link) {
- child_links.push(link);
- });
- self.child_links(child_links);
- } else {
- self.child_links.push(link);
- }
- return index != -1;
- },
- /**
- * Get the error child
- */
- getErrorChild: function() {
- var self = this;
- var children = [];
- $.each(self.meta_links(), function(index, link) {
- if (link.name() == 'error') {
- children.push(self.registry.get(link.child()));
- }
- });
- return (children.length > 0) ? children[0] : null;
- },
- isChild: function(node) {
- var self = this;
- var res = false;
- $.each(self.links(), function(index, link) {
- if (link.child() == node.id()) {
- res = true;
- }
- });
- return res;
- },
- erase: function() {
- var self = this;
- self.registry.remove(self.id());
- }
- });
- return module;
- };
|