Эх сурвалжийг харах

[oozie] Add workflow and coordinator variables

Added a new 'parameters' field to the Job model
Refactored all the submissions views and templates
Submission popup now integrates job and actions variables
Refactored properties form and made it pluggable
Create a new template for the submission popup
Integration with resubmissions views
Added new tests
Removed all the tabs and corrected some indentations
Got rid of all js and added boostrap modals to the popups
Romain Rigaux 13 жил өмнө
parent
commit
941ab54

+ 12 - 5
apps/oozie/src/oozie/forms.py

@@ -15,7 +15,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import datetime
 import logging
 
 from django import forms
@@ -27,6 +26,11 @@ from oozie.models import Workflow, Node, Java, Mapreduce, Streaming, Coordinator
 LOG = logging.getLogger(__name__)
 
 
+class ParameterForm(forms.Form):
+  name = forms.CharField(max_length=40, widget=forms.widgets.HiddenInput())
+  value = forms.CharField(max_length=40, required=False)
+
+
 class WorkflowForm(forms.ModelForm):
   class Meta:
     model = Workflow
@@ -117,15 +121,17 @@ class DefaultLinkForm(forms.ModelForm):
     super(DefaultLinkForm, self).__init__(*args, **kwargs)
     self.fields['child'].widget = forms.Select(choices=((node.id, node) for node in set(workflow.node_set.all())))
 
+
 DATE_FORMAT = '%m/%d/%Y'
 TIME_FORMAT = '%I:%M %p'
 
 class CoordinatorForm(forms.ModelForm):
-  """Used for specifying a design"""
   start = forms.SplitDateTimeField(input_time_formats=[TIME_FORMAT],
-                                   widget=SplitDateTimeWidget(attrs={'class': 'short', 'id': 'coordinator_start'}, date_format=DATE_FORMAT, time_format=TIME_FORMAT))
+                                   widget=SplitDateTimeWidget(attrs={'class': 'short', 'id': 'coordinator_start'},
+                                                              date_format=DATE_FORMAT, time_format=TIME_FORMAT))
   end = forms.SplitDateTimeField(input_time_formats=[TIME_FORMAT],
-                                 widget=SplitDateTimeWidget(attrs={'class': 'short', 'id': 'coordinator_end'}, date_format=DATE_FORMAT, time_format=TIME_FORMAT))
+                                 widget=SplitDateTimeWidget(attrs={'class': 'short', 'id': 'coordinator_end'},
+                                                            date_format=DATE_FORMAT, time_format=TIME_FORMAT))
 
   class Meta:
     model = Coordinator
@@ -137,7 +143,8 @@ class CoordinatorForm(forms.ModelForm):
 
 class DatasetForm(forms.ModelForm):
   start = forms.SplitDateTimeField(input_time_formats=[TIME_FORMAT],
-                                   widget=SplitDateTimeWidget(attrs={'class': 'short'}, date_format=DATE_FORMAT, time_format=TIME_FORMAT))
+                                   widget=SplitDateTimeWidget(attrs={'class': 'short'},
+                                                              date_format=DATE_FORMAT, time_format=TIME_FORMAT))
 
   class Meta:
     model = Dataset

+ 14 - 3
apps/oozie/src/oozie/models.py

@@ -64,6 +64,8 @@ class Job(models.Model):
   schema_version = models.CharField(max_length=128, blank=True, default='')
   deployment_dir = models.CharField(max_length=1024, blank=True, verbose_name=_('HDFS deployment directory'))
   is_shared = models.BooleanField(default=False, db_index=True)
+  parameters = models.TextField(default='[]',
+                                help_text=_t('Configuration parameters (e.g. market=US)'))
 
   unique_together = ('owner', 'name')
 
@@ -97,6 +99,9 @@ class Job(models.Model):
   def get_absolute_url(self):
     return self.get_full_node().get_absolute_url()
 
+  def get_parameters(self):
+    return json.loads(self.parameters)
+
   @property
   def status(self):
     if self.is_shared:
@@ -104,8 +109,12 @@ class Job(models.Model):
     else:
       return 'personal'
 
-  def find_parameters(self, fields=None):
-    return find_parameters(self, fields)
+  def find_all_parameters(self):
+    params = dict([(param, '') for param in self.find_parameters()])
+    for param in self.get_parameters():
+      params[param['name'].strip()] = param['value']
+
+    return  [{'name': name, 'value': value} for name, value in params.iteritems()]
 
 
 class WorkflowManager(models.Manager):
@@ -927,6 +936,8 @@ class Coordinator(Job):
     for dataset in self.dataset_set.all():
       params.update(set(find_parameters(dataset, ['uri'])))
 
+    params.update(set(self.workflow.find_parameters()))
+
     return list(params - set(['MINUTE', 'DAY', 'MONTH', 'YEAR']))
 
 
@@ -1041,7 +1052,7 @@ class History(models.Model):
       history = History.objects.get(oozie_job_id=oozie_id)
       if history.job.owner != user:
         history = None
-    except History.DoesNotExist, ex:
+    except History.DoesNotExist:
       pass
 
     return history

+ 7 - 6
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinator.mako

@@ -76,9 +76,10 @@ ${ layout.menubar(section='dashboard') }
           <tr>
             <td>${ _('Manage') }</td>
             <td>
-            <form action="${ url('oozie:resubmit_coordinator', job_id=oozie_coordinator.id) }" method="post">
+            <form action="${ url('oozie:resubmit_coordinator', oozie_coord_id=oozie_coordinator.id) }" method="post">
             % if oozie_coordinator.is_running():
-              <button type="button" class="btn manage-oozie-job-btn" data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_coordinator.id, action='kill') }"  data-message="The coordinator was killed!">
+              <button type="button" class="btn manage-oozie-job-btn" data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_coordinator.id, action='kill') }"
+                data-message="The coordinator was killed!">
                 ${ _('Kill') }
               </button>
             % else:
@@ -95,10 +96,10 @@ ${ layout.menubar(section='dashboard') }
 
     <ul class="nav nav-tabs">
       <li class="active"><a href="#calendar" data-toggle="tab">${ _('Calendar') }</a></li>
-	  <li><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
-	  <li><a href="#configuration" data-toggle="tab">${ _('Configuration') }</a></li>
-	  <li><a href="#log" data-toggle="tab">${ _('Log') }</a></li>
-	  <li><a href="#definition" data-toggle="tab">${ _('Definition') }</a></li>
+    <li><a href="#actions" data-toggle="tab">${ _('Actions') }</a></li>
+    <li><a href="#configuration" data-toggle="tab">${ _('Configuration') }</a></li>
+    <li><a href="#log" data-toggle="tab">${ _('Log') }</a></li>
+    <li><a href="#definition" data-toggle="tab">${ _('Definition') }</a></li>
     </ul>
 
     <div class="tab-content" style="padding-bottom:200px">

+ 3 - 3
apps/oozie/src/oozie/templates/dashboard/list_oozie_coordinators.mako

@@ -45,9 +45,9 @@ ${layout.menubar(section='dashboard')}
         </span>
         <span style="float:left;padding-left:10px;padding-right:10px;margin-top:3px">${ _('days with status') }</span>
         <span class="btn-group" style="float:left;">
-	      <a class="btn btn-status btn-success">Succeeded</a>
-	      <a class="btn btn-status btn-warning">Running</a>
-	      <a class="btn btn-status btn-danger">Killed</a>
+        <a class="btn btn-status btn-success">Succeeded</a>
+        <a class="btn btn-status btn-warning">Running</a>
+        <a class="btn btn-status btn-danger">Killed</a>
         </span>
    </form>
   </div>

+ 3 - 2
apps/oozie/src/oozie/templates/dashboard/list_oozie_workflow.mako

@@ -112,9 +112,10 @@ ${ layout.menubar(section='dashboard') }
       ${ _('Manage') }
     </div>
     <div class="span3">
-      <form action="${ url('oozie:resubmit_workflow', job_id=oozie_workflow.id) }" method="post">
+      <form action="${ url('oozie:resubmit_workflow', oozie_wf_id=oozie_workflow.id) }" method="post">
       % if oozie_workflow.is_running():
-        <button type="button" class="btn manage-oozie-job-btn" data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_workflow.id, action='kill') }" data-message="The workflow was killed!">
+        <button type="button" class="btn manage-oozie-job-btn" data-url="${ url('oozie:manage_oozie_jobs', job_id=oozie_workflow.id, action='kill') }"
+            data-message="The workflow was killed!">
           ${ _('Kill') }
         </button>
       % else:

+ 12 - 12
apps/oozie/src/oozie/templates/editor/create_workflow.mako

@@ -46,14 +46,14 @@ ${ layout.menubar(section='workflows') }
                    ${ utils.render_field(workflow_form['name']) }
                    ${ utils.render_field(workflow_form['description']) }
 
-					<div class="control-group ">
-					  <label class="control-label">
-					    <a href="#" id="advanced-btn" onclick="$('#advanced-container').toggle('hide')">
-					      <i class="icon-share-alt"></i> ${ _('advanced') }</a>
-					  </label>
-					  <div class="controls">					
-					  </div>				
-					</div>
+          <div class="control-group ">
+            <label class="control-label">
+              <a href="#" id="advanced-btn" onclick="$('#advanced-container').toggle('hide')">
+                <i class="icon-share-alt"></i> ${ _('advanced') }</a>
+            </label>
+            <div class="controls">
+            </div>
+          </div>
 
                    <div id="advanced-container" class="hide">
                      ${ utils.render_field(workflow_form['deployment_dir']) }
@@ -66,10 +66,10 @@ ${ layout.menubar(section='workflows') }
         </div>
       </div>
 
-	    <div class="form-actions center">
-	      <a class="btn" onclick="history.back()">${ _('Back') }</a>
-	      <input class="btn btn-primary" type="submit" value="${ _('Save') }"></input>
-	    </div>
+      <div class="form-actions center">
+        <a class="btn" onclick="history.back()">${ _('Back') }</a>
+        <input class="btn btn-primary" type="submit" value="${ _('Save') }"></input>
+      </div>
       </form>
     </div>
 </div>

+ 6 - 9
apps/oozie/src/oozie/templates/editor/edit_coordinator.mako

@@ -21,6 +21,7 @@
 
 <%namespace name="layout" file="../navigation-bar.mako" />
 <%namespace name="utils" file="../utils.inc.mako" />
+<%namespace name="properties" file="job_action_properties.mako" />
 
 ${ commonheader(_("Oozie App"), "oozie", "100px") }
 ${ layout.menubar(section='coordinators') }
@@ -45,12 +46,7 @@ ${ layout.menubar(section='coordinators') }
     % endif
   </ul>
 
-% if coordinator.id:
-  <form class="form-horizontal" id="workflowForm" action="${ url('oozie:edit_coordinator', coordinator=coordinator.id) }" method="POST">
-  % else:
-  <form class="form-horizontal" id="workflowForm" action="${ url('oozie:edit_coordinator') }" method="POST">
-  % endif
-
+  <form class="form-horizontal" id="jobForm" action="${ url('oozie:edit_coordinator', coordinator=coordinator.id) }" method="POST">
     <div class="tab-content">
       <div class="tab-pane active" id="editor">
         <div class="row-fluid">
@@ -63,6 +59,7 @@ ${ layout.menubar(section='coordinators') }
                ${ utils.render_field(coordinator_form['description']) }
                ${ utils.render_field(coordinator_form['workflow']) }
                ${ utils.render_field(coordinator_form['is_shared']) }
+               ${ properties.print_key_value(_('Parameters'), 'parameters', coordinator_form, parameters) }
              </div>
 
             <hr/>
@@ -307,7 +304,7 @@ ${ layout.menubar(section='coordinators') }
   <div class="form-actions center">
     <a href="${ url('oozie:list_coordinator') }" class="btn">${ _('Back') }</a>
     % if can_edit_coordinator:
-      <input class="btn btn-primary" type="submit" value="${ _('Save') }"></input>
+      <input class="btn btn-primary" data-bind="click: submit" type="submit" value="${ _('Save') }"></input>
     % endif
   </div>
 
@@ -384,8 +381,8 @@ ${ layout.menubar(section='coordinators') }
       endTime: '23:59',
       step: 60
     };
-    $( "input.date" ).datepicker();
-    $( "input.time" ).timePicker(timeOptions);
+    $("input.date").datepicker();
+    $("input.time").timePicker(timeOptions);
 
     $("#datasets-btn").click(function() {
       $('[href=#datasets]').tab('show');

+ 12 - 11
apps/oozie/src/oozie/templates/editor/edit_workflow.mako

@@ -21,6 +21,7 @@
 
 <%namespace name="layout" file="../navigation-bar.mako" />
 <%namespace name="utils" file="../utils.inc.mako" />
+<%namespace name="properties" file="job_action_properties.mako" />
 
 ${ commonheader(_("Oozie App"), "oozie", "100px") }
 ${ layout.menubar(section='workflows') }
@@ -47,7 +48,7 @@ ${ layout.menubar(section='workflows') }
     <li><a href="#history" data-toggle="tab">${ _('History') }</a></li>
   </ul>
 
-  <form class="form-horizontal" id="workflowForm" action="${ url('oozie:edit_workflow', workflow=workflow.id) }" method="POST">
+  <form class="form-horizontal" id="jobForm" action="${ url('oozie:edit_workflow', workflow=workflow.id) }" method="POST">
 
     <div class="tab-content">
       <div class="tab-pane active" id="editor">
@@ -117,13 +118,11 @@ ${ layout.menubar(section='workflows') }
             <h2>${ _('Properties') }</h2>
             <br/>
               <fieldset>
-               ${ utils.render_field(workflow_form['name']) }
-               ${ utils.render_field(workflow_form['description']) }
-                <div class="control-group">
-                  <label class="control-label">${ _('Properties') }</label><div class="controls"></div>
-                </div>
+                ${ utils.render_field(workflow_form['name']) }
+                ${ utils.render_field(workflow_form['description']) }
                 ${ utils.render_field(workflow_form['deployment_dir']) }
                 ${ utils.render_field(workflow_form['is_shared']) }
+                ${ properties.print_key_value(_('Parameters'), 'parameters', workflow_form, parameters) }
              </fieldset>
            </div>
         </div>
@@ -166,13 +165,15 @@ ${ layout.menubar(section='workflows') }
 <script src="/static/ext/js/knockout-2.0.0.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript">
-  $(".action-link").click(function(){
-    window.location = $(this).attr('data-edit');
-  });
+  $(document).ready(function(){
+    $(".action-link").click(function(){
+      window.location = $(this).attr('data-edit');
+    });
 
-  $("a[data-row-selector='true']").jHueRowSelector();
+    $("a[data-row-selector='true']").jHueRowSelector();
+  });
 </script>
 
 ${ utils.path_chooser_libs(True) }
 
-${commonfooter(messages)}
+${ commonfooter(messages) }

+ 17 - 17
apps/oozie/src/oozie/templates/editor/edit_workflow_action.mako

@@ -347,16 +347,16 @@ ${ layout.menubar(section='workflows') }
         };
       };
 
-	  var viewModel = new ViewModel(
-	            ${ job_properties },
-	            arrayToDictArray(${ files }),
-	            arrayToDictArray(${ archives }),
-	            ${ params });
-	
-	  ko.bindingHandlers.fileChooser = {
-	        init: function(element, valueAccessor, allBindings, model) {
-	        var self = $(element);
-	        self.after(getFileBrowseButton(self));
+    var viewModel = new ViewModel(
+              ${ job_properties },
+              arrayToDictArray(${ files }),
+              arrayToDictArray(${ archives }),
+              ${ params });
+
+    ko.bindingHandlers.fileChooser = {
+          init: function(element, valueAccessor, allBindings, model) {
+          var self = $(element);
+          self.after(getFileBrowseButton(self));
       }
     };
 
@@ -371,13 +371,13 @@ ${ layout.menubar(section='workflows') }
       return $("<button>").addClass("btn").addClass("fileChooserBtn").text("..").click(function(e){
         e.preventDefault();
         $("#fileChooserModal").jHueFileChooser({
-	        onFileChoose: function(filePath) {
-	            inputElement.val(filePath);
-	            $("#chooseFile").modal("hide");
-	        },
-	        createFolder: false
-	      });
-	    $("#chooseFile").modal("show");
+          onFileChoose: function(filePath) {
+              inputElement.val(filePath);
+              $("#chooseFile").modal("hide");
+          },
+          createFolder: false
+        });
+      $("#chooseFile").modal("show");
       })
     }
 

+ 36 - 36
apps/oozie/src/oozie/templates/editor/edit_workflow_fork.mako

@@ -64,7 +64,7 @@ ${ layout.menubar(section='workflows') }
       <div class="control-group">
         <label class="control-label"></label>
         <div class="controls">
-	        <table class="table-condensed">
+          <table class="table-condensed">
             <thead>
               <tr>
                 <th>${ _('Predicate') }</th>
@@ -72,41 +72,41 @@ ${ layout.menubar(section='workflows') }
                 <th>${ _('Action') }</th>
               </tr>
             </thead>
-	          <tbody>
-	            % for form in link_formset.forms:
-	                % for hidden in form.hidden_fields():
-	                  ${ hidden }
-	                % endfor
-	            <tr>
-	              <td>
-	                ${ utils.render_field(form['comment']) }
-	              </td>
-	              <td class="center">
-	                ${ _('go to') }
-	              </td>
-	              <td class="right">
-	                <a href="${ form.instance.child.get_full_node().get_edit_link() }" class="span3">${ form.instance.child }</a>
-	              </td>
-	            </tr>
-	            % endfor
-	              <tr>
-	                <td>
-	           <div class="control-group">
-	              <label class="control-label"></label>
-	              <div class="controls span8">
-	                  <div>${ _('default') }</div>
-	              </div>
-	            </div>
-	                </td>
-	                <td class="center">
-	                ${ _('go to') }
-	                </td>
-	                <td class="right">
-	                  ${ utils.render_field(default_link_form['child']) }
-	                </td>
-	              </tr>
-	          </tbody>
-	        </table>
+            <tbody>
+              % for form in link_formset.forms:
+                  % for hidden in form.hidden_fields():
+                    ${ hidden }
+                  % endfor
+              <tr>
+                <td>
+                  ${ utils.render_field(form['comment']) }
+                </td>
+                <td class="center">
+                  ${ _('go to') }
+                </td>
+                <td class="right">
+                  <a href="${ form.instance.child.get_full_node().get_edit_link() }" class="span3">${ form.instance.child }</a>
+                </td>
+              </tr>
+              % endfor
+                <tr>
+                  <td>
+             <div class="control-group">
+                <label class="control-label"></label>
+                <div class="controls span8">
+                    <div>${ _('default') }</div>
+                </div>
+              </div>
+                  </td>
+                  <td class="center">
+                  ${ _('go to') }
+                  </td>
+                  <td class="right">
+                    ${ utils.render_field(default_link_form['child']) }
+                  </td>
+                </tr>
+            </tbody>
+          </table>
         </div>
       </div>
 

+ 1 - 1
apps/oozie/src/oozie/templates/editor/gen/workflow-graph-editable.xml.mako

@@ -53,7 +53,7 @@
             <div class="span10">
           % endif
 
-            <span class="">${ node.node_type }</span>
+          <span class="">${ node.node_type }</span>
 
           <br/>
           ${ node.description }

+ 92 - 0
apps/oozie/src/oozie/templates/editor/job_action_properties.mako

@@ -0,0 +1,92 @@
+## 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.
+
+<%!
+from django.utils.translation import ugettext as _
+%>
+
+<%namespace name="utils" file="../../utils.inc.mako" />
+
+
+<%def name="print_key_value(label, element, form, initial_parameters)">
+  <div class="control-group">
+    <label class="control-label">${ label }</label>
+    <div class="controls">
+      <table class="table-condensed designTable" data-bind="visible: ${ element }().length > 0">
+        <thead>
+          <tr>
+            <th>${ _('Name') }</th>
+            <th>${ _('Value') }</th>
+            <th/>
+          </tr>
+        </thead>
+        <tbody data-bind="foreach: ${ element }">
+          <tr>
+            <td><input class="required" data-bind="value: name, uniqueName: false" /></td>
+            <td><input class="required" data-bind="value: value, uniqueName: false" /></td>
+            <td><a class="btn btn-small" href="#" data-bind="click: $root.remove_${ element }">${ _('Delete') }</a></td>
+          </tr>
+        </tbody>
+      </table>
+      % if len(form[element].errors):
+        <div class="row">
+          <div class="alert alert-error">
+            ${ unicode(form[element].errors) | n }
+          </div>
+        </div>
+      % endif
+
+      <button class="btn" data-bind="click: add_${ element }">${ _('Add') }</button>
+    </div>
+  </div>
+
+  <style>
+    .designTable th {
+      text-align:left;
+    }
+  </style>
+
+  <script type="text/javascript">
+    $(document).ready(function(){
+      var ViewModel = function(parameters) {
+        var self = this;
+        self.parameters = ko.observableArray(parameters);
+
+        self.add_parameters = function() {
+          self.parameters.push({name: "", value: ""});
+        };
+
+        self.remove_parameters = function(val) {
+          self.parameters.remove(val);
+        };
+
+        self.submit = function(form) {
+          var form = $("#jobForm");
+
+          $("<input>").attr("type", "hidden")
+              .attr("name", "parameters")
+              .attr("value", ko.utils.stringifyJson(self.parameters))
+              .appendTo(form);
+
+          form.submit();
+        };
+      };
+
+      var viewModel = new ViewModel(${ initial_parameters });
+      ko.applyBindings(viewModel);
+    });
+  </script>
+</%def>

+ 39 - 84
apps/oozie/src/oozie/templates/editor/list_coordinators.mako

@@ -27,6 +27,7 @@
 ${ commonheader(_("Oozie App"), "oozie", "100px") }
 ${ layout.menubar(section='coordinators') }
 
+
 <div class="container-fluid">
   <h1>${ _('Coordinator Editor') }</h1>
 
@@ -38,15 +39,15 @@ ${ layout.menubar(section='coordinators') }
     <div class="row-fluid">
       <div class="span3">
         <form class="form-search">
-            ${ _('Filter:') }
-            <input id="filterInput" class="input-xlarge search-query" placeholder="${ _('Search for username, name, etc...') }">
+          ${ _('Filter:') }
+          <input id="filterInput" class="input-xlarge search-query" placeholder="${ _('Search for username, name, etc...') }">
         </form>
       </div>
       <div class="span3">
         <button class="btn action-buttons" id="submit-btn" disabled="disabled"><i class="icon-play"></i> ${ _('Submit') }</button>
-	    &nbsp;&nbsp;&nbsp;&nbsp;
-	    <button class="btn action-buttons" id="clone-btn" disabled="disabled"><i class="icon-retweet"></i> ${ _('Copy') }</button>
-	    <button class="btn action-buttons" id="delete-btn" disabled="disabled"><i class="icon-remove"></i> ${ _('Delete') }</button>
+      &nbsp;&nbsp;&nbsp;&nbsp;
+      <button class="btn action-buttons" id="clone-btn" disabled="disabled"><i class="icon-retweet"></i> ${ _('Copy') }</button>
+      <button class="btn action-buttons" id="delete-btn" disabled="disabled"><i class="icon-remove"></i> ${ _('Delete') }</button>
       </div>
     </div>
   </div>
@@ -72,7 +73,6 @@ ${ layout.menubar(section='coordinators') }
           <td class=".btn-large action-column" data-row-selector-exclude="true" style="background-color: white;">
             <input type="radio" name="action" data-row-selector-exclude="true"
               % if can_edit_job(currentuser, coordinator):
-                  data-param-url="${ url('oozie:workflow_parameters', workflow=coordinator.id) }"
                   data-delete-url="${ url('oozie:delete_coordinator', coordinator=coordinator.id) }"
               % endif
               % if can_access_job(currentuser, coordinator):
@@ -106,26 +106,9 @@ ${ layout.menubar(section='coordinators') }
 </div>
 
 
-<div id="submitWf" class="modal hide fade">
-  <form id="submitWfForm" action="" method="POST">
-    <div class="modal-header">
-        <a href="#" class="close" data-dismiss="modal">&times;</a>
-        <h3 id="submitWfMessage">${ _('Submit this coordinator?') }</h3>
-    </div>
-    <div class="modal-body">
-      <fieldset>
-        <div id="param-container">
-        </div>
-      </fieldset>
-    </div>
-    <div class="modal-footer">
-      <a href="#" class="btn secondary hideModal">${ _('Cancel') }</a>
-      <input id="submitBtn" type="submit" class="btn primary" value="${ _('Submit') }"/>
-    </div>
-  </form>
-</div>
+<div id="submit-job-modal" class="modal hide"></div>
 
-<div id="deleteWf" class="modal hide fade">
+<div id="delete-job" class="modal hide">
   <form id="deleteWfForm" action="" method="POST">
     <div class="modal-header">
       <a href="#" class="close" data-dismiss="modal">&times;</a>
@@ -133,14 +116,16 @@ ${ layout.menubar(section='coordinators') }
     </div>
     <div class="modal-footer">
       <input type="submit" class="btn primary" value="${ _('Yes') }"/>
-      <a href="#" class="btn secondary hideModal">${ _('No') }</a>
+      <a href="#" class="btn secondary" data-dismiss="modal">${ _('No') }</a>
     </div>
   </form>
 </div>
 
 
 <style>
-  td .btn-large{ cursor: crosshair;  }
+  td .btn-large {
+  cursor: crosshair;
+  }
 
   .action-column {
     cursor: auto;
@@ -175,82 +160,52 @@ ${ layout.menubar(section='coordinators') }
         ['#clone-btn', 'data-clone-url']]
 
       $.each(action_buttons, function(index) {
-          if (select_btn.attr(this[1])) {
-            $(this[0]).removeAttr('disabled');
-          } else {
-            $(this[0]).attr("disabled", "disabled");
-          }
+        if (select_btn.attr(this[1])) {
+          $(this[0]).removeAttr('disabled');
+        } else {
+          $(this[0]).attr("disabled", "disabled");
+        }
       });
     }
 
     update_action_buttons_status();
 
     $("#delete-btn").click(function(e){
-        var _this = $('input[name=action]:checked');
-        var _action = _this.attr("data-delete-url");
-        $("#deleteWfForm").attr("action", _action);
-        $("#deleteWfMessage").text(_this.attr("alt"));
-        $("#deleteWf").modal("show");
+      var _this = $('input[name=action]:checked');
+      var _action = _this.attr("data-delete-url");
+      $("#deleteWfForm").attr("action", _action);
+      $("#deleteWfMessage").text(_this.attr("alt"));
+      $("#delete-job").modal("show");
     });
 
-    $("#submit-btn").click(function(){
-        var _this = $('input[name=action]:checked');
-        var _action = _this.attr("data-submit-url");
-        $("#submitWfForm").attr("action", _action);
-        $("#submitWfMessage").text(_this.attr("alt"));
-        // We will show the model form, but disable the submit button
-        // until we've finish loading the parameters via ajax.
-        $("#submitBtn").attr("disabled", "disabled");
-        $("#submitWf").modal("show");
-
-        $.get(_this.attr("data-param-url"), function(data) {
-            var params = data["params"]
-            var container = $("#param-container");
-            container.empty();
-
-            for (key in params) {
-                if (! params.hasOwnProperty(key)) {
-                    continue;
-                }
-                container.append(
-	              $("<div/>").addClass("clearfix")
-	                .append($("<label/>").text(params[key]))
-	                .append(
-	                    $("<div/>").addClass("input")
-	                      .append($("<input/>").attr("name", key).attr("type", "text"))
-	                )
-                )
-            }
-            // Good. We can submit now.
-            $("#submitBtn").removeAttr("disabled");
-        }, "json");
-    });
+    $('#submit-btn').click(function() {
+      var _this = $('input[name=action]:checked');
+      var _action = _this.attr("data-submit-url");
+
+      $.get(_action,  function(response) {
+          $('#submit-job-modal').html(response);
+          $('#submit-job-modal').modal('show');
+        }
+      );
+     });
 
     $(".deleteConfirmation").click(function(){
-        var _this = $(this);
-        var _action = _this.attr("data-confirmation-url");
-        $("#deleteWfForm").attr("action", _action);
-        $("#deleteWfMessage").text(_this.attr("alt"));
-        $("#deleteWf").modal("show");
+      var _this = $(this);
+      var _action = _this.attr("data-url");
+      $("#deleteWfForm").attr("action", _action);
+      $("#deleteWfMessage").text(_this.attr("alt"));
+      $("#delete-job").modal("show");
     });
 
     $("#clone-btn").click(function(e){
-        var _this = $('input[name=action]:checked');
-        var _url = _this.attr("data-clone-url");
+      var _this = $('input[name=action]:checked');
+      var _url = _this.attr("data-clone-url");
 
       $.post(_url, function(data) {
         window.location = data.url;
       });
     });
 
-    $("#deleteWf .hideModal").click(function(){
-        $("#deleteWf").modal("hide");
-    });
-
-    $("#submitWf .hideModal").click(function(){
-        $("#submitWf").modal("hide");
-    });
-
     var oTable = $('#coordinatorTable').dataTable( {
       "sPaginationType": "bootstrap",
       'iDisplayLength': 50,

+ 49 - 96
apps/oozie/src/oozie/templates/editor/list_workflows.mako

@@ -76,7 +76,6 @@ ${ layout.menubar(section='workflows') }
           <td class=".btn-large action-column" data-row-selector-exclude="true" style="background-color: white;">
             <input type="radio" name="action" data-row-selector-exclude="true"
               % if can_access_job(currentuser, workflow):
-                  data-param-url="${ url('oozie:workflow_parameters', workflow=workflow.id) }"
                   data-submit-url="${ url('oozie:submit_workflow', workflow=workflow.id) }"
                   data-schedule-url="${ url('oozie:create_coordinator', workflow=workflow.id) }"
               % endif
@@ -108,24 +107,7 @@ ${ layout.menubar(section='workflows') }
 </div>
 
 
-<div id="submitWf" class="modal hide fade">
-  <form id="submitWfForm" action="" method="POST">
-    <div class="modal-header">
-      <a href="#" class="close" data-dismiss="modal">&times;</a>
-      <h3 id="submitWfMessage">${ _('Submit this workflow?') }</h3>
-    </div>
-    <div class="modal-body">
-      <fieldset>
-        <div id="param-container">
-        </div>
-      </fieldset>
-    </div>
-    <div class="modal-footer">
-      <a href="#" class="btn secondary hideModal">${ _('Cancel') }</a>
-      <input id="submitBtn" type="submit" class="btn primary" value="${ _('Submit') }"/>
-    </div>
-  </form>
-</div>
+<div id="submit-wf-modal" class="modal hide"></div>
 
 <div id="deleteWf" class="modal hide fade">
   <form id="deleteWfForm" action="" method="POST">
@@ -135,7 +117,7 @@ ${ layout.menubar(section='workflows') }
     </div>
     <div class="modal-footer">
       <input type="submit" class="btn primary" value="${ _('Yes') }"/>
-      <a href="#" class="btn secondary hideModal">${ _('No') }</a>
+      <a href="#" class="btn secondary" data-dismiss="modal">${ _('No') }</a>
     </div>
   </form>
 </div>
@@ -168,81 +150,60 @@ ${ layout.menubar(section='workflows') }
 
 <script type="text/javascript" charset="utf-8">
   $(document).ready(function() {
-      $(".action-row").click(function(e){
-        var select_btn = $(this).find('input');
-        select_btn.prop("checked", true);
-
-        $(".action-row").css("background-color", "");
-        $(this).css("background-color", "#ECF4F8");
+    $(".action-row").click(function(e){
+      var select_btn = $(this).find('input');
+      select_btn.prop("checked", true);
 
-        $(".action-buttons").attr("disabled", "disabled");
+      $(".action-row").css("background-color", "");
+      $(this).css("background-color", "#ECF4F8");
 
-        update_action_buttons_status();
-      });
+      $(".action-buttons").attr("disabled", "disabled");
 
-      function update_action_buttons_status() {
-        var select_btn = $('input[name=action]:checked');
+      update_action_buttons_status();
+    });
 
-        var action_buttons = [
-          ['#submit-btn', 'data-submit-url'],
-          ['#schedule-btn', 'data-schedule-url'],
-          ['#delete-btn', 'data-delete-url'],
-          ['#clone-btn', 'data-clone-url']]
+    function update_action_buttons_status() {
+      var select_btn = $('input[name=action]:checked');
+
+      var action_buttons = [
+        ['#submit-btn', 'data-submit-url'],
+        ['#schedule-btn', 'data-schedule-url'],
+        ['#delete-btn', 'data-delete-url'],
+        ['#clone-btn', 'data-clone-url']]
+
+      $.each(action_buttons, function(index) {
+        if (select_btn.attr(this[1])) {
+          $(this[0]).removeAttr('disabled');
+        } else {
+          $(this[0]).attr("disabled", "disabled");
+        }
+      });
+    }
 
-        $.each(action_buttons, function(index) {
-          if (select_btn.attr(this[1])) {
-            $(this[0]).removeAttr('disabled');
-          } else {
-            $(this[0]).attr("disabled", "disabled");
-          }
-        });
-      }
+    update_action_buttons_status();
 
-      update_action_buttons_status();
+    $("#delete-btn").click(function(e){
+      var _this = $('input[name=action]:checked');
+      var _action = _this.attr("data-delete-url");
+      $("#deleteWfForm").attr("action", _action);
+      $("#deleteWfMessage").text(_this.attr("alt"));
+      $("#deleteWf").modal("show");
+    });
 
-      $("#delete-btn").click(function(e){
-          var _this = $('input[name=action]:checked');
-          var _action = _this.attr("data-delete-url");
-          $("#deleteWfForm").attr("action", _action);
-          $("#deleteWfMessage").text(_this.attr("alt"));
-          $("#deleteWf").modal("show");
-      });
+    $('#submit-btn').click(function() {
+      var _this = $('input[name=action]:checked');
+      var _action = _this.attr("data-submit-url");
 
-      $("#submit-btn").click(function(e){
-          var _this = $('input[name=action]:checked');
-          var _action = _this.attr("data-submit-url");
-          $("#submitWfForm").attr("action", _action);
-          $("#submitWfMessage").text(_this.attr("alt"));
-          // We will show the model form, but disable the submit button
-          // until we've finish loading the parameters via ajax.
-          $("#submitBtn").attr("disabled", "disabled");
-          $("#submitWf").modal("show");
-
-          $.get(_this.attr("data-param-url"), function(data) {
-              var params = data["params"]
-              var container = $("#param-container");
-              container.empty();
-              for (key in params) {
-                if (!params.hasOwnProperty(key)) {
-                    continue;
-                }
-                container.append(
-                  $("<div/>").addClass("clearfix")
-                    .append($("<label/>").text(params[key]))
-                    .append(
-                        $("<div/>").addClass("input")
-                          .append($("<input/>").attr("name", key).attr("type", "text"))
-                    )
-                )
-              }
-              // Good. We can submit now.
-              $("#submitBtn").removeAttr("disabled");
-          }, "json");
-      });
+      $.get(_action,  function(response) {
+          $('#submit-wf-modal').html(response);
+          $('#submit-wf-modal').modal('show');
+        }
+      );
+     });
 
     $("#clone-btn").click(function(e){
-        var _this = $('input[name=action]:checked');
-        var _url = _this.attr("data-clone-url");
+      var _this = $('input[name=action]:checked');
+      var _url = _this.attr("data-clone-url");
 
       $.post(_url, function(data) {
         window.location = data.url;
@@ -250,18 +211,10 @@ ${ layout.menubar(section='workflows') }
     });
 
     $("#schedule-btn").click(function(e){
-        var _this = $('input[name=action]:checked');
-        var _url = _this.attr("data-schedule-url");
-
-        window.location.replace(_url);
-    });
-
-    $("#submitWf .hideModal").click(function(){
-        $("#submitWf").modal("hide");
-    });
+      var _this = $('input[name=action]:checked');
+      var _url = _this.attr("data-schedule-url");
 
-    $("#deleteWf .hideModal").click(function(){
-        $("#deleteWf").modal("hide");
+      window.location.replace(_url);
     });
 
     var oTable = $('#workflowTable').dataTable( {

+ 57 - 0
apps/oozie/src/oozie/templates/editor/submit_job_popup.mako

@@ -0,0 +1,57 @@
+## 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.
+
+<%!
+  from django.utils.translation import ugettext as _
+%>
+
+<%namespace name="utils" file="../utils.inc.mako" />
+
+
+<form action="${ action }" method="POST">
+  <div class="modal-header">
+    <a href="#" class="close" data-dismiss="modal">&times;</a>
+    <h3>${ _('Submit this job?') }</h3>
+  </div>
+  <div class="modal-body">
+    <fieldset>
+      <div id="param-container">
+
+       ${ params_form.management_form }
+
+       % for form in params_form.forms:
+          % for hidden in form.hidden_fields():
+            ${ hidden }
+          % endfor
+          <div class="fieldWrapper">
+            <div class="row-fluid">
+              <div class="span6">
+                ${ form['name'].form.initial.get('name') }
+              </div>
+              <div class="span6">
+                ${ utils.render_field(form['value'], show_label=False) }
+              </div>
+            </div>
+          </div>
+         % endfor
+      </div>
+    </fieldset>
+  </div>
+  <div class="modal-footer">
+    <a href="#" class="btn secondary" data-dismiss="modal">${ _('Cancel') }</a>
+    <input id="submit-btn" type="submit" class="btn primary" value="${ _('Submit') }"/>
+  </div>
+</form>

+ 4 - 2
apps/oozie/src/oozie/templates/utils.inc.mako

@@ -137,11 +137,13 @@
 </%def>
 
 
-<%def name="render_field(field)">
+<%def name="render_field(field, show_label=True)">
   %if not field.is_hidden:
     <% group_class = len(field.errors) and "error" or "" %>
     <div class="control-group ${group_class}">
-      <label class="control-label">${ field.label | n }</label>
+      % if show_label:
+        <label class="control-label">${ field.label | n }</label>
+      % endif
       <div class="controls">
         ${ field }
         % if len(field.errors):

+ 12 - 5
apps/oozie/src/oozie/tests.py

@@ -31,7 +31,8 @@ from liboozie import oozie_api
 from liboozie.types import WorkflowList, Workflow as OozieWorkflow, Coordinator as OozieCoordinator,\
   CoordinatorList, WorkflowAction
 
-from oozie.models import Workflow, Node, Job, Coordinator, Fork, History
+from oozie.models import Workflow, Node, Job, Coordinator, Fork, History,\
+  find_parameters
 from oozie.conf import SHARE_JOBS, REMOTE_DEPLOYMENT_DIR
 
 LOG = logging.getLogger(__name__)
@@ -117,7 +118,7 @@ class TestEditor:
             Job(name="foo ${b} $$"),
             Job(name="${foo}", description="xxx ${foo}")]
 
-    result = [job.find_parameters(['name', 'description']) for job in jobs]
+    result = [find_parameters(job, ['name', 'description']) for job in jobs]
     assert_equal(set(["b", "foo"]), reduce(lambda x, y: x | set(y), result, set()))
 
 
@@ -126,6 +127,10 @@ class TestEditor:
     pass
 
 
+  def test_find_all_parameters(self):
+        assert_equal([{'name': u'SLEEP', 'value': ''}, {'name': u'market', 'value': u'US'}], self.wf.find_all_parameters())
+
+
   def test_move_up(self):
     action1 = Node.objects.get(name='action-name-1')
     action2 = Node.objects.get(name='action-name-2')
@@ -727,7 +732,7 @@ class TestEditor:
 
 
 # Utils
-WORKFLOW_DICT = {u'deployment_dir': [u''], u'name': [u'wf-name-1'], u'description': [u'']}
+WORKFLOW_DICT = {u'deployment_dir': [u''], u'name': [u'wf-name-1'], u'description': [u''], u'parameters': [u'[{"name":"market","value":"US"}]']}
 
 
 # Beware: client not consistent with self.c in TestEditor
@@ -735,7 +740,8 @@ def add_action(workflow, action, name):
   c = make_logged_in_client()
 
   response = c.post("/oozie/new_action/%s/%s/%s" % (workflow, 'mapreduce', action), {
-     u'files': [u'[]'], u'name': [name], u'jar_path': [u'/tmp/.file.jar'], u'job_properties': [u'[]'], u'archives': [u'[]'], u'description': [u'']}, follow=True)
+     u'files': [u'[]'], u'name': [name], u'jar_path': [u'/tmp/.file.jar'], u'job_properties': [u'[{"name":"sleep","value":"${SLEEP}"}]'],
+     u'archives': [u'[]'], u'description': [u'']}, follow=True)
   assert_equal(200, response.status_code)
   assert_true(Node.objects.filter(name=name).exists(), response)
   return Node.objects.get(name=name)
@@ -775,7 +781,8 @@ def create_coordinator(workflow):
                         u'frequency_number': [u'1'], u'frequency_unit': [u'days'],
                         u'start_0': [u'07/01/2012'], u'start_1': [u'12:00 AM'],
                         u'end_0': [u'07/04/2012'], u'end_1': [u'12:00 AM'],
-                        u'timezone': [u'America/Los_Angeles']})
+                        u'timezone': [u'America/Los_Angeles'],
+                        u'parameters': [u'[{"name":"market","value":"US,France"}]']})
   assert_equal(coord_count + 1, Coordinator.objects.count(), response)
 
   return Coordinator.objects.get()

+ 2 - 3
apps/oozie/src/oozie/urls.py

@@ -30,7 +30,7 @@ urlpatterns = patterns(
   url(r'^delete_workflow/(?P<workflow>\d+)$', 'delete_workflow', name='delete_workflow'),
   url(r'^clone_workflow/(?P<workflow>\d+)$', 'clone_workflow', name='clone_workflow'),
   url(r'^submit_workflow/(?P<workflow>\d+)$', 'submit_workflow', name='submit_workflow'),
-  url(r'^resubmit_workflow/(?P<job_id>[-\w]+)$', 'resubmit_workflow', name='resubmit_workflow'),
+  url(r'^resubmit_workflow/(?P<oozie_wf_id>[-\w]+)$', 'resubmit_workflow', name='resubmit_workflow'),
 
   url(r'^new_action/(?P<workflow>\d+)/(?P<node_type>\w+)/(?P<parent_action_id>\d+)$', 'new_action', name='new_action'),
   url(r'^edit_action/(?P<action>\d+)$', 'edit_action', name='edit_action'),
@@ -48,9 +48,8 @@ urlpatterns = patterns(
   url(r'^create_coordinator_dataset/(?P<coordinator>[-\w]+)$', 'create_coordinator_dataset', name='create_coordinator_dataset'),
   url(r'^create_coordinator_data/(?P<coordinator>[-\w]+)/(?P<data_type>(input|output))$', 'create_coordinator_data', name='create_coordinator_data'),
   url(r'^submit_coordinator/(?P<coordinator>\d+)$', 'submit_coordinator', name='submit_coordinator'),
-  url(r'^resubmit_coordinator/(?P<job_id>[-\w]+)$', 'resubmit_coordinator', name='resubmit_coordinator'),
+  url(r'^resubmit_coordinator/(?P<oozie_coord_id>[-\w]+)$', 'resubmit_coordinator', name='resubmit_coordinator'),
 
-  url(r'^workflow_parameters/(?P<workflow>\d+)$', 'get_workflow_parameters', name='workflow_parameters'),
   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'),

+ 83 - 71
apps/oozie/src/oozie/views/editor.py

@@ -22,9 +22,10 @@ except ImportError:
 import logging
 
 
-from django.forms.models import inlineformset_factory, modelformset_factory
 from django.core.urlresolvers import reverse
 from django.db.models import Q
+from django.forms.formsets import formset_factory
+from django.forms.models import inlineformset_factory, modelformset_factory
 from django.http import HttpResponse
 from django.shortcuts import redirect
 from django.utils.functional import wraps
@@ -42,7 +43,7 @@ from oozie.models import Workflow, Node, Link, History, Coordinator,\
   Dataset, DataInput, DataOutput, Job, _STD_PROPERTIES_JSON
 from oozie.forms import NodeForm, WorkflowForm, CoordinatorForm, DatasetForm,\
   DataInputForm, DataInputSetForm, DataOutputForm, DataOutputSetForm, LinkForm,\
-  DefaultLinkForm, design_form_by_type
+  DefaultLinkForm, design_form_by_type, ParameterForm
 
 
 LOG = logging.getLogger(__name__)
@@ -253,6 +254,7 @@ def edit_workflow(request, workflow):
     'graph': graph,
     'history': history,
     'user_can_edit_job': user_can_edit_job,
+    'parameters': extract_field_data(workflow_form['parameters'])
   })
 
 
@@ -285,58 +287,59 @@ def clone_workflow(request, workflow):
 
 @check_job_access_permission
 def submit_workflow(request, workflow):
-  if request.method != 'POST':
-    raise PopupException(_('A POST request is required.'))
+  ParametersFormSet = formset_factory(ParameterForm, extra=0)
 
-  try:
-    mapping = dict(request.POST.iteritems())
-    job_id = _submit_workflow(request, workflow, mapping)
-  except RestException, ex:
-    raise PopupException(_("Error submitting workflow %s") % (workflow,),
-                         detail=ex._headers.get('oozie-error-message', ex))
-
-  request.info(_('Workflow submitted'))
-
-  return redirect(reverse('oozie:list_oozie_workflow', kwargs={'job_id': job_id}))
+  if request.method == 'POST':
+    params_form = ParametersFormSet(request.POST)
 
+    if params_form.is_valid():
+      mapping = dict([(param['name'], param['value']) for param in params_form.cleaned_data])
 
-def _submit_workflow(request, workflow, mapping):
-  submission = Submission(request.user, workflow, request.fs, mapping)
-  job_id = submission.run()
-  History.objects.create_from_submission(submission)
-  return job_id
+      job_id = _submit_workflow(request, workflow, mapping)
 
+      request.info(_('Workflow submitted'))
+      return redirect(reverse('oozie:list_oozie_workflow', kwargs={'job_id': job_id}))
+    else:
+      request.error(_('Invalid submission form: %s' % params_form.errors))
+  else:
+    parameters = workflow.find_all_parameters()
+    params_form = ParametersFormSet(initial=parameters)
 
-def resubmit_workflow(request, job_id):
-  if request.method != 'POST':
-    raise PopupException(_('A POST request is required.'))
+  popup = render('editor/submit_job_popup.mako', request, {
+                 'params_form': params_form,
+                 'action': reverse('oozie:submit_workflow', kwargs={'workflow': workflow.id})
+                 }, force_template=True).content
+  return HttpResponse(json.dumps(popup), mimetype="application/json")
 
-  history = History.objects.get(oozie_job_id=job_id)
 
-  can_access_job_or_exception(request, history.job.id)
 
+def _submit_workflow(request, workflow, mapping):
   try:
-    workflow = history.get_workflow().get_full_node()
-    properties = history.properties_dict
-    job_id = _submit_workflow(request, workflow, properties)
+    submission = Submission(request.user, workflow, request.fs, mapping)
+    job_id = submission.run()
+    History.objects.create_from_submission(submission)
+    return job_id
   except RestException, ex:
     raise PopupException(_("Error submitting workflow %s") % (workflow,),
                          detail=ex._headers.get('oozie-error-message', ex))
 
   request.info(_('Workflow submitted'))
-
   return redirect(reverse('oozie:list_oozie_workflow', kwargs={'job_id': job_id}))
 
-@check_job_access_permission
-def get_workflow_parameters(request, workflow):
-  """
-  Return the parameters found in the workflow as a JSON dictionary of {param_key : label}.
-  This expects an Ajax call.
-  """
-  params = workflow.find_parameters()
 
-  params_with_labels = dict((p, p.upper()) for p in params)
-  return render('dont_care_for_ajax', request, { 'params': params_with_labels })
+def resubmit_workflow(request, oozie_wf_id):
+  if request.method != 'POST':
+    raise PopupException(_('A POST request is required.'))
+
+  history = History.objects.get(oozie_job_id=oozie_wf_id)
+  can_access_job_or_exception(request, history.job.id)
+
+  workflow = history.get_workflow().get_full_node()
+  properties = history.properties_dict
+  job_id = _submit_workflow(request, workflow, properties)
+
+  request.info(_('Workflow re-submitted'))
+  return redirect(reverse('oozie:list_oozie_workflow', kwargs={'job_id': job_id}))
 
 
 @check_job_access_permission
@@ -561,7 +564,8 @@ def edit_coordinator(request, coordinator):
     'data_input_form': data_input_form,
     'data_output_form': data_output_form,
     'history': history,
-    'can_edit_coordinator': can_edit_job(request.user, coordinator.workflow)
+    'can_edit_coordinator': can_edit_job(request.user, coordinator.workflow),
+    'parameters': extract_field_data(coordinator_form['parameters'])
   })
 
 
@@ -647,57 +651,65 @@ def clone_coordinator(request, coordinator):
 
 @check_job_access_permission
 def submit_coordinator(request, coordinator):
-  if request.method != 'POST':
-    raise PopupException(_('A POST request is required.'))
+  ParametersFormSet = formset_factory(ParameterForm, extra=0)
 
-  try:
-    job_id = _submit_coordinator(request, coordinator, request.POST)
-  except RestException, ex:
-    raise PopupException(_("Error submitting coordinator %s") % (coordinator,),
-                         detail=ex._headers.get('oozie-error-message', ex))
+  if request.method == 'POST':
+    params_form = ParametersFormSet(request.POST)
 
-  request.info(_('Coordinator submitted'))
+    if params_form.is_valid():
+      mapping = dict([(param['name'], param['value']) for param in params_form.cleaned_data])
+      job_id = _submit_coordinator(request, coordinator, mapping)
 
-  return redirect(reverse('oozie:list_oozie_coordinator', kwargs={'job_id': job_id}))
+      request.info(_('Coordinator submitted'))
+      return redirect(reverse('oozie:list_oozie_coordinator', kwargs={'job_id': job_id}))
+    else:
+      request.error(_('Invalid submission form: %s' % params_form.errors))
+  else:
+    parameters = coordinator.find_all_parameters()
+    params_form = ParametersFormSet(initial=parameters)
+
+  popup = render('editor/submit_job_popup.mako', request, {
+                 'params_form': params_form,
+                 'action': reverse('oozie:submit_coordinator',  kwargs={'coordinator': coordinator.id})
+                }, force_template=True).content
+  return HttpResponse(json.dumps(popup), mimetype="application/json")
 
 
 def _submit_coordinator(request, coordinator, mapping):
-  if not coordinator.workflow.is_deployed(request.fs):
-    submission = Submission(request.user, coordinator.workflow, request.fs, mapping)
-    wf_dir = submission.deploy()
-    coordinator.workflow.deployment_dir = wf_dir
-    coordinator.workflow.save()
+  try:
+    if not coordinator.workflow.is_deployed(request.fs):
+      submission = Submission(request.user, coordinator.workflow, request.fs, mapping)
+      wf_dir = submission.deploy()
+      coordinator.workflow.deployment_dir = wf_dir
+      coordinator.workflow.save()
 
-  coordinator.deployment_dir = coordinator.workflow.deployment_dir
-  properties = {'wf_application_path': coordinator.workflow.deployment_dir}
-  properties.update(dict(request.POST.iteritems()))
+    coordinator.deployment_dir = coordinator.workflow.deployment_dir
+    properties = {'wf_application_path': request.fs.get_hdfs_path(coordinator.workflow.deployment_dir)}
+    properties.update(mapping)
 
-  submission = Submission(request.user, coordinator, request.fs, properties=properties)
-  job_id = submission.run()
+    submission = Submission(request.user, coordinator, request.fs, properties=properties)
+    job_id = submission.run()
 
-  History.objects.create_from_submission(submission)
+    History.objects.create_from_submission(submission)
 
-  return job_id
+    return job_id
+  except RestException, ex:
+    raise PopupException(_("Error submitting coordinator %s") % (coordinator,),
+                         detail=ex._headers.get('oozie-error-message', ex))
 
 
-def resubmit_coordinator(request, job_id):
+def resubmit_coordinator(request, oozie_coord_id):
   if request.method != 'POST':
     raise PopupException(_('A POST request is required.'))
 
-  history = History.objects.get(oozie_job_id=job_id)
-
+  history = History.objects.get(oozie_job_id=oozie_coord_id)
   can_access_job_or_exception(request, history.job.id)
 
-  try:
-    coordinator = history.get_coordinator().get_full_node()
-    properties = history.properties_dict
-    job_id = _submit_coordinator(request, coordinator, properties)
-  except RestException, ex:
-    raise PopupException(_("Error submitting coordinator %s") % (coordinator,),
-                         detail=ex._headers.get('oozie-error-message', ex))
-
-  request.info(_('Coordinator submitted'))
+  coordinator = history.get_coordinator().get_full_node()
+  properties = history.properties_dict
+  job_id = _submit_coordinator(request, coordinator, properties)
 
+  request.info(_('Coordinator re-submitted'))
   return redirect(reverse('oozie:list_oozie_coordinator', kwargs={'job_id': job_id}))
 
 

+ 1 - 1
desktop/core/src/desktop/lib/django_util.py

@@ -195,7 +195,7 @@ def render(template, request, data, json=None, template_lib=None, force_template
   template_lib), as well as as arbitrary data.
 
   It typically renders to an HttpResponse.  If the request is a non-JFrame
-  AJAX reqeust (or if data is None), it renders into JSON.
+  AJAX request (or if data is None), it renders into JSON.
 
   if force-template is True, will render the non-AJAX template response even if the
   request is via AJAX. This is to facilitate fetching HTML fragments.