Browse Source

[oozie] Prettify dataset add in coordinator editor

Updated input/output form to use growable field similar to "parameters" field
Created edit dataset modal window
abec 13 years ago
parent
commit
5fc85ba

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

@@ -174,6 +174,9 @@ class DataInputForm(forms.ModelForm):
   class Meta:
     model = DataInput
     exclude = ('coordinator')
+    widgets = {
+      'dataset': forms.Select(attrs={'data-bind': 'value: dataset, uniqueName: false'})
+    }
 
   def __init__(self, *args, **kwargs):
     coordinator = kwargs['coordinator']
@@ -181,7 +184,8 @@ class DataInputForm(forms.ModelForm):
     super(DataInputForm, self).__init__(*args, **kwargs)
     self.fields['dataset'].queryset = Dataset.objects.filter(coordinator=coordinator)
     if coordinator.workflow:
-      self.fields['name'].widget = forms.Select(choices=((param, param) for param in set(coordinator.workflow.find_parameters())))
+      attrs = {'data-bind': 'value: name, uniqueName: false'}
+      self.fields['name'].widget = forms.Select(choices=((param, param) for param in set(coordinator.workflow.find_parameters())), attrs=attrs)
 
 
 class DataOutputSetForm(forms.ModelForm):

+ 91 - 12
apps/oozie/src/oozie/templates/editor/create_coordinator_data.mako

@@ -15,20 +15,99 @@
 ## limitations under the License.
 
 <%!
-  from django.utils.translation import ugettext as _
+import re
+
+from django.utils.translation import ugettext as _
 %>
 
+<%namespace name="utils" file="../../utils.inc.mako" />
+
+
+<%def name="print_datasets(label, element, formset, direction)">
+  <table id="${element}" class="table-condensed designTable">
+    <thead>
+      <tr>
+        <th>${ _('Name') }</th>
+        <th>${ _('Dataset') }</th>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody data-bind="foreach: ${ element }">
+      % if 'forms' in formset.__dict__:
+        % for form in formset.forms:
+          <tr>
+            <td>${ form['name'] }</td>
+            <td>${ form['dataset'] }</td>
+            <td><a class="btn btn-small" href="#" data-bind="click: $root.remove_${ element }">${ _('Delete') }</a></td>
+          </tr>
+        % endfor
+        <tr>
+          <td>${ formset.empty_form['name'] }</td>
+          <td>${ formset.empty_form['dataset'] }</td>
+          <td><a class="btn btn-small" href="#" data-bind="click: $root.remove_${ element }">${ _('Delete') }</a></td>
+        </tr>
+      % else:
+        <tr>
+          <td>${ formset['name'] }</td>
+          <td>${ formset['dataset'] }</td>
+          <td><a class="btn btn-small" href="#" data-bind="click: $root.remove_${ element }">${ _('Delete') }</a></td>
+        </tr>
+      % endif
+    </tbody>
+  </table>
+  % if 'forms' in formset.__dict__:
+    % for form in formset.forms:
+      % if form.errors:
+        <div class="row">
+          <div class="alert alert-error">
+            ${ unicode(form.errors) | n }
+          </div>
+        </div>
+      % endif
+    % endfor
+  % else:
+    % if formset.errors:
+      <div class="row">
+        <div class="alert alert-error">
+          ${ unicode(formset.errors) | n }
+        </div>
+      </div>
+    % endif
+  % endif
+
+  <button type="button" class="btn" data-bind="click: add_${ element }">${ _('Add') }</button>
+
+  <style>
+    .designTable th {
+      text-align:left;
+    }
+  </style>
+
+  <script type="text/javascript">
+    $(document).ready(function(){
+      window.viewModel.${element} = ko.observableArray([]);
+
+      window.viewModel.add_${element} = function() {
+        window.viewModel.${element}.push({name: "", dataset: ""});
+      };
 
-<%page args="form"/>
+      window.viewModel.remove_${element} = function(val) {
+        window.viewModel.${element}.remove(val);
+      };
 
+      var previousSubmit = window.viewModel.submit;
+      window.viewModel.submit = function(form) {
+        $.post("${ url('oozie:create_coordinator_data', coordinator=coordinator.id, data_type='%s' % direction) }",
+          $("#${element} :input").serialize(), function(response) {
+            if (response['status'] != 0) {
+              $.jHueNotify.error("${ _('Problem: ') }" + response['data']);
+            } else {
+              window.location.replace(response['data']);
+            }
+        });
 
-<div class="container-fluid">
-<div class="row-fluid">
-  <div class="span9" style="align:left">
-    ${ form }
-  </div>
-  <div class="span3">
-    ${ _('No Dataset?') } <a class="btn" data-toggle="modal" href="#add-dataset-modal">${ _('Add one') }</a>
-  </div>
-</div>
-</div>
+        previousSubmit(form);
+      };
+    });
+  </script>
+</%def>

+ 34 - 74
apps/oozie/src/oozie/templates/editor/edit_coordinator.mako

@@ -22,6 +22,7 @@
 <%namespace name="layout" file="../navigation-bar.mako" />
 <%namespace name="utils" file="../utils.inc.mako" />
 <%namespace name="properties" file="job_action_properties.mako" />
+<%namespace name="coordinator_data" file="create_coordinator_data.mako" />
 
 ${ commonheader(_("Oozie App"), "oozie", "100px") }
 ${ layout.menubar(section='coordinators') }
@@ -139,16 +140,16 @@ ${ layout.menubar(section='coordinators') }
                   ${ _('No inputs') }
                 </div>
               % endif
-             % if can_edit_coordinator:
-               <a class="btn" data-toggle="modal" href="#add-data-input-modal">${ _('Add') }</a>
-             % endif
+
+              % if can_edit_coordinator:
+                ${ coordinator_data.print_datasets(_('Datasets'), 'dataset_input', data_input_form, 'input') }
+              % endif
             </div>
 
             <br/>
 
             <div class="row-fluid">
               <h3>${ _('Outputs') }</h3>
-
               % if data_output_formset.forms:
               <table class="table table-striped table-condensed" cellpadding="0" cellspacing="0">
                 <thead>
@@ -183,7 +184,7 @@ ${ layout.menubar(section='coordinators') }
               % endif
 
               % if can_edit_coordinator:
-                <a class="btn" data-toggle="modal" href="#add-data-output-modal">${ _('Add') }</a>
+                ${ coordinator_data.print_datasets(_('Datasets'), 'dataset_output', data_output_form, 'output') }
               % endif
             </div>
           % endif
@@ -216,7 +217,6 @@ ${ layout.menubar(section='coordinators') }
                 <table class="table table-striped table-condensed" cellpadding="0" cellspacing="0">
                   <thead>
                     <tr>
-                      <th>${ _('Pick dataset as input/output') }</th>
                       <th>${ _('Name') }</th>
                       <th>${ _('Description') }</th>
                       <th>${ _('Frequency') }</th>
@@ -237,11 +237,10 @@ ${ layout.menubar(section='coordinators') }
                     <tr>
                       <td>
                         % if can_edit_coordinator:
-                          <a class="btn" data-toggle="modal" href="#add-data-input-modal">${ _('input') }</a>
-                          <a class="btn" data-toggle="modal" href="#add-data-output-modal">${ _('output') }</a>
+                          <a href="javascript:modalRequest('${ url('oozie:edit_coordinator_dataset', dataset=form.instance.id) }', '#edit-dataset-modal');" data-row-selector="true"/>
                         % endif
+                        ${ form.instance.name }
                       </td>
-                      <td>${ form.instance.name }</td>
                       <td>${ form.instance.description }</td>
                       <td>${ form.instance.text_frequency }</td>
                       <td>${ form.instance.start }</td>
@@ -329,40 +328,8 @@ ${ layout.menubar(section='coordinators') }
     </form>
   </div>
 
-  <div class="modal hide" id="add-data-input-modal">
-    <form class="form-horizontal" id="add-data-input-form">
-        <div class="modal-header">
-          <button class="close" data-dismiss="modal">&times;</button>
-          <h3>${ _('Create a data input') }</h3>
-        </div>
-
-        <div class="modal-body" id="add-data-input-body">
-            <%include file="create_coordinator_data.mako" args="form=data_input_form"/>
-        </div>
+  <div class="modal hide" id="edit-dataset-modal" style="z-index:1500;width:850px">
 
-        <div class="modal-footer">
-          <a href="#" class="btn" data-dismiss="modal">${ _('Close') }</a>
-          <a href="#" class="btn btn-primary" id="add-data-input-btn">${ _('Add data input') }</a>
-        </div>
-    </form>
-  </div>
-
-  <div class="modal hide" id="add-data-output-modal">
-    <form class="form-horizontal" id="add-data-output-form">
-        <div class="modal-header">
-          <button class="close" data-dismiss="modal">&times;</button>
-          <h3>${ _('Create a data output') }</h3>
-        </div>
-
-        <div class="modal-body" id="add-data-output-body">
-            <%include file="create_coordinator_data.mako" args="form=data_output_form"/>
-        </div>
-
-        <div class="modal-footer">
-          <a href="#" class="btn" data-dismiss="modal">${ _('Close') }</a>
-          <a href="#" class="btn btn-primary" id="add-data-output-btn">${ _('Add data output') }</a>
-        </div>
-    </form>
   </div>
 </div>
 
@@ -374,13 +341,30 @@ ${ layout.menubar(section='coordinators') }
 <script src="/static/ext/js/knockout-2.0.0.js" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
+  var timeOptions = {
+    show24Hours: false,
+    startTime: '00:00',
+    endTime: '23:59',
+    step: 60
+  };
+
+  function modalRequest(url, el) {
+    $.ajax({
+         url: url,
+         beforeSend: function(xhr){
+             xhr.setRequestHeader("X-Requested-With", "Hue");
+         },
+         dataType: "html",
+         success: function(data){
+             $(el).html(data);
+             $(el).modal("show");
+             $("input.date").datepicker();
+             $("input.time").timePicker(timeOptions);
+         }
+     });
+  }
+
   $(document).ready(function() {
-    var timeOptions = {
-      show24Hours: false,
-      startTime: '00:00',
-      endTime: '23:59',
-      step: 60
-    };
     $("input.date").datepicker();
     $("input.time").timePicker(timeOptions);
 
@@ -401,33 +385,9 @@ ${ layout.menubar(section='coordinators') }
       );
    });
 
-  $('#add-data-input-btn').click(function() {
-    $.post("${ url('oozie:create_coordinator_data', coordinator=coordinator.id, data_type='input') }",
-      $("#add-data-input-form").serialize(),
-        function(response) {
-          if (response['status'] != 0) {
-            $('#add-data-input-body').html(response['data']);
-          } else {
-            window.location.replace(response['data']);
-          }
-        }
-      );
-   });
-
-  $('#add-data-output-btn').click(function() {
-    $.post("${ url('oozie:create_coordinator_data', coordinator=coordinator.id, data_type='output') }",
-      $("#add-data-output-form").serialize(),
-        function(response) {
-          if (response['status'] != 0) {
-            $('#add-data-output-body').html(response['data']);
-          } else {
-            window.location.replace(response['data']);
-          }
-        }
-      );
-   });
-
    $("a[data-row-selector='true']").jHueRowSelector();
+
+   ko.applyBindings(window.viewModel)
  });
 </script>
 

+ 59 - 0
apps/oozie/src/oozie/templates/editor/edit_coordinator_dataset.mako

@@ -0,0 +1,59 @@
+## 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 class="form-horizontal" id="edit-dataset-form" method="POST" action="${path}">
+  <div class="modal-header">
+    <button class="close" data-dismiss="modal">&times;</button>
+    <h3>${ _('Edit dataset') }</h3>
+  </div>
+
+  <div class="modal-body" id="edit-dataset-body">
+    <fieldset>
+      ${ utils.render_field(dataset_form['name']) }
+      ${ utils.render_field(dataset_form['description']) }
+      ${ utils.render_field(dataset_form['start']) }
+      <div class="row-fluid">
+        <div class="span6">
+          ${ utils.render_field(dataset_form['frequency_number']) }
+        </div>
+        <div class="span6">
+          ${ utils.render_field(dataset_form['frequency_unit']) }
+        </div>
+      </div>
+
+      <div class="alert alert-info">
+        ${ _('You can parameterize the values using') } <code>${"${"}MINUTE}</code>, <code>${"${"}DAY}</code>, <code>${"${"}MONTH}</code> ${ _('and') } <code>${"${"}YEAR}</code>.
+      </div>
+      ${ utils.render_field(dataset_form['uri']) }
+
+      ${ utils.render_field(dataset_form['timezone']) }
+      ${ utils.render_field(dataset_form['done_flag']) }
+    </fieldset>
+  </div>
+
+  <div class="modal-footer">
+    <a href="#" class="btn" data-dismiss="modal">${ _('Close') }</a>
+    <button type="submit" class="btn btn-primary" id="edit-dataset-btn">${ _('Save') }</a>
+  </div>
+</form>

+ 2 - 3
apps/oozie/src/oozie/templates/editor/job_action_properties.mako

@@ -22,7 +22,7 @@ from django.utils.translation import ugettext as _
 
 
 <%def name="print_key_value(label, element, form, initial_parameters)">
-  <div class="control-group">
+  <div class="control-group ko-${element}">
     <label class="control-label">${ label }</label>
     <div class="controls">
       <table class="table-condensed designTable" data-bind="visible: ${ element }().length > 0">
@@ -85,8 +85,7 @@ from django.utils.translation import ugettext as _
         };
       };
 
-      var viewModel = new ViewModel(${ initial_parameters });
-      ko.applyBindings(viewModel);
+      window.viewModel = new ViewModel(${ initial_parameters });
     });
   </script>
 </%def>

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

@@ -47,6 +47,7 @@ urlpatterns = patterns(
   url(r'^delete_coordinator/(?P<coordinator>\d+)$', 'delete_coordinator', name='delete_coordinator'),
   url(r'^clone_coordinator/(?P<coordinator>\d+)$', 'clone_coordinator', name='clone_coordinator'),
   url(r'^create_coordinator_dataset/(?P<coordinator>[-\w]+)$', 'create_coordinator_dataset', name='create_coordinator_dataset'),
+  url(r'^edit_coordinator_dataset/(?P<dataset>\d+)$', 'edit_coordinator_dataset', name='edit_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<oozie_coord_id>[-\w]+)$', 'resubmit_coordinator', name='resubmit_coordinator'),

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

@@ -178,6 +178,61 @@ def check_action_edition_permission(view_func):
   return wraps(view_func)(decorate)
 
 
+def can_access_dataset_or_exception(request, dataset_id):
+  if dataset_id is None:
+    return
+  try:
+    dataset = Dataset.objects.get(pk=dataset_id)
+    if can_access_job(request.user, dataset.coordinator):
+      return dataset
+    else:
+      message = _("Permission denied. %(username)s don't have the permissions to access dataset %(id)s") % \
+          {'username': request.user.username, 'id': dataset.id}
+      access_warn(request, message)
+      request.error(message)
+      raise PopupException(message)
+
+  except Dataset.DoesNotExist:
+    raise PopupException(_('dataset %(id)s not exist') % {'id': dataset_id})
+
+
+def check_dataset_edition_permission(authorize_get=False):
+  """
+  Decorator ensuring that the user has the permissions to modify a dataset.
+  A dataset can be edited if the coordinator that owns the dataset can be edited.
+
+  Need to appear below @check_dataset_access_permission
+  """
+  def inner(view_func):
+    def decorate(request, *args, **kwargs):
+      dataset = kwargs.get('dataset')
+      if dataset is not None and not (authorize_get and request.method == 'GET'):
+        can_edit_job_or_exception(request, dataset.coordinator)
+
+      return view_func(request, *args, **kwargs)
+    return wraps(view_func)(decorate)
+  return inner
+
+
+def check_dataset_access_permission(view_func):
+  """
+  Decorator ensuring that the user has access to dataset.
+
+  Arg: 'dataset'.
+  Return: the dataset or raise an exception
+
+  Notice: its gets an id in input and returns the full object in output (not an id).
+  """
+  def decorate(request, *args, **kwargs):
+    dataset = kwargs.get('dataset')
+    if dataset is not None:
+      dataset = can_access_dataset_or_exception(request, dataset)
+    kwargs['dataset'] = dataset
+
+    return view_func(request, *args, **kwargs)
+  return wraps(view_func)(decorate)
+
+
 def list_workflows(request, job_type='workflow'):
   show_setup_app = True
 
@@ -643,6 +698,30 @@ def create_coordinator_dataset(request, coordinator):
   return HttpResponse(json.dumps(response), mimetype="application/json")
 
 
+@check_dataset_access_permission
+@check_dataset_edition_permission()
+def edit_coordinator_dataset(request, dataset):
+  """Returns HTML for modal to edit datasets"""
+
+  if request.method == 'POST':
+    dataset_form = DatasetForm(request.POST, instance=dataset)
+
+    if dataset_form.is_valid():
+      dataset_form.save()
+      request.info(_('Dataset modified'));
+      return redirect(reverse('oozie:edit_coordinator', kwargs={'coordinator': dataset.coordinator.id}))
+    else:
+      dataset_form = DatasetForm(request.POST, instance=dataset)
+  else:
+    dataset_form = DatasetForm(instance=dataset)
+
+  return render('editor/edit_coordinator_dataset.mako', request, {
+    'coordinator': dataset.coordinator,
+    'dataset_form': dataset_form,
+    'path': request.path,
+  }, force_template=True)
+
+
 @check_job_access_permission
 @check_job_edition_permission()
 def create_coordinator_data(request, coordinator, data_type):