Преглед изворни кода

HUE-1381 [sqoop2] support for mapped type

Abraham Elmahrek пре 12 година
родитељ
комит
cb3de6a

+ 3 - 0
apps/sqoop/src/sqoop/client/form.py

@@ -59,6 +59,9 @@ class Form(object):
           "id": 5,
           "name": "connection.jdbcProperties",
           "type": "MAP",
+          "value": {
+            "key": "value"
+          },
           "sensitive": false
         }
       ],

+ 72 - 20
apps/sqoop/src/sqoop/templates/app.mako

@@ -382,25 +382,51 @@ ${ commonheader(None, "sqoop", user, "40px") | n,unicode }
 </fieldset>
 </script>
 
-<script type="text/html" id="framework-enum">
-<div data-bind="css:{'control-group': id() != null, warning: name() in $root.warnings(), error: name() in $root.errors()}">
+<script type="text/html" id="framework-map">
+<div data-bind="css: {
+                  warning: name() in $root.warnings(),
+                  error: name() in $root.errors()
+                }" class="control-group">
   <label class="control-label" data-bind="text: $root.label('framework', name())"></label>
   <div class="controls">
-    <select class="input-xlarge" data-bind="'options': values, 'value': value, 'optionsCaption': '${ _('Choose...') }', 'attr': { 'name': name, 'title': $root.help('framework', name())}" rel="tooltip">
-    </select>
-    <span data-bind="template: {'name': 'job-editor-form-field-error'}" class="help-inline"></span>
+
+    <table data-bind="visible: value() && value().length > 0" style="margin-bottom: 4px">
+      <thead>
+        <tr>
+          <th>${ _('Property name') }</th>
+          <th>${ _('Value') }</th>
+          <th/>
+        </tr>
+      </thead>
+      <tbody data-bind="foreach: value()">
+        <tr>
+          <td>
+            <input data-bind="'value': key,
+                              'attr': {
+                                'title': $root.help('framework', $parent.name())
+                              }" type="text" class="span12 required propKey" />
+          </td>
+          <td>
+            <input data-bind="value: value" type="text" class="span12 required" />
+          </td>
+          <td>
+            <a data-bind="click: $parent.removeFromMap.bind($parent, [$index()])" class="btn" href="javascript:void(0);">${ _('Delete') }</a>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+
+    <a data-bind="click: $data.addToMap.bind($data, [])" href="javascript:void(0);" class="btn">${_('Add')}</a>
   </div>
 </div>
 </script>
 
-<script type="text/html" id="framework-map">
-<div data-bind="css: {
-                  warning: name() in $root.warnings(),
-                  error: name() in $root.errors()
-                }" class="control-group">
-  <label class="control-label" data-bind="text: $root.label('framework', name())" rel="tooltip"></label>
+<script type="text/html" id="framework-enum">
+<div data-bind="css:{'control-group': id() != null, warning: name() in $root.warnings(), error: name() in $root.errors()}">
+  <label class="control-label" data-bind="text: $root.label('framework', name())"></label>
   <div class="controls">
-    <input class="input-xlarge" data-bind="value: value, attr: {'type': (sensitive() ? 'password' : 'text'), 'name': name, 'title': $root.help('framework', name())}" rel="tooltip">
+    <select class="input-xlarge" data-bind="'options': values, 'value': value, 'optionsCaption': '${ _('Choose...') }', 'attr': { 'name': name, 'title': $root.help('framework', name())}" rel="tooltip">
+    </select>
     <span data-bind="template: {'name': 'job-editor-form-field-error'}" class="help-inline"></span>
   </div>
 </div>
@@ -450,28 +476,54 @@ ${ commonheader(None, "sqoop", user, "40px") | n,unicode }
 </div>
 </script>
 
-<script type="text/html" id="connector-enum">
+<script type="text/html" id="connector-map">
 <div data-bind="css: {
                   warning: name() in $root.warnings(),
                   error: name() in $root.errors()
                 }" class="control-group">
-  <label class="control-label" data-bind="text: $root.label('connector', name())" rel="tooltip"></label>
+  <label class="control-label" data-bind="text: $root.label('connector', name())"></label>
   <div class="controls">
-    <select class="input-xlarge" data-bind="'options': values, 'value': value, 'optionsCaption': '${ _('Choose...') }', attr: { 'name': name, 'title': $root.help('connector', name()) }" rel="tooltip">
-    </select>
-    <span data-bind="template: { 'name': 'job-editor-form-field-error' }" class="help-inline"></span>
+
+    <table data-bind="visible: value() && value().length > 0" style="margin-bottom: 4px">
+      <thead>
+        <tr>
+          <th>${ _('Property name') }</th>
+          <th>${ _('Value') }</th>
+          <th/>
+        </tr>
+      </thead>
+      <tbody data-bind="foreach: value()">
+        <tr>
+          <td>
+            <input data-bind="'value': key,
+                              'attr': {
+                                'title': $root.help('connector', $parent.name())
+                              }" type="text" class="span12 required propKey" />
+          </td>
+          <td>
+            <input data-bind="value: value" type="text" class="span12 required" />
+          </td>
+          <td>
+            <a data-bind="click: $parent.removeFromMap.bind($parent, [$index()])" class="btn" href="javascript:void(0);">${ _('Delete') }</a>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+
+    <a data-bind="click: $data.addToMap.bind($data, [])" href="javascript:void(0);" class="btn">${_('Add')}</a>
   </div>
 </div>
 </script>
 
-<script type="text/html" id="connector-map">
+<script type="text/html" id="connector-enum">
 <div data-bind="css: {
                   warning: name() in $root.warnings(),
                   error: name() in $root.errors()
                 }" class="control-group">
   <label class="control-label" data-bind="text: $root.label('connector', name())" rel="tooltip"></label>
   <div class="controls">
-    <input class="input-xlarge" data-bind="value: value, attr: { 'type': (sensitive() ? 'password' : 'text'), 'name': name, 'title': $root.help('connector', name()) }" rel="tooltip">
+    <select class="input-xlarge" data-bind="'options': values, 'value': value, 'optionsCaption': '${ _('Choose...') }', attr: { 'name': name, 'title': $root.help('connector', name()) }" rel="tooltip">
+    </select>
     <span data-bind="template: { 'name': 'job-editor-form-field-error' }" class="help-inline"></span>
   </div>
 </div>
@@ -532,7 +584,7 @@ ${ commonheader(None, "sqoop", user, "40px") | n,unicode }
 <script src="/sqoop/static/js/koify.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.utils.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.wizard.js" type="text/javascript" charset="utf-8"></script>
-<script src="/sqoop/static/js/sqoop.models.js" type="text/javascript" charset="utf-8"></script>
+<script src="/sqoop/static/js/sqoop.forms.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.framework.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.connectors.js" type="text/javascript" charset="utf-8"></script>
 <script src="/sqoop/static/js/sqoop.connections.js" type="text/javascript" charset="utf-8"></script>

+ 13 - 1
apps/sqoop/static/js/cclass.js

@@ -15,7 +15,10 @@
 // limitations under the License.
 
 /**
- * cclass is a javascript Class inheritance implementation
+ * cclass is a javascript Class inheritance implementation.
+ * Methods and class members should be in the second argument.
+ * This ensures that they end up in the prototype of the function
+ * and makes them available to each new object.
  *
  * Example Usage:
  * var A = cclass.create(function() {
@@ -41,11 +44,20 @@
 var cclass = (function($, undefined) {
   function extend(ext_fn, attrs) {
     function parent() {};
+    // Need a function to create an object from.
+    // The 'fn' function will use 'ext_fn' as its initializer.
+    // We don't use 'ext_fn' itself since we modify the function extensively.
     function fn() {
       ext_fn.apply(this, arguments);
     };
+    // 'this' should be the caller.
+    // Should be a 'cclass' object.
     parent.prototype = this.prototype;
+    // Create a 'class' members region via prototype chain.
+    // If a method is not part of the object, it will check its
+    // prototype chain.
     fn.prototype = new parent();
+    // The constructor should be the copy of 'ext_fn'.
     fn.prototype.constructor = fn;
     fn.extend = extend;
     $.extend(fn.prototype, attrs || {}, {

+ 28 - 19
apps/sqoop/static/js/koify.js

@@ -45,37 +45,45 @@ var koify = (function($, undefined) {
   });
 
 
-  var Node = KOClass(function(options) {
+  var MinimalNode = KOClass(function(options) {
     var self = this;
     var options = options || {};
 
     self.options = options;
     self.initialize(options);
   }, {
-    persist: true,
-    identifier: undefined,
-    modelClass: undefined,
+    model_class: undefined,
 
     initialize: function(options) {
       var self = this;
 
       if (options.modelDict) {
-        self.model = new self.modelClass(options.modelDict);
+        self.model = new self.model_class(options.modelDict);
       } else {
-        self.model = $.extend(true, options.model, {});
+        self.model = $.extend(true, {}, options.model);
       }
 
+      self.map();
+    },
+    map: function() {
+      var self = this;
+      var mapping_options = {
+        'ignore': ['parent', 'initialize']
+      };
       if ('__ko_mapping__' in self) {
-        ko.mapping.fromJS(self.model, {
-          'ignore': ['parent', 'initialize']
-        }, self);
+        ko.mapping.fromJS(self.model, mapping_options, self);
       } else {
-        var mapped = ko.mapping.fromJS(self.model, {
-          'ignore': ['parent', 'initialize']
-        });
+        var mapped = ko.mapping.fromJS(self.model, mapping_options);
         $.extend(self, mapped);
       }
-    },
+    }
+  });
+
+
+  var Node = MinimalNode.extend({
+    persist: true,
+    identifier: undefined,
+
     request: function(url, options) {
       var self = this;
       var request = $.extend({
@@ -178,8 +186,9 @@ var koify = (function($, undefined) {
     },
     getData: function() {
       var self = this;
-      data = {};
-      data[self.identifier] = ko.mapping.toJSON(self);
+      var model = ko.sqoop.fixModel(self);
+      var data = {};
+      data[self.identifier] = ko.utils.stringifyJson(model);
       return data;
     },
     loadUrl: function(persist) {
@@ -204,10 +213,10 @@ var koify = (function($, undefined) {
     }
   });
 
-
   return {
-    KOClass: KOClass,
-    Node: Node,
-    Model: Model
+    'KOClass': KOClass,
+    'MinimalNode': MinimalNode,
+    'Node': Node,
+    'Model': Model
   }
 })($, undefined);

+ 19 - 7
apps/sqoop/static/js/sqoop.connections.js

@@ -26,24 +26,24 @@ var connections = (function($) {
     'framework': [],
     'initialize': function(attrs) {
       var self = this;
-      var attrs = $.extend(true, attrs, {});
-      attrs = transform_keys(attrs, {
+      var _attrs = $.extend(true, {}, attrs);
+      _attrs = transform_keys(_attrs, {
         'connector-id': 'connector_id'
       });
-      attrs = transform_values(attrs, {
+      _attrs = transform_values(_attrs, {
         'connector': to_forms,
         'framework': to_forms
       });
-      return attrs;
+      return _attrs;
     }
   });
 
   var Connection = koify.Node.extend({
     'identifier': 'connection',
     'persists': true,
-    'modelClass': ConnectionModel,
+    'model_class': ConnectionModel,
     'base_url': '/sqoop/api/connections/',
-    'initialize': function() {
+    'initialize': function(options) {
       var self = this;
       self.parent.initialize.apply(self, arguments);
       self.selected = ko.observable();
@@ -63,7 +63,19 @@ var connections = (function($) {
         });
         return connection_string;
       });
-    }
+    },
+    'map': function() {
+      var self = this;
+      var mapping_options = $.extend(true, {
+        'ignore': ['parent', 'initialize']
+      }, forms.MapProperties);
+      if ('__ko_mapping__' in self) {
+        ko.mapping.fromJS(self.model, mapping_options, self);
+      } else {
+        var mapped = ko.mapping.fromJS(self.model, mapping_options);
+        $.extend(self, mapped);
+      }
+    },
   });
 
   function fetch_connections(options) {

+ 17 - 5
apps/sqoop/static/js/sqoop.connectors.js

@@ -29,12 +29,12 @@ var connectors = (function($) {
     'resources': {},
     'initialize': function(attrs) {
       var self = this;
-      var attrs = $.extend(true, attrs, {});
-      attrs = transform_keys(attrs, {
+      var _attrs = $.extend(true, {}, attrs);
+      _attrs = transform_keys(_attrs, {
         'job-forms': 'job_forms',
         'con-forms': 'con_forms'
       });
-      attrs = transform_values(attrs, {
+      _attrs = transform_values(_attrs, {
         'con_forms': to_forms,
         'job_forms': function(key, value) {
           transform_values(value, {
@@ -44,19 +44,31 @@ var connectors = (function($) {
           return value;
         }
       });
-      return attrs;
+      return _attrs;
     }
   });
 
   var Connector = koify.Node.extend({
     'identifier': 'connector',
     'persist': false,
-    'modelClass': ConnectorModel,
+    'model_class': ConnectorModel,
     'base_url': '/sqoop/api/connectors/',
     'initialize': function() {
       var self = this;
       self.parent.initialize.apply(self, arguments);
       self.selected = ko.observable();
+    },
+    map: function() {
+      var self = this;
+      var mapping_options = $.extend(true, {
+        'ignore': ['parent', 'initialize']
+      }, forms.MapProperties);
+      if ('__ko_mapping__' in self) {
+        ko.mapping.fromJS(self.model, mapping_options, self);
+      } else {
+        var mapped = ko.mapping.fromJS(self.model, mapping_options);
+        $.extend(self, mapped);
+      }
     }
   });
 

+ 235 - 0
apps/sqoop/static/js/sqoop.forms.js

@@ -0,0 +1,235 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+function transform_keys(model, keys_dict) {
+  $.each(keys_dict, function(key, new_key) {
+    if (key in model) {
+      model[new_key] = model[key];
+      delete model[key];
+    }
+  });
+  return model;
+}
+
+function transform_values(model, func_dict) {
+  $.each(func_dict, function(key, f) {
+    if (key in model) {
+      model[key] = f(key, model[key]);
+    }
+  });
+  return model;
+}
+
+function to_form(value) {
+  return new forms.FormModel(value);
+}
+
+function to_forms(key, value) {
+  $.each(value, function(index, form_dict) {
+    value[index] = to_form(form_dict);
+  });
+  return value;
+}
+
+function to_input(value) {
+  if (value.type.toLowerCase() == 'map') {
+    return new forms.MapInputModel(value);
+  } else {
+    return new forms.InputModel(value);
+  }
+}
+
+function to_inputs(key, value) {
+  $.each(value, function(index, input_dict) {
+    value[index] = to_input(input_dict);
+  });
+  return value;
+}
+
+
+var forms = (function($) {
+  var map_form_properties = {
+    'create': function(options) {
+      return new SqoopForm({modelDict: options.data});
+    },
+    'update': function(options) {
+      options.target.initialize({modelDict: options.data})
+      return options.target;
+    },
+  };
+  var map_input_properties = {
+    'create': function(options) {
+      switch(options.data.type.toLowerCase()) {
+        case 'map':
+        return new SqoopMapInput({modelDict: options.data});
+
+        default:
+        return new SqoopInput({modelDict: options.data});
+      }
+    },
+    'update': function(options) {
+      options.target.initialize({modelDict: options.data})
+      return options.target;
+    },
+  };
+  var map_properties = {
+    'connector': map_form_properties,
+    'framework': map_form_properties,
+    'con-forms': map_form_properties,
+    'job-forms': map_form_properties,
+    'inputs': map_input_properties
+  };
+
+  var FormModel = koify.Model.extend({
+    'id': -1,
+    'inputs': [],
+    'name': null,
+    'type': null,
+    'initialize': function(attrs) {
+      var self = this;
+      attrs = transform_values(attrs, {
+        'inputs': to_inputs
+      });
+      return attrs;
+    }
+  });
+
+  var InputModel = koify.Model.extend({
+    'id': -1,
+    'name': null,
+    'type': null,
+    'size': -1,
+    'sensitive': false,
+    'values': null,
+    'value': "",
+    'initialize': function(attrs) {
+      var self = this;
+      var attrs = $.extend(true, {}, attrs);
+      if ('values' in attrs && attrs['values']) {
+        attrs['values'] = ($.isArray(attrs['values'])) ? attrs['values'] : attrs['values'].split(',');
+      }
+      if ('value' in attrs) {
+        attrs.value = decodeURIComponent(attrs.value);
+      }
+      return attrs;
+    }
+  });
+
+  var MapInputModel = InputModel.extend({
+    'value': {},
+    'initialize': function(attrs) {
+      var self = this;
+
+      if ('value' in attrs && attrs.value) {
+        if (!$.isArray(attrs.value)) {
+          var map = attrs['value'];
+          attrs.value = [];
+          $.each(map, function(key, value) {
+            attrs.value.push({
+              'key': key,
+              'value': value
+            });
+          });
+        }
+      } else {
+        attrs.value = [];
+      }
+      return attrs;
+    }
+  });
+
+  // Form is reserved word
+  var SqoopForm = koify.MinimalNode.extend({
+    'model_class': FormModel,
+    'map': function() {
+      var self = this;
+      var mapping_options = $.extend(true, {
+        'ignore': ['parent', 'initialize']
+      }, map_properties);
+      if ('__ko_mapping__' in self) {
+        ko.mapping.fromJS(self.model, mapping_options, self);
+      } else {
+        var mapped = ko.mapping.fromJS(self.model, mapping_options);
+        $.extend(self, mapped);
+      }
+    }
+  });
+
+  var SqoopInput = koify.MinimalNode.extend({
+    'model_class': InputModel,
+    'map': function() {
+      var self = this;
+      var mapping_options = {
+        'ignore': ['parent', 'initialize']
+      };
+      if ('__ko_mapping__' in self) {
+        ko.mapping.fromJS(self.model, mapping_options, self);
+      } else {
+        var mapped = ko.mapping.fromJS(self.model, mapping_options);
+        $.extend(self, mapped);
+      }
+    }
+  });
+
+  var SqoopMapInput = SqoopInput.extend({
+    'model_class': MapInputModel,
+    'initialize': function() {
+      var self = this;
+      self.parent.initialize.apply(self, arguments);
+
+      self.addToMap = function() {
+        var value = {
+          'key': ko.observable(""),
+          'value': ko.observable("")
+        };
+        value.key.subscribe(function() {
+          self.value.valueHasMutated();
+        });
+        value.value.subscribe(function() {
+          self.value.valueHasMutated();
+        });
+        self.value().push(value);
+        self.value.valueHasMutated();
+      };
+
+      self.removeFromMap = function(index) {
+        self.value().splice(index, 1);
+        self.value.valueHasMutated();
+      };
+    },
+    fixModel: function(model) {
+      var map = {};
+      $.each(model.value, function(index, obj) {
+        if (obj.key) {
+          map[obj.key] = obj.value;
+        }
+      });
+      model.value = map;
+      return model;
+    }
+  });
+
+  return {
+    'FormModel': FormModel,
+    'InputModel': InputModel,
+    'MapInputModel': MapInputModel,
+    'Form': SqoopForm,
+    'Input': SqoopInput,
+    'MapInput': SqoopMapInput,
+    'MapProperties': map_properties
+  };
+})(jQuery);

+ 18 - 5
apps/sqoop/static/js/sqoop.framework.js

@@ -27,12 +27,12 @@ var framework = (function($) {
     'resources': {},
     'initialize': function(attrs) {
       var self = this;
-      var attrs = $.extend(true, attrs, {});
-      attrs = transform_keys(attrs, {
+      var _attrs = $.extend(true, {}, attrs);
+      _attrs = transform_keys(_attrs, {
         'job-forms': 'job_forms',
         'con-forms': 'con_forms'
       });
-      attrs = transform_values(attrs, {
+      _attrs = transform_values(_attrs, {
         'con_forms': to_forms,
         'job_forms': function(key, value) {
           transform_values(value, {
@@ -42,18 +42,31 @@ var framework = (function($) {
           return value;
         }
       });
-      return attrs;
+      return _attrs;
     }
   });
 
   var Framework = koify.Node.extend({
     'identifier': 'framework',
     'persists': false,
-    'modelClass': FrameworkModel,
+    'model_class': FrameworkModel,
     'base_url': '/sqoop/api/framework/',
     'initialize': function() {
       var self = this;
       self.parent.initialize.apply(self, arguments);
+      self.selected = ko.observable();
+    },
+    'map': function() {
+      var self = this;
+      var mapping_options = $.extend(true, {
+        'ignore': ['parent', 'initialize']
+      }, forms.MapProperties);
+      if ('__ko_mapping__' in self) {
+        ko.mapping.fromJS(self.model, mapping_options, self);
+      } else {
+        var mapped = ko.mapping.fromJS(self.model, mapping_options);
+        $.extend(self, mapped);
+      }
     }
   });
 

+ 17 - 5
apps/sqoop/static/js/sqoop.jobs.js

@@ -37,23 +37,23 @@ var jobs = (function($) {
     },
     'initialize': function(attrs) {
       var self = this;
-      var attrs = $.extend(true, attrs, {});
-      attrs = transform_keys(attrs, {
+      var _attrs = $.extend(true, {}, attrs);
+      _attrs = transform_keys(_attrs, {
         'connector-id': 'connector_id',
         'connection-id': 'connection_id'
       });
-      attrs = transform_values(attrs, {
+      _attrs = transform_values(_attrs, {
         'connector': to_forms,
         'framework': to_forms
       });
-      return attrs;
+      return _attrs;
     }
   });
 
   var Job = koify.Node.extend({
     'identifier': 'job',
     'persists': true,
-    'modelClass': JobModel,
+    'model_class': JobModel,
     'base_url': '/sqoop/api/jobs/',
     'initialize': function() {
       var self = this;
@@ -150,6 +150,18 @@ var jobs = (function($) {
 
       self.runningInterval = 0;
     },
+    map: function() {
+      var self = this;
+      var mapping_options = $.extend(true, {
+        'ignore': ['parent', 'initialize']
+      }, forms.MapProperties);
+      if ('__ko_mapping__' in self) {
+        ko.mapping.fromJS(self.model, mapping_options, self);
+      } else {
+        var mapped = ko.mapping.fromJS(self.model, mapping_options);
+        $.extend(self, mapped);
+      }
+    },
     'start': function(options) {
       var self = this;
       $(document).trigger('start.job', [options, self]);

+ 7 - 4
apps/sqoop/static/js/sqoop.js

@@ -61,10 +61,10 @@ function create_connection(attrs, options) {
   // Need a copy of the forms so that when editing
   // we don't re-use forms.
   $.each(viewModel.connector().con_forms(), function(index, form) {
-    node.connector.push($.extend(true, form, {}));
+    node.connector.push($.extend(true, {}, form));
   });
   $.each(viewModel.framework().con_forms(), function(index, form) {
-    node.framework.push($.extend(true, form, {}));
+    node.framework.push($.extend(true, {}, form));
   });
   return node;
 }
@@ -112,7 +112,7 @@ var viewModel = new (function() {
       return self.connectors()[0];
     }
     var connectorArr = ko.utils.arrayFilter(self.connectors(), function (connector) {
-      return connector.id() == self.connection().connector();
+      return connector.id() == self.connection().connector_id();
     });
     return (connectorArr.length > 0) ? connectorArr[0] : self.connectors()[0];
   });
@@ -295,7 +295,7 @@ var viewModel = new (function() {
     var self = this;
     if (!self.job() || self.job().persisted()) {
       var job = create_job();
-      job.name = defaultName;
+      job.name(defaultName);
       self.jobs.push(job);
       self.deselectAllJobs();
       job.selected(true);
@@ -312,13 +312,16 @@ var viewModel = new (function() {
   };
 
   self.chooseJobById = function(id) {
+    var found_job = false;
     $.each(self.jobs(), function(index, job) {
       if (job.id() == id) {
+        found_job = true;
         job.selected(true);
       } else {
         job.selected(false);
       }
     });
+    return found_job;
   };
 
   self.toggleAllJobsSelected = function() {

+ 0 - 93
apps/sqoop/static/js/sqoop.models.js

@@ -1,93 +0,0 @@
-// Licensed to Cloudera, Inc. under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  Cloudera, Inc. licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-
-function transform_keys(model, keys_dict) {
-  $.each(keys_dict, function(key, new_key) {
-    if (key in model) {
-      model[new_key] = model[key];
-      delete model[key];
-    }
-  });
-  return model;
-}
-
-function transform_values(model, func_dict) {
-  $.each(func_dict, function(key, f) {
-    if (key in model) {
-      model[key] = f(key, model[key]);
-    }
-  });
-  return model;
-}
-
-function to_form(value) {
-  return new FormModel(value);
-}
-
-function to_forms(key, value) {
-  $.each(value, function(index, form_dict) {
-    value[index] = to_form(form_dict);
-  });
-  return value;
-}
-
-function to_input(value) {
-  return new InputModel(value);
-}
-
-function to_inputs(key, value) {
-  $.each(value, function(index, input_dict) {
-    value[index] = to_input(input_dict);
-  });
-  return value;
-}
-
-var FormModel = koify.Model.extend({
-  'id': -1,
-  'inputs': [],
-  'name': null,
-  'type': null,
-  'initialize': function(attrs) {
-    var self = this;
-    var attrs = $.extend(true, attrs, {});
-    attrs = transform_values(attrs, {
-      'inputs': to_inputs
-    });
-    return attrs;
-  }
-});
-
-var InputModel = koify.Model.extend({
-  'id': -1,
-  'name': null,
-  'type': null,
-  'size': -1,
-  'sensitive': false,
-  'values': null,
-  'value': null,
-  'initialize': function(attrs) {
-    var self = this;
-    var attrs = $.extend(true, attrs, {});
-    if ('values' in attrs) {
-      attrs['values'] = attrs['values'].split(',');
-    }
-    if ('value' in attrs) {
-      attrs.value = decodeURIComponent(attrs.value);
-    }
-    return attrs;
-  }
-});

+ 4 - 4
apps/sqoop/static/js/sqoop.submissions.js

@@ -28,14 +28,14 @@ var submissions = (function($) {
     'external_link': null,
     'initialize': function(attrs) {
       var self = this;
-      var attrs = $.extend(true, attrs || {}, {});
-      attrs = transform_keys(attrs, {
+      var _attrs = $.extend(true, {}, attrs || {});
+      _attrs = transform_keys(_attrs, {
         'creation-date': 'creation_date',
         'last-update-date': 'last_update_date',
         'external-id': 'external_id',
         'external-link': 'external_link'
       });
-      return attrs;
+      return _attrs;
     }
   });
 
@@ -43,7 +43,7 @@ var submissions = (function($) {
   var Submission = koify.Node.extend({
     'identifier': 'submission',
     'persists': false,
-    'modelClass': SubmissionModel,
+    'model_class': SubmissionModel,
     'base_url': '/sqoop/api/submissions/',
     'initialize': function() {
       var self = this;

+ 87 - 0
apps/sqoop/static/js/sqoop.utils.js

@@ -90,6 +90,93 @@ ko.bindingHandlers.clickValue = {
   }
 };
 
+ko.sqoop = {
+  'fixModel' : function(root) {
+    function parsePropertyName(propertyName) {
+      var properties = [];
+      if (propertyName) {
+        // get object keys in form of array
+        var propertyNames = propertyName.split('.');
+        // add indexes to array
+        $.each(propertyNames, function(index, property) {
+          if (property.indexOf(']') !== -1) {
+            var left = property.indexOf('[');
+            var right = property.indexOf(']');
+            var index = parseInt(property.substring(left+1, right));
+
+            properties.push(property.substring(0, left), index);
+          } else {
+            properties.push(property);
+          }
+        });
+      }
+
+      return properties;
+    }
+
+    function getPropertyByName(root, propertyName) {
+      var parts = parsePropertyName(propertyName);
+      var obj = root;
+      $.each(parts, function(index, property) {
+        if (ko.isObservable(obj)) {
+          obj = obj()[property];
+        } else {
+          obj = obj[property];
+        }
+      });
+      return obj;
+    }
+
+    function setPropertyByName(root, propertyName, propertyValue) {
+      var parts = parsePropertyName(propertyName);
+      var obj = root;
+      var parent = null;
+      var propertyName = null;
+      if (parts.length != 0) {
+        $.each(parts, function(index, property) {
+          propertyName = property;
+          parent = obj;
+          if (ko.isObservable(obj)) {
+            obj = obj()[property];
+          } else {
+            obj = obj[property];
+          }
+        });
+
+        if (ko.isObservable(parent)) {
+          parent()[propertyName] = propertyValue;
+        } else {
+          parent[propertyName] = propertyValue;
+        }
+      }
+    }
+
+    var models = [];
+
+    // Map to original model
+    var root_model = ko.mapping.toJS(root);
+
+    // ko.mapping.visitModel performs a breadth first search.
+    ko.mapping.visitModel(root, function(value, parent_name) {
+      if (value['fixModel']) {
+        models.push(parent_name);
+      }
+    });
+    // Need a depth first search result
+    models = models.reverse();
+    console.log(models);
+
+    // Call fixModel on every node that has that method.
+    $.each(models, function(index, parent_name) {
+      var old_model = getPropertyByName(root_model, parent_name);
+      var new_model = getPropertyByName(root, parent_name).fixModel(old_model);
+      setPropertyByName(root_model, parent_name, new_model);
+    });
+
+    return root_model;
+  }
+};
+
 //// JQuery Utils
 if (jQuery) {
   jQuery.extend({