瀏覽代碼

HUE-937 [useradmin] New JS tests for dynamic workflow editor

- Use WorkflowModel
- Test verbs: append, detach
abec 13 年之前
父節點
當前提交
bd36c45

+ 2 - 2
apps/oozie/src/oozie/templates/editor/edit_workflow.mako

@@ -565,7 +565,7 @@ $.extend(Workflow.prototype, {
  *  - 2 Layers of models.
  */
 // Fetch all nodes from server.
-var workflow_model = {
+var workflow_model = new WorkflowModel({
   id: ${ workflow.id },
   name: "${ workflow.name }",
   description: "${ workflow.description }",
@@ -576,7 +576,7 @@ var workflow_model = {
   is_shared: "${ workflow.is_shared }" == "True",
   parameters: ${ workflow.parameters },
   job_properties: ${ workflow.job_properties }
-};
+});
 var registry = new Registry();
 var workflow = new Workflow({
   model: workflow_model,

+ 15 - 0
apps/oozie/src/oozie/templates/editor/jasmine.mako

@@ -0,0 +1,15 @@
+<%inherit file="/common_jasmine.mako"/>
+
+<%block name="specs">
+  <script src="/static/ext/js/knockout.mapping-2.3.2.js" type="text/javascript" charset="utf-8"></script>
+  <script src="/static/ext/js/moment.min.js" type="text/javascript" charset="utf-8"></script>
+  <script src="static/js/workflow.js" type="text/javascript" charset="utf-8"></script>
+  <script src="static/jasmine/workflow.js" type="text/javascript" charset="utf-8"></script>
+</%block>
+
+<%block name="fixtures">
+  <div style="display:none">
+    <h1>Buongiorno, world!</h1>
+    <div id="graph"></div>
+  </div>
+</%block>

+ 1 - 0
apps/oozie/src/oozie/urls.py

@@ -49,6 +49,7 @@ urlpatterns = patterns(
   url(r'^list_history$', 'list_history', name='list_history'),
   url(r'^list_history/(?P<record_id>[-\w]+)$', 'list_history_record', name='list_history_record'),
   url(r'^setup_app/$', 'setup_app', name='setup_app'),
+  url(r'^jasmine', 'jasmine'),
 )
 
 

+ 4 - 0
apps/oozie/src/oozie/views/editor.py

@@ -577,3 +577,7 @@ def import_workflow(request):
     'workflow_form': workflow_form,
     'workflow': workflow,
   })
+
+
+def jasmine(request):
+  return render('editor/jasmine.mako', request, None)

+ 167 - 0
apps/oozie/static/jasmine/workflow.js

@@ -0,0 +1,167 @@
+
+describe("WorkflowModel", function(){
+
+  function create_three_step_workflow() {
+    var workflow_model = new WorkflowModel({
+      id: 1,
+      name: "Test-Three-Step-Workflow",
+      start: 1,
+      end: 5
+    });
+    var registry = new Registry();
+    var workflow = new Workflow({
+      model: workflow_model,
+      data: {
+        "nodes":[{
+            "description":"",
+            "workflow":1,
+            "child_links":[{
+                "comment":"",
+                "name":"to",
+                "parent":1,
+                "child":2
+              },{
+                "comment":"",
+                "name":"related",
+                "parent":1,
+                "child":5
+            }],
+            "node_type":"start",
+            "id":1,
+            "name":"start"
+          },{
+            "id":2,
+            "name":"Sleep-1",
+            "workflow":1,
+            "node_type":"mapreduce",
+            "jar_path":"/user/hue/oozie/workspaces/lib/hadoop-examples.jar",
+            "child_links":[{
+                "comment":"",
+                "name":"ok",
+                "parent":2,
+                "child":3
+              },{
+                "comment":"",
+                "name":"error",
+                "parent":2,
+                "child":6
+              }],
+        },{
+          "id":3,
+          "name":"Sleep-2",
+          "workflow":1,
+          "node_type":"mapreduce",
+          "jar_path":"/user/hue/oozie/workspaces/lib/hadoop-examples.jar",
+          "child_links":[{
+              "comment":"",
+              "name":"ok",
+              "parent":3,
+              "child":4
+            },{
+              "comment":"",
+              "name":"error",
+              "parent":3,
+              "child":6
+            }],
+        },{
+          "id":4,
+          "name":"Sleep-3",
+          "workflow":1,
+          "node_type":"mapreduce",
+          "jar_path":"/user/hue/oozie/workspaces/lib/hadoop-examples.jar",
+          "child_links":[{
+              "comment":"",
+              "name":"ok",
+              "parent":4,
+              "child":5
+            },{
+              "comment":"",
+              "name":"error",
+              "parent":4,
+              "child":6
+            }],
+        },{
+          "id":6,
+          "name":"kill",
+          "workflow":1,
+          "node_type":"kill",
+          "child_links":[],
+          "message":"Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}]",
+        },{
+          "id":5,
+          "name":"end",
+          "workflow":1,
+          "node_type":"end",
+          "child_links":[],
+        }],
+      },
+      registry: registry
+    });
+    return workflow;
+  }
+
+  describe("Node APIs", function(){
+    var node = null;
+    var viewModel = create_three_step_workflow();
+    viewModel.rebuild();
+
+    it("Should be able to detach a node", function() {
+      node = viewModel.nodes()[2];
+      node.detach();
+      viewModel.rebuild();
+      expect(viewModel.nodes().length).toEqual(4);
+    });
+
+    it("Should be able to append a node", function() {
+      viewModel.nodes()[1].append(node);
+      viewModel.rebuild();
+      expect(viewModel.nodes().length).toEqual(5);
+    });
+
+    it("Should be able to fail when appending the same node", function() {
+      viewModel.nodes()[1].append(node);
+      viewModel.rebuild();
+      expect(viewModel.nodes().length).toEqual(5);
+    });
+  });
+
+  // describe("Node movement", function(){
+  //   it("Should be able to move a node up", function() {
+
+  //   });
+
+  //   it("Should be able to move a node down", function() {
+
+  //   });
+
+  //   it("Should be able to create a fork", function() {
+
+  //   });
+
+  //   it("Should be able to create a decision", function() {
+
+  //   });
+  // });
+
+  // describe("Node operations", function(){
+  //   it("Should be able to edit a node", function() {
+  //     $('.edit-node-link')[0].click();
+  //     expect($("#node-modal").length).toBeGreaterThan(0);
+  //   });
+
+  //   it("Should be able to create a node", function() {
+  //     $('.new-node-link[data-node-type=mapreduce]')[0].click();
+  //     expect($("#node-modal").length).toBeGreaterThan(0);
+  //   });
+
+  //   it("Should be able to clone a node", function() {
+  //     $('.clone-node-link')[0].click();
+  //     expect($("#node-modal").length).toBeGreaterThan(0);
+  //   });
+
+  //   it("Should be able to remove a node", function() {
+  //     $('.delete-node-btn')[0].click();
+  //     expect($("#node-modal").length).toBeGreaterThan(0);
+  //   });
+  // });
+});

+ 130 - 0
apps/oozie/static/jasmine/workflow.json

@@ -0,0 +1,130 @@
+{
+  "job_xml":"",
+  "is_shared":true,
+  "end":2,
+  "description":"Example of MapReduce action that sleeps",
+  "parameters":"[]",
+  "is_single":false,
+  "schema_version":"uri:oozie:workflow:0.4",
+  "start":1,
+  "last_modified":"2012-12-21 15:46:57.639664",
+  "owner":1,
+  "nodes":[{
+      "description":"",
+      "workflow":21,
+      "child_links":[{
+          "comment":"",
+          "name":"to",
+          "id":115,
+          "parent":91,
+          "child":92
+        },{
+          "comment":"",
+          "name":"related",
+          "id":116,
+          "parent":91,
+          "child":90
+      }],
+      "node_type":"start",
+      "node_ptr":91,
+      "id":91,
+      "name":"start"
+    },{
+      "files":"[]",
+      "job_xml":"",
+      "description":"Sleep for some time",
+      "workflow":21,
+      "jar_path":"/user/hue/oozie/workspaces/lib/hadoop-examples.jar",
+      "job_properties":"[{\"name\":\"mapred.reduce.tasks\",\"value\":\"1\"},{\"name\":\"mapred.mapper.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.reducer.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.mapoutput.key.class\",\"value\":\"org.apache.hadoop.io.IntWritable\"},{\"name\":\"mapred.mapoutput.value.class\",\"value\":\"org.apache.hadoop.io.NullWritable\"},{\"name\":\"mapred.output.format.class\",\"value\":\"org.apache.hadoop.mapred.lib.NullOutputFormat\"},{\"name\":\"mapred.input.format.class\",\"value\":\"org.apache.hadoop.examples.SleepJob$SleepInputFormat\"},{\"name\":\"mapred.partitioner.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.speculative.execution\",\"value\":\"false\"},{\"name\":\"sleep.job.map.sleep.time\",\"value\":\"0\"},{\"name\":\"sleep.job.reduce.sleep.time\",\"value\":\"${REDUCER_SLEEP_TIME}\"}]",
+      "node_type":"mapreduce",
+      "archives":"[]",
+      "node_ptr":92,
+      "prepares":"[]",
+      "child_links":[{
+          "comment":"",
+          "name":"ok",
+          "id":117,
+          "parent":92,
+          "child":94
+        },{
+          "comment":"",
+          "name":"error",
+          "id":118,
+          "parent":92,
+          "child":89
+        }],
+    "id":92,
+    "name":"Sleep"
+  },{
+    "files":"[]",
+    "job_xml":"",
+    "description":"Sleep for some time",
+    "workflow":21,
+    "jar_path":"/user/hue/oozie/workspaces/lib/hadoop-examples.jar",
+    "job_properties":"[{\"name\":\"mapred.reduce.tasks\",\"value\":\"1\"},{\"name\":\"mapred.mapper.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.reducer.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.mapoutput.key.class\",\"value\":\"org.apache.hadoop.io.IntWritable\"},{\"name\":\"mapred.mapoutput.value.class\",\"value\":\"org.apache.hadoop.io.NullWritable\"},{\"name\":\"mapred.output.format.class\",\"value\":\"org.apache.hadoop.mapred.lib.NullOutputFormat\"},{\"name\":\"mapred.input.format.class\",\"value\":\"org.apache.hadoop.examples.SleepJob$SleepInputFormat\"},{\"name\":\"mapred.partitioner.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.speculative.execution\",\"value\":\"false\"},{\"name\":\"sleep.job.map.sleep.time\",\"value\":\"0\"},{\"name\":\"sleep.job.reduce.sleep.time\",\"value\":\"${REDUCER_SLEEP_TIME}\"}]",
+    "node_type":"mapreduce",
+    "archives":"[]",
+    "node_ptr":94,
+    "prepares":"[]",
+    "child_links":[{
+        "comment":"",
+        "name":"ok",
+        "id":121,
+        "parent":94,
+        "child":93
+      },{
+        "comment":"",
+        "name":"error",
+        "id":122,
+        "parent":94,
+        "child":89
+      }],
+    "id":94,
+    "name":"Sleep-copy"
+  },{
+    "files":"[]",
+    "job_xml":"",
+    "description":"Sleep for some time",
+    "workflow":21,
+    "jar_path":"/user/hue/oozie/workspaces/lib/hadoop-examples.jar",
+    "job_properties":"[{\"name\":\"mapred.reduce.tasks\",\"value\":\"1\"},{\"name\":\"mapred.mapper.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.reducer.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.mapoutput.key.class\",\"value\":\"org.apache.hadoop.io.IntWritable\"},{\"name\":\"mapred.mapoutput.value.class\",\"value\":\"org.apache.hadoop.io.NullWritable\"},{\"name\":\"mapred.output.format.class\",\"value\":\"org.apache.hadoop.mapred.lib.NullOutputFormat\"},{\"name\":\"mapred.input.format.class\",\"value\":\"org.apache.hadoop.examples.SleepJob$SleepInputFormat\"},{\"name\":\"mapred.partitioner.class\",\"value\":\"org.apache.hadoop.examples.SleepJob\"},{\"name\":\"mapred.speculative.execution\",\"value\":\"false\"},{\"name\":\"sleep.job.map.sleep.time\",\"value\":\"0\"},{\"name\":\"sleep.job.reduce.sleep.time\",\"value\":\"${REDUCER_SLEEP_TIME}\"}]",
+    "node_type":"mapreduce",
+    "archives":"[]",
+    "node_ptr":93,
+    "prepares":"[]",
+    "child_links":[{
+        "comment":"",
+        "name":"ok",
+        "id":119,
+        "parent":93,
+        "child":90
+      },{
+        "comment":"",
+        "name":"error",
+        "id":120,
+        "parent":93,
+        "child":89
+      }],
+    "id":93,
+    "name":"Sleep-copy"
+  },{
+    "description":"",
+    "workflow":21,
+    "child_links":[],
+    "node_type":"kill",
+    "node_ptr":89,
+    "message":"Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}]",
+    "id":89,
+    "name":"kill"
+  },{
+    "description":"",
+    "workflow":21,
+    "child_links":[],
+    "node_type":"end",
+    "node_ptr":90,
+    "id":90,
+    "name":"end"
+  }],
+  "id":21,
+  "name":"MapReduce-copy"
+}

+ 63 - 53
apps/oozie/static/js/workflow.js

@@ -40,6 +40,23 @@ var normalize_model_fields = (function() {
   return fn;
 })();
 
+// Parse JSON if it is JSON and appropriately map.
+// Apply subscriber to each mapping.
+var map_params = function(options, subscribe) {
+  options.data = ($.type(options.data) == "string") ? $.parseJSON(options.data) : options.data;
+  if ($.isArray(options.data)) {
+    var mapping =  ko.mapping.fromJS(options.data);
+    $.each(mapping(), function(index, value) {
+      subscribe(value);
+    });
+    return mapping;
+  } else {
+    var mapping =  ko.mapping.fromJS(options.data, {});
+    subscribe(mapping);
+    return mapping;
+  }
+};
+
 /**
  * Registry of models
  *  - Each model should have an ID attribute.
@@ -857,21 +874,6 @@ var NodeModule = function($, IdGeneratorTable) {
     map: function(model) {
       var self = this;
 
-      var map_params = function(options, subscribe) {
-        options.data = ($.type(options.data) == "string") ? $.parseJSON(options.data) : options.data;
-        if ($.isArray(options.data)) {
-          var mapping =  ko.mapping.fromJS(options.data);
-          $.each(mapping(), function(index, value) {
-            subscribe(value);
-          });
-          return mapping;
-        } else {
-          var mapping =  ko.mapping.fromJS(options.data, {});
-          subscribe(mapping);
-          return mapping;
-        }
-      };
-
       // @see http://knockoutjs.com/documentation/plugins-mapping.html
       var mapping = ko.mapping.fromJS(model, {
         ignore: ['initialize', 'toString'],
@@ -1144,10 +1146,10 @@ var NodeModule = function($, IdGeneratorTable) {
       var self = this;
 
       // Not fork nor decision nor self
-      if ($.inArray(self.node_type(), ['fork', 'decision']) == -1 && node.id() != self.id()) {
+      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(registry.get(link.child()));
+          node.addChild(self.registry.get(link.child()));
         });
         self.removeAllChildren();
         self.addChild(node);
@@ -1161,7 +1163,7 @@ var NodeModule = function($, IdGeneratorTable) {
       var self = this;
 
       var parents = [];
-      $.each(registry.nodes, function(id, node) {
+      $.each(self.registry.nodes, function(id, node) {
         $.each(node.links(), function(index, link) {
           if (link.child() == self.id()) {
             parents.push(node);
@@ -1179,7 +1181,7 @@ var NodeModule = function($, IdGeneratorTable) {
 
       var children = [];
       $.each(self.links(), function(index, link) {
-        children.push(registry.get(link.child()));
+        children.push(self.registry.get(link.child()));
       });
 
       return children;
@@ -1196,7 +1198,7 @@ var NodeModule = function($, IdGeneratorTable) {
 
       $.each(self.findParents(), function(index, parent) {
         $.each(self.links(), function(index, link) {
-          var node = registry.get(link.child());
+          var node = self.registry.get(link.child());
           parent.replaceChild(self, node);
         });
       });
@@ -1702,52 +1704,60 @@ var WorkflowModule = function($, NodeModelChooser, Node, ForkNode, DecisionNode,
       ignore: ['initialize', 'toString'],
       job_properties: {
         create: function(options) {
-          var mapping = ko.mapping.fromJS($.parseJSON(options.data) || options.data);
           var parent = options.parent;
-          mapping.name.subscribe(function(value) {
-            parent.job_properties.valueHasMutated();
-          });
-          mapping.value.subscribe(function(value) {
-            parent.job_properties.valueHasMutated();
-          });
-          return mapping;
+          var subscribe = function(mapping) {
+            mapping.name.subscribe(function(value) {
+              parent.job_properties.valueHasMutated();
+            });
+            mapping.value.subscribe(function(value) {
+              parent.job_properties.valueHasMutated();
+            });
+          };
+
+          return map_params(options, subscribe);
         },
         update: function(options) {
-          var mapping = ko.mapping.fromJS($.parseJSON(options.data) || options.data);
           var parent = options.parent;
-          mapping.name.subscribe(function(value) {
-            parent.job_properties.valueHasMutated();
-          });
-          mapping.value.subscribe(function(value) {
-            parent.job_properties.valueHasMutated();
-          });
-          return mapping;
+          var subscribe = function(mapping) {
+            mapping.name.subscribe(function(value) {
+              parent.job_properties.valueHasMutated();
+            });
+            mapping.value.subscribe(function(value) {
+              parent.job_properties.valueHasMutated();
+            });
+          };
+
+          return map_params(options, subscribe);
         }
       },
       parameters: {
         // Will receive individual objects to subscribe.
         // Containing array is mapped automagically
         create: function(options) {
-          var mapping =  ko.mapping.fromJS($.parseJSON(options.data) || options.data);
           var parent = options.parent;
-          mapping.name.subscribe(function(value) {
-            parent.parameters.valueHasMutated();
-          });
-          mapping.value.subscribe(function(value) {
-            parent.parameters.valueHasMutated();
-          });
-          return mapping;
+          var subscribe = function(mapping) {
+            mapping.name.subscribe(function(value) {
+              parent.parameters.valueHasMutated();
+            });
+            mapping.value.subscribe(function(value) {
+              parent.parameters.valueHasMutated();
+            });
+          };
+
+          return map_params(options, subscribe);
         },
         update: function(options) {
-          var mapping =  ko.mapping.fromJS($.parseJSON(options.data) || options.data);
           var parent = options.parent;
-          mapping.name.subscribe(function(value) {
-            parent.parameters.valueHasMutated();
-          });
-          mapping.value.subscribe(function(value) {
-            parent.parameters.valueHasMutated();
-          });
-          return mapping;
+          var subscribe = function(mapping) {
+            mapping.name.subscribe(function(value) {
+              parent.parameters.valueHasMutated();
+            });
+            mapping.value.subscribe(function(value) {
+              parent.parameters.valueHasMutated();
+            });
+          };
+
+          return map_params(options, subscribe);
         },
       }
     });
@@ -1863,7 +1873,7 @@ var WorkflowModule = function($, NodeModelChooser, Node, ForkNode, DecisionNode,
             }
           }
         });
-        workflow.is_dirty( false );
+        self.is_dirty( false );
       }
 
       if (!self.kill) {