Prechádzať zdrojové kódy

[metastore] Add assist to the database and tables page

Enrico Berti 10 rokov pred
rodič
commit
1cacfa8

+ 220 - 100
apps/metastore/src/metastore/templates/databases.mako

@@ -14,34 +14,95 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 <%!
+from desktop import conf
 from desktop.lib.i18n import smart_unicode
 from desktop.views import commonheader, commonfooter
 from django.utils.translation import ugettext as _
 %>
 <%namespace name="actionbar" file="actionbar.mako" />
 <%namespace name="components" file="components.mako" />
+<%namespace name="assist" file="/assist.mako" />
+<%namespace name="tableStats" file="/table_stats.mako" />
+<%namespace name="require" file="/require.mako" />
+
 
 ${ commonheader(_('Databases'), 'metastore', user) | n,unicode }
 ${ components.menubar() }
 
-<div class="container-fluid" id="databases">
-  <div class="row-fluid">
-  % if has_write_access:
-    <div class="span3">
-      <div class="sidebar-nav card-small">
-        <ul class="nav nav-list">
-          <li class="nav-header">${_('actions')}</li>
-          <li><a href="${ url('beeswax:create_database') }"><i class="fa fa-plus-circle"></i> ${_('Create a new database')}</a></li>
-        </ul>
-      </div>
-    </div>
-    <div class="span9">
-  %else:
-    <div class="span12">
-  % endif
-      <div class="card card-small">
-        <h1 class="card-heading simple">${ components.breadcrumbs(breadcrumbs) }</h1>
-        <%actionbar:render>
+${ require.config() }
+
+${ tableStats.tableStats() }
+${ assist.assistPanel() }
+
+
+<script src="${ static('desktop/ext/js/d3.v3.js') }" type="text/javascript" charset="utf-8"></script>
+
+<link rel="stylesheet" href="${ static('metastore/css/metastore.css') }" type="text/css">
+<link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
+<style type="text/css">
+% if conf.CUSTOM.BANNER_TOP_HTML.get():
+  .show-assist {
+    top: 110px!important;
+  }
+  .main-content {
+    top: 112px!important;
+  }
+% endif
+</style>
+
+
+<a title="${_('Toggle Assist')}" class="pointer show-assist" data-bind="visible: !$root.isLeftPanelVisible() && $root.assistAvailable(), click: function() { $root.isLeftPanelVisible(true); }">
+  <i class="fa fa-chevron-right"></i>
+</a>
+
+<div class="main-content">
+  <div class="vertical-full container-fluid">
+    <div class="vertical-full">
+      <div class="vertical-full row-fluid panel-container">
+
+        <div class="assist-container left-panel" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable()">
+          <a title="${_('Toggle Assist')}" class="pointer hide-assist" data-bind="click: function() { $root.isLeftPanelVisible(false) }">
+            <i class="fa fa-chevron-left"></i>
+          </a>
+          <div class="assist" data-bind="component: {
+              name: 'assist-panel',
+              params: {
+                sourceTypes: [{
+                  name: 'hive',
+                  type: 'hive'
+                }],
+                user: '${user.username}',
+                navigationSettings: {
+                  openItem: true,
+                  showPreview: true,
+                  showStats: false
+                }
+              }
+            }"></div>
+        </div>
+        <div class="resizer" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable(), splitDraggable : { appName: 'notebook', leftPanelVisible: $root.isLeftPanelVisible }"><div class="resize-bar">&nbsp;</div></div>
+
+        <div class="right-panel">
+
+          <div class="metastore-main">
+            <h3>
+              <ul class="nav nav-pills pull-right" style="margin-top: -8px">
+                % if has_write_access:
+                <li><a href="${ url('beeswax:create_database') }" title="${_('Create a new database')}"><i class="fa fa-plus-circle"></i></a></li>
+                % endif
+              </ul>
+              <ul class="nav nav-pills hueBreadcrumbBar" id="breadcrumbs">
+                <li>
+                  <i class="fa fa-th-large muted"></i>
+                </li>
+                <li>
+                  <a href="${url('metastore:databases')}">${_('Databases')}</a>
+                </li>
+              </ul>
+            </h3>
+
+
+            <%actionbar:render>
           <%def name="search()">
             <form id="searchQueryForm" action="${ url('metastore:databases') }" method="GET" class="inline">
               <input id="filterInput" type="text" name="filter" class="input-xlarge search-query" value="${ search_filter }" placeholder="${_('Search for database name')}" />
@@ -78,11 +139,19 @@ ${ components.menubar() }
           % endfor
           </tbody>
         </table>
+
+
+          </div>
+
+        </div>
+
       </div>
     </div>
   </div>
 </div>
 
+
+
 <div id="dropDatabase" class="modal hide fade">
   <form id="dropDatabaseForm" action="${ url('metastore:drop_database') }" method="POST">
     ${ csrf_token(request) | n,unicode }
@@ -100,110 +169,161 @@ ${ components.menubar() }
   </form>
 </div>
 
-<link rel="stylesheet" href="${ static('metastore/css/metastore.css') }" type="text/css">
-
-<script src="${ static('desktop/ext/js/knockout.min.js') }" type="text/javascript" charset="utf-8"></script>
 
 <script type="text/javascript" charset="utf-8">
-  $(document).ready(function () {
-    var viewModel = {
-      availableDatabases: ko.observableArray(${ databases_json | n,unicode }),
-      chosenDatabases: ko.observableArray([])
-    };
 
-    ko.applyBindings(viewModel);
+  require([
+    "knockout",
+    "ko.charts",
+    "desktop/js/assist/assistHelper",
+    "assistPanel",
+    "tableStats",
+    "knockout-mapping",
+    "knockout-sortable",
+    "knockout-deferred-updates",
+    "ko.editable",
+    "ko.hue-bindings"
+  ], function (ko, charts, AssistHelper) {
+
+
+    function MetastoreViewModel(options) {
+      var self = this;
+      self.assistAvailable = ko.observable(true);
+      self.isLeftPanelVisible = ko.observable(self.assistAvailable() && $.totalStorage('spark_left_panel_visible') != null && $.totalStorage('spark_left_panel_visible'));
+
+      self.assistHelper = new AssistHelper(options);
+      self.availableDatabases = ko.observableArray(${ databases_json | n });
+      self.chosenDatabases = ko.observableArray([]);
+
+      huePubSub.subscribe("assist.table.selected", function (tableDef) {
+        location.href = '/metastore/table/' + tableDef.database + '/' + tableDef.name;
+      });
+
+      huePubSub.subscribe("assist.database.selected", function (databaseDef) {
+        location.href = '/metastore/tables/' + databaseDef.name;
+      });
 
-    if (location.getParameter("error") != "") {
-      $.jHueNotify.error(location.getParameter("error"));
+      self.isLeftPanelVisible.subscribe(function (newValue) {
+        $.totalStorage('spark_left_panel_visible', newValue);
+      });
     }
 
-    var databases = $(".datatables").dataTable({
-      "sDom": "<'row'r>t<'row'<'span8'i><''p>>",
-      "bPaginate": false,
-      "bLengthChange": false,
-      "bInfo": false,
-      "aoColumns": [
-        {"bSortable": false, "sWidth": "1%" },
-        null,
-      ],
-      "oLanguage": {
-        "sEmptyTable": "${_('No data available')}",
-        "sZeroRecords": "${_('No matching records')}",
+    $(document).ready(function () {
+
+      var options = {
+        user: '${ user.username }',
+        i18n: {
+          errorLoadingDatabases: "${ _('There was a problem loading the databases') }",
+          errorLoadingTablePreview: "${ _('There was a problem loading the table preview.') }"
+        }
       }
-    });
 
-    var _searchInputValue = $("#filterInput").val();
+      var viewModel = new MetastoreViewModel(options);
+
+      ko.applyBindings(viewModel);
 
-    $("#filterInput").jHueDelayedInput(function(){
-      if ($("#filterInput").val() != _searchInputValue){
-        $("#searchQueryForm").submit();
+      window.hueDebug = {
+        viewModel: viewModel,
+        ko: ko
+      };
+
+      if (location.getParameter("error") != "") {
+        $.jHueNotify.error(location.getParameter("error"));
       }
-    });
 
-    $("#filterInput").focus();
-    $("#filterInput").val(_searchInputValue); // set caret at the end of the field
+      var databases = $(".datatables").dataTable({
+        "sDom": "<'row'r>t<'row'<'span8'i><''p>>",
+        "bPaginate": false,
+        "bLengthChange": false,
+        "bInfo": false,
+        "aoColumns": [
+          {"bSortable": false, "sWidth": "1%"},
+          null,
+        ],
+        "oLanguage": {
+          "sEmptyTable": "${_('No data available')}",
+          "sZeroRecords": "${_('No matching records')}",
+        }
+      });
 
-    $("a[data-row-selector='true']").jHueRowSelector();
+      var _searchInputValue = $("#filterInput").val();
 
-    $(".selectAll").click(function () {
-      if ($(this).attr("checked")) {
-        $(this).removeAttr("checked").removeClass("fa-check");
-        $("." + $(this).data("selectables")).removeClass("fa-check").removeAttr("checked");
-      }
-      else {
-        $(this).attr("checked", "checked").addClass("fa-check");
-        $("." + $(this).data("selectables")).addClass("fa-check").attr("checked", "checked");
-      }
-      toggleActions();
-    });
+      $("#filterInput").jHueDelayedInput(function () {
+        if ($("#filterInput").val() != _searchInputValue) {
+          $("#searchQueryForm").submit();
+        }
+      });
 
-    $(".databaseCheck").click(function () {
-      if ($(this).attr("checked")) {
-        $(this).removeClass("fa-check").removeAttr("checked");
-      }
-      else {
-        $(this).addClass("fa-check").attr("checked", "checked");
-      }
-      $(".selectAll").removeAttr("checked").removeClass("fa-check");
-      toggleActions();
-    });
+      $("#filterInput").focus();
+      $("#filterInput").val(_searchInputValue); // set caret at the end of the field
+
+      $("a[data-row-selector='true']").jHueRowSelector();
 
-    $(".databaseLink").mouseover(function() {
-      var _link = $(this);
-      $.ajax({
-        type: "GET",
-        url: "/metastore/databases/" + $(this).text() + "/metadata",
-        dataType: "json",
-        data: {},
-        success: function (response) {
-          if (response && response.status == 0) {
-            _link.attr("title", response.data.comment).tooltip("show");
-          }
-        },
+      $(".selectAll").click(function () {
+        if ($(this).attr("checked")) {
+          $(this).removeAttr("checked").removeClass("fa-check");
+          $("." + $(this).data("selectables")).removeClass("fa-check").removeAttr("checked");
+        }
+        else {
+          $(this).attr("checked", "checked").addClass("fa-check");
+          $("." + $(this).data("selectables")).addClass("fa-check").attr("checked", "checked");
+        }
+        toggleActions();
       });
-    });
 
-    function toggleActions() {
-      $(".toolbarBtn").attr("disabled", "disabled");
-      var selector = $(".hueCheckbox[checked='checked']");
-      if (selector.length >= 1) {
-        $("#dropBtn").removeAttr("disabled");
-      }
-    }
+      $(".databaseCheck").click(function () {
+        if ($(this).attr("checked")) {
+          $(this).removeClass("fa-check").removeAttr("checked");
+        }
+        else {
+          $(this).addClass("fa-check").attr("checked", "checked");
+        }
+        $(".selectAll").removeAttr("checked").removeClass("fa-check");
+        toggleActions();
+      });
 
-    $("#dropBtn").click(function () {
-      $.getJSON("${ url('metastore:drop_database') }", function (data) {
-        $("#dropDatabaseMessage").text(data.title);
+      $(".databaseLink").mouseover(function () {
+        var _link = $(this);
+        $.ajax({
+          type: "GET",
+          url: "/metastore/databases/" + $(this).text() + "/metadata",
+          dataType: "json",
+          data: {},
+          success: function (response) {
+            if (response && response.status == 0) {
+              _link.attr("title", response.data.comment).tooltip("show");
+            }
+          },
+        });
       });
-      var _tempList = [];
-      $(".hueCheckbox[checked='checked']").each(function (index) {
-        _tempList.push($(this).data("drop-name"));
+
+      function toggleActions() {
+        $(".toolbarBtn").attr("disabled", "disabled");
+        var selector = $(".hueCheckbox[checked='checked']");
+        if (selector.length >= 1) {
+          $("#dropBtn").removeAttr("disabled");
+        }
+      }
+
+      $("#dropBtn").click(function () {
+        $.getJSON("${ url('metastore:drop_database') }", function (data) {
+          $("#dropDatabaseMessage").text(data.title);
+        });
+        var _tempList = [];
+        $(".hueCheckbox[checked='checked']").each(function (index) {
+          _tempList.push($(this).data("drop-name"));
+        });
+        viewModel.chosenDatabases.removeAll();
+        viewModel.chosenDatabases(_tempList);
+        $("#dropDatabase").modal("show");
       });
-      viewModel.chosenDatabases.removeAll();
-      viewModel.chosenDatabases(_tempList);
-      $("#dropDatabase").modal("show");
     });
+
+
   });
+
+
+
 </script>
 
 ${ commonfooter(messages) | n,unicode }

+ 280 - 183
apps/metastore/src/metastore/templates/tables.mako

@@ -14,102 +14,163 @@
 ## See the License for the specific language governing permissions and
 ## limitations under the License.
 <%!
+from desktop import conf
 from desktop.lib.i18n import smart_unicode
-from desktop.views import commonheader, commonfooter
+from desktop.views import commonheader, commonfooter, _ko
 from django.utils.translation import ugettext as _
 %>
 
 <%namespace name="actionbar" file="actionbar.mako" />
 <%namespace name="components" file="components.mako" />
+<%namespace name="assist" file="/assist.mako" />
+<%namespace name="tableStats" file="/table_stats.mako" />
+<%namespace name="require" file="/require.mako" />
+
 
 ${ commonheader(_('Tables'), 'metastore', user) | n,unicode }
 ${ components.menubar() }
 
-<link rel="stylesheet" href="${ static('desktop/ext/chosen/chosen.min.css') }">
-<script src="${ static('desktop/ext/chosen/chosen.jquery.min.js') }" type="text/javascript" charset="utf-8"></script>
-
-<div class="container-fluid" id="tables">
-  <div class="row-fluid">
-    <div class="span3">
-      <div class="sidebar-nav card-small">
-        <ul class="nav nav-list">
-          <li class="nav-header">${_('database')}</li>
-          <li class="white">
-            <form action="${ url('metastore:show_tables') }" id="db_form" method="POST" style="margin-bottom: 0">
-              ${ csrf_token(request) | n,unicode }
-              ${ db_form | n,unicode }
-            </form>
-          </li>
-          % if has_write_access:
-          <li class="nav-header">${_('Actions')}</li>
-          <li><a href="${ url('beeswax:import_wizard', database=database) }"><i class="fa fa-files-o"></i> ${_('Create a new table from a file')}</a></li>
-          <li><a href="${ url('beeswax:create_table', database=database) }"><i class="fa fa-wrench"></i> ${_('Create a new table manually')}</a></li>
-          % endif
-        </ul>
-      </div>
-    </div>
-    <div class="span9">
-      <div class="card card-small">
-        <h1 class="card-heading simple">${ components.breadcrumbs(breadcrumbs) }</h1>
-        <%actionbar:render>
-          <%def name="search()">
-            <div>
-              ## Also available in Ajax, so ko-ifable
-              ${ database_meta.get('comment') }
-              ${ database_meta.get('location') }
-              ${ database_meta.get('owner_name') }
-              ${ database_meta.get('owner_type') }
-              ${ database_meta.get('parameters') }
-            </div>
-            <form id="searchQueryForm" action="${ url('metastore:show_tables') }" method="GET" class="inline">
-              <input id="filterInput" type="text" name="filter" class="input-xlarge search-query" value="${ search_filter }" placeholder="${_('Search for table name')}" />
-            </form>
-          </%def>
-          <%def name="actions()">
-            <button id="viewBtn" class="btn toolbarBtn" title="${_('Browse the selected table')}" disabled="disabled"><i class="fa fa-eye"></i> ${_('View')}</button>
-            <button id="browseBtn" class="btn toolbarBtn" title="${_('Browse the selected table')}" disabled="disabled"><i class="fa fa-list"></i> ${_('Browse Data')}</button>
-            % if has_write_access:
-            <button id="dropBtn" class="btn toolbarBtn" title="${_('Delete the selected tables')}" disabled="disabled"><i class="fa fa-trash-o"></i>  ${_('Drop')}</button>
-            % endif
-          </%def>
-        </%actionbar:render>
-        <div style="padding-left: 10px; padding-right: 10px">
-          <table class="table table-condensed datatables" data-tablescroller-disable="true">
-            <thead>
-              <tr>
-                <th width="1%"><div class="hueCheckbox selectAll fa" data-selectables="tableCheck"></div></th>
-                <th>&nbsp;</th>
-                <th>${_('Table Name')}</th>
-                <th>${_('Comment')}</th>
-                <th>${_('Type')}</th>
-              </tr>
-            </thead>
-            <tbody>
-            % for table in tables:
-              <tr>
-                <td data-row-selector-exclude="true" width="1%">
-                  <div class="hueCheckbox tableCheck fa"
-                       data-view-url="${ url('metastore:describe_table', database=database, table=table['name']) }"
-                       data-browse-url="${ url('metastore:read_table', database=database, table=table['name']) }"
-                       data-drop-name="${ table['name'] }"
-                       data-row-selector-exclude="true"></div>
-                </td>
-                <td class="row-selector-exclude"><a href="javascript:void(0)" data-table="${ table['name'] }"><i class="fa fa-bar-chart" title="${ _('View statistics') }"></i></a></td>
-                <td>
-                  <a class="tableLink" href="${ url('metastore:describe_table', database=database, table=table['name']) }" data-row-selector="true">${ table['name'] }</a>
-                </td>
-                <td>${ smart_unicode(table['comment']) if table['comment'] else '' }</td>
-                <td>${ smart_unicode(table['type']) }</td>
-              </tr>
-            % endfor
-            </tbody>
-          </table>
+${ require.config() }
+
+${ tableStats.tableStats() }
+${ assist.assistPanel() }
+
+
+<script src="${ static('desktop/ext/js/d3.v3.js') }" type="text/javascript" charset="utf-8"></script>
+
+<link rel="stylesheet" href="${ static('metastore/css/metastore.css') }" type="text/css">
+<link rel="stylesheet" href="${ static('notebook/css/notebook.css') }">
+<style type="text/css">
+% if conf.CUSTOM.BANNER_TOP_HTML.get():
+  .show-assist {
+    top: 110px!important;
+  }
+  .main-content {
+    top: 112px!important;
+  }
+% endif
+</style>
+
+
+<a title="${_('Toggle Assist')}" class="pointer show-assist" data-bind="visible: !$root.isLeftPanelVisible() && $root.assistAvailable(), click: function() { $root.isLeftPanelVisible(true); }">
+  <i class="fa fa-chevron-right"></i>
+</a>
+
+<div class="main-content">
+  <div class="vertical-full container-fluid">
+    <div class="vertical-full">
+      <div class="vertical-full row-fluid panel-container">
+
+        <div class="assist-container left-panel" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable()">
+          <a title="${_('Toggle Assist')}" class="pointer hide-assist" data-bind="click: function() { $root.isLeftPanelVisible(false) }">
+            <i class="fa fa-chevron-left"></i>
+          </a>
+          <div class="assist" data-bind="component: {
+              name: 'assist-panel',
+              params: {
+                sourceTypes: [{
+                  name: 'hive',
+                  type: 'hive'
+                }],
+                user: '${user.username}',
+                navigationSettings: {
+                  openItem: true,
+                  showPreview: true,
+                  showStats: false
+                }
+              }
+            }"></div>
         </div>
+        <div class="resizer" data-bind="visible: $root.isLeftPanelVisible() && $root.assistAvailable(), splitDraggable : { appName: 'notebook', leftPanelVisible: $root.isLeftPanelVisible }"><div class="resize-bar">&nbsp;</div></div>
+
+        <div class="right-panel">
+
+          <div class="metastore-main">
+            <h3>
+              <ul class="nav nav-pills pull-right" style="margin-top: -8px">
+                % if has_write_access:
+                <li><a href="${ url('beeswax:import_wizard', database=database) }" title="${_('Create a new table from a file')}"><i class="fa fa-files-o"></i></a></li>
+                <li><a href="${ url('beeswax:create_table', database=database) }" title="${_('Create a new table manually')}"><i class="fa fa-wrench"></i></a></li>
+                % endif
+              </ul>
+              <ul class="nav nav-pills hueBreadcrumbBar" id="breadcrumbs">
+                <li>
+                  <i class="fa fa-th-list muted"></i>
+                </li>
+                <li>
+                  <a href="${url('metastore:databases')}">${_('Databases')}</a><span class="divider">&gt;</span>
+                </li>
+                <li>
+                  <span style="padding-left:12px">${database}</span>
+                </li>
+              </ul>
+            </h3>
+
+            <%actionbar:render>
+              <%def name="search()">
+                <div>
+                  ## Also available in Ajax, so ko-ifable
+                  ## ${ database_meta.get('comment') }
+                  ## ${ database_meta.get('location') }
+                  ## ${ database_meta.get('owner_name') }
+                  ## ${ database_meta.get('owner_type') }
+                  ## ${ database_meta.get('parameters') }
+                </div>
+                <form id="searchQueryForm" action="${ url('metastore:show_tables') }" method="GET" class="inline">
+                  <input id="filterInput" type="text" name="filter" class="input-xlarge search-query" value="${ search_filter }" placeholder="${_('Search for table name')}" />
+                </form>
+              </%def>
+              <%def name="actions()">
+                <button id="viewBtn" class="btn toolbarBtn" title="${_('Browse the selected table')}" disabled="disabled"><i class="fa fa-eye"></i> ${_('View')}</button>
+                <button id="browseBtn" class="btn toolbarBtn" title="${_('Browse the selected table')}" disabled="disabled"><i class="fa fa-list"></i> ${_('Browse Data')}</button>
+                % if has_write_access:
+                <button id="dropBtn" class="btn toolbarBtn" title="${_('Delete the selected tables')}" disabled="disabled"><i class="fa fa-trash-o"></i>  ${_('Drop')}</button>
+                % endif
+              </%def>
+            </%actionbar:render>
+              <table class="table table-condensed datatables" data-tablescroller-disable="true">
+                <thead>
+                  <tr>
+                    <th width="1%"><div class="hueCheckbox selectAll fa" data-selectables="tableCheck"></div></th>
+                    <th>&nbsp;</th>
+                    <th>${_('Table Name')}</th>
+                    <th>${_('Comment')}</th>
+                    <th>${_('Type')}</th>
+                  </tr>
+                </thead>
+                <tbody>
+                % for table in tables:
+                  <tr>
+                    <td data-row-selector-exclude="true" width="1%">
+                      <div class="hueCheckbox tableCheck fa"
+                           data-view-url="${ url('metastore:describe_table', database=database, table=table['name']) }"
+                           data-browse-url="${ url('metastore:read_table', database=database, table=table['name']) }"
+                           data-drop-name="${ table['name'] }"
+                           data-row-selector-exclude="true"></div>
+                    </td>
+                    <td class="row-selector-exclude"><a href="javascript:void(0)" data-table="${ table['name'] }"><i class="fa fa-bar-chart" title="${ _('View statistics') }"></i></a></td>
+                    <td>
+                      <a class="tableLink" href="${ url('metastore:describe_table', database=database, table=table['name']) }" data-row-selector="true">${ table['name'] }</a>
+                    </td>
+                    <td>${ smart_unicode(table['comment']) if table['comment'] else '' }</td>
+                    <td>${ smart_unicode(table['type']) }</td>
+                  </tr>
+                % endfor
+                </tbody>
+              </table>
+
+          </div>
+
+        </div>
+
       </div>
     </div>
   </div>
 </div>
 
+
+
+
 <div id="tableAnalysis" class="popover mega-popover right">
   <div class="arrow"></div>
   <h3 class="popover-title" style="text-align: left">
@@ -143,140 +204,176 @@ ${ components.menubar() }
   </form>
 </div>
 
-<link rel="stylesheet" href="${ static('metastore/css/metastore.css') }" type="text/css">
 
-<script src="${ static('desktop/ext/js/knockout.min.js') }" type="text/javascript" charset="utf-8"></script>
 <script src="${ static('beeswax/js/stats.utils.js') }"></script>
 
 <script type="text/javascript" charset="utf-8">
+
   var STATS_PROBLEMS = "${ _('There was a problem loading the stats.') }";
 
-  $(document).ready(function () {
-    var viewModel = {
-      availableTables: ko.observableArray(${ table_names | n }),
-      chosenTables: ko.observableArray([])
-    };
+  require([
+    "knockout",
+    "ko.charts",
+    "desktop/js/assist/assistHelper",
+    "assistPanel",
+    "tableStats",
+    "knockout-mapping",
+    "knockout-sortable",
+    "knockout-deferred-updates",
+    "ko.editable",
+    "ko.hue-bindings"
+  ], function (ko, charts, AssistHelper) {
 
-    ko.applyBindings(viewModel);
 
-    if (location.getParameter("error") != "") {
-      $.jHueNotify.error(location.getParameter("error"));
+    function MetastoreViewModel(options) {
+      var self = this;
+      self.assistAvailable = ko.observable(true);
+      self.isLeftPanelVisible = ko.observable(self.assistAvailable() && $.totalStorage('spark_left_panel_visible') != null && $.totalStorage('spark_left_panel_visible'));
+
+      self.assistHelper = new AssistHelper(options);
+      self.availableTables = ko.observableArray(${ table_names | n });
+      self.chosenTables = ko.observableArray([]);
+
+      huePubSub.subscribe("assist.table.selected", function (tableDef) {
+        location.href = '/metastore/table/${database}/' + tableDef.name;
+      });
+
+      huePubSub.subscribe("assist.database.selected", function (databaseDef) {
+        location.href = '/metastore/tables/' + databaseDef.name;
+      });
+
+      self.isLeftPanelVisible.subscribe(function (newValue) {
+        $.totalStorage('spark_left_panel_visible', newValue);
+      });
     }
 
-    var tables = $(".datatables").dataTable({
-      "sDom": "<'row'r>t<'row'<'span8'i><''p>>",
-      "bPaginate": false,
-      "bLengthChange": false,
-      "bInfo": false,
-      "bFilter": true,
-      "aoColumns": [
-        {"bSortable": false, "sWidth": "1%" },
-        {"bSortable": false, "sWidth": "1%" },
-        null,
-        null,
-        null
-      ],
-      "oLanguage": {
-        "sEmptyTable": "${_('No data available')}",
-        "sZeroRecords": "${_('No matching records')}",
+    $(document).ready(function () {
+      var options = {
+        user: '${ user.username }',
+        i18n: {
+          errorLoadingDatabases: "${ _('There was a problem loading the databases') }",
+          errorLoadingTablePreview: "${ _('There was a problem loading the table preview.') }"
+        }
       }
-    });
 
-    var _searchInputValue = $("#filterInput").val();
+      var viewModel = new MetastoreViewModel(options);
+
+      ko.applyBindings(viewModel);
+
+      window.hueDebug = {
+        viewModel: viewModel,
+        ko: ko
+      };
 
-    $("#filterInput").jHueDelayedInput(function(){
-      if ($("#filterInput").val() != _searchInputValue){
-        $("#searchQueryForm").submit();
+      if (location.getParameter("error") != "") {
+        $.jHueNotify.error(location.getParameter("error"));
       }
-    });
 
-    $("#filterInput").focus();
-    $("#filterInput").val(_searchInputValue); // set caret at the end of the field
+      var tables = $(".datatables").dataTable({
+        "sDom": "<'row'r>t<'row'<'span8'i><''p>>",
+        "bPaginate": false,
+        "bLengthChange": false,
+        "bInfo": false,
+        "bFilter": true,
+        "aoColumns": [
+          {"bSortable": false, "sWidth": "1%"},
+          {"bSortable": false, "sWidth": "1%"},
+          null,
+          null,
+          null
+        ],
+        "oLanguage": {
+          "sEmptyTable": "${_('No data available')}",
+          "sZeroRecords": "${_('No matching records')}",
+        }
+      });
 
-    $("a[data-row-selector='true']").jHueRowSelector();
+      var _searchInputValue = $("#filterInput").val();
 
-    $("a[data-table]").on("click", function () {
-      var _link = $(this);
-      var statsUrl = "/beeswax/api/table/${database}/" + _link.data("table") + "/stats/";
-      var refreshUrl = "/beeswax/api/analyze/${database}/" + _link.data("table") + "/";
-      $("#tableAnalysisStats .content").html("<i class='fa fa-spinner fa-spin'></i>");
-      $("#tableAnalysis").show().css("top", _link.position().top - $("#tableAnalysis").outerHeight()/2 + _link.outerHeight()/2).css("left", _link.position().left + _link.outerWidth());
-      showTableStats(statsUrl, refreshUrl, _link.data("table"), STATS_PROBLEMS, function(){
-        $("#tableAnalysis").show().css("top", _link.position().top - $("#tableAnalysis").outerHeight()/2 + _link.outerHeight()/2).css("left", _link.position().left + _link.outerWidth());
+      $("#filterInput").jHueDelayedInput(function () {
+        if ($("#filterInput").val() != _searchInputValue) {
+          $("#searchQueryForm").submit();
+        }
       });
-    });
 
-    $(document).on("click", "#tableAnalysis .close-popover", function () {
-      $("#tableAnalysis").hide();
-    });
+      $("#filterInput").focus();
+      $("#filterInput").val(_searchInputValue); // set caret at the end of the field
 
-    $("#id_database").chosen({
-      disable_search_threshold: 5,
-      width: "100%",
-      no_results_text: "${_('Oops, no database found!')}"
-    });
+      $("a[data-row-selector='true']").jHueRowSelector();
 
-    $("#id_database").chosen().change(function () {
-      $.cookie("hueBeeswaxLastDatabase", $(this).val(), {expires: 90});
-      $('#db_form').submit();
-    });
+      $("a[data-table]").on("click", function () {
+        var _link = $(this);
+        var statsUrl = "/beeswax/api/table/${database}/" + _link.data("table") + "/stats/";
+        var refreshUrl = "/beeswax/api/analyze/${database}/" + _link.data("table") + "/";
+        $("#tableAnalysisStats .content").html("<i class='fa fa-spinner fa-spin'></i>");
+        $("#tableAnalysis").show().css("top", _link.position().top - $("#tableAnalysis").outerHeight() / 2 + _link.outerHeight() / 2).css("left", _link.position().left + _link.outerWidth());
+        showTableStats(statsUrl, refreshUrl, _link.data("table"), STATS_PROBLEMS, function () {
+          $("#tableAnalysis").show().css("top", _link.position().top - $("#tableAnalysis").outerHeight() / 2 + _link.outerHeight() / 2).css("left", _link.position().left + _link.outerWidth());
+        });
+      });
 
-    $(".selectAll").click(function () {
-      if ($(this).attr("checked")) {
-        $(this).removeAttr("checked").removeClass("fa-check");
-        $("." + $(this).data("selectables")).removeClass("fa-check").removeAttr("checked");
-      }
-      else {
-        $(this).attr("checked", "checked").addClass("fa-check");
-        $("." + $(this).data("selectables")).addClass("fa-check").attr("checked", "checked");
-      }
-      toggleActions();
-    });
+      $(document).on("click", "#tableAnalysis .close-popover", function () {
+        $("#tableAnalysis").hide();
+      });
 
-    $(".tableCheck").click(function () {
-      if ($(this).attr("checked")) {
-        $(this).removeClass("fa-check").removeAttr("checked");
-      }
-      else {
-        $(this).addClass("fa-check").attr("checked", "checked");
-      }
-      $(".selectAll").removeAttr("checked").removeClass("fa-check");
-      toggleActions();
-    });
+      $(".selectAll").click(function () {
+        if ($(this).attr("checked")) {
+          $(this).removeAttr("checked").removeClass("fa-check");
+          $("." + $(this).data("selectables")).removeClass("fa-check").removeAttr("checked");
+        }
+        else {
+          $(this).attr("checked", "checked").addClass("fa-check");
+          $("." + $(this).data("selectables")).addClass("fa-check").attr("checked", "checked");
+        }
+        toggleActions();
+      });
 
-    function toggleActions() {
-      $(".toolbarBtn").attr("disabled", "disabled");
-      var selector = $(".hueCheckbox[checked='checked']");
-      if (selector.length == 1) {
-        if (selector.data("view-url")) {
-          $("#viewBtn").removeAttr("disabled").click(function () {
-            location.href = selector.data("view-url");
-          });
+      $(".tableCheck").click(function () {
+        if ($(this).attr("checked")) {
+          $(this).removeClass("fa-check").removeAttr("checked");
         }
-        if (selector.data("browse-url")) {
-          $("#browseBtn").removeAttr("disabled").click(function () {
-            location.href = selector.data("browse-url")
-          });
+        else {
+          $(this).addClass("fa-check").attr("checked", "checked");
+        }
+        $(".selectAll").removeAttr("checked").removeClass("fa-check");
+        toggleActions();
+      });
+
+      function toggleActions() {
+        $(".toolbarBtn").attr("disabled", "disabled");
+        var selector = $(".hueCheckbox[checked='checked']");
+        if (selector.length == 1) {
+          if (selector.data("view-url")) {
+            $("#viewBtn").removeAttr("disabled").click(function () {
+              location.href = selector.data("view-url");
+            });
+          }
+          if (selector.data("browse-url")) {
+            $("#browseBtn").removeAttr("disabled").click(function () {
+              location.href = selector.data("browse-url")
+            });
+          }
+        }
+        if (selector.length >= 1) {
+          $("#dropBtn").removeAttr("disabled");
         }
       }
-      if (selector.length >= 1) {
-        $("#dropBtn").removeAttr("disabled");
-      }
-    }
 
-    $("#dropBtn").click(function () {
-      $.getJSON("${ url('metastore:drop_table', database=database) }", function (data) {
-        $("#dropTableMessage").text(data.title);
-      });
-      var _tempList = [];
-      $(".hueCheckbox[checked='checked']").each(function (index) {
-        _tempList.push($(this).data("drop-name"));
+      $("#dropBtn").click(function () {
+        $.getJSON("${ url('metastore:drop_table', database=database) }", function (data) {
+          $("#dropTableMessage").text(data.title);
+        });
+        var _tempList = [];
+        $(".hueCheckbox[checked='checked']").each(function (index) {
+          _tempList.push($(this).data("drop-name"));
+        });
+        viewModel.chosenTables.removeAll();
+        viewModel.chosenTables(_tempList);
+        $("#dropTable").modal("show");
       });
-      viewModel.chosenTables.removeAll();
-      viewModel.chosenTables(_tempList);
-      $("#dropTable").modal("show");
     });
   });
+
 </script>
 
 ${ commonfooter(messages) | n,unicode }