浏览代码

HUE-6176 [frontend] Integrate dynamic configuration in the assist panels

Romain Rigaux 8 年之前
父节点
当前提交
f281e8f

+ 1 - 1
desktop/core/src/desktop/api2.py

@@ -67,7 +67,7 @@ def api_error_handler(func):
 @api_error_handler
 def get_config(request):
   app_config = ClusterConfig(request.user).get_apps()
-  
+
   return JsonResponse({
     'status': 0,
     'app_config': app_config,

+ 36 - 60
desktop/core/src/desktop/models.py

@@ -1500,29 +1500,26 @@ class Document2Permission(models.Model):
     return self.groups.filter(id__in=user.groups.all()).exists() or user in self.users.all()
 
 
-
-
 class ClusterConfig():
-  
+
   def __init__(self, user, apps=None):
     self.user = user
     self.apps = appmanager.get_apps_dict(self.user) if apps is None else apps
-  
-  def setConfig(self):
-    # dataeng Execute  in Hive by default
-    #         JB: hide yarn, show dataeng
-    # Nav, NavOpt?
-    # reload "some ini sections"
+
+
+  def refreshConfig(self):
+    # TODO: reload "some ini sections"
     pass
-  
+
+
   @property
   def main_quick_action(self):
-    return self._get_editor()['interpreters'][1]
-  
-  
+    return self._get_editor()['interpreters'][1] # TODO handle default and user personal one
+
+
   def _get_editor(self):
     interpreters = []
-    
+
     if SHOW_NOTEBOOKS.get():
       interpreters.append({
         'name': 'notebook',
@@ -1530,8 +1527,8 @@ class ClusterConfig():
         'displayName': 'Notebook',
         'tooltip': _('Notebook'),
         'page': '/notebook'
-      })    
-    
+      })
+
     for interpreter in get_ordered_interpreters(self.user):
       interpreters.append({
         'name': interpreter['name'],
@@ -1541,20 +1538,19 @@ class ClusterConfig():
         'page': '/editor/?type=%(type)s' % interpreter,
       })
 
-    return {
-        'name': 'editor',
-        'displayName': _('Editor'),
-        'interpreters': interpreters 
+    if interpreters:
+      return {
+          'name': 'editor',
+          'displayName': _('Editor'),
+          'interpreters': interpreters
       }
+    else:
+      return None
 
   def _get_dashboard(self):
     interpreters = [] # TODO Integrate SQL Dashboards and Solr 6 configs
-#     'interpreters': [
-#           {'solr': {}},
-#           {'impala': {}}
-#         ]    
-    
-    if IS_DASHBOARD_ENABLED.get():    
+
+    if IS_DASHBOARD_ENABLED.get():
       return {
           'name': 'dashboard',
           'displayName': _('Dashboard'),
@@ -1563,10 +1559,10 @@ class ClusterConfig():
         }
     else:
       return None
-  
+
   def _get_browser(self):
     interpreters = []
-   
+
     if 'filebrowser' in self.apps:
       interpreters.append({
         'type': 'hdfs',
@@ -1574,7 +1570,7 @@ class ClusterConfig():
         'tooltip': _('Files'),
         'page': '/filebrowser/'
       })
-  
+
     if is_s3_enabled() and has_s3_access(self.user):
       interpreters.append({
         'type': 's3',
@@ -1582,7 +1578,7 @@ class ClusterConfig():
         'tooltip': _('S3'),
         'page': '/filebrowser/view=S3A://'
       })
-        
+
     if 'metastore' in self.apps:
       interpreters.append({
         'type': 'tables',
@@ -1636,11 +1632,12 @@ class ClusterConfig():
           'name': 'browser',
           'displayName': _('Browsers'),
           'interpreters': interpreters,
+          'interpreter_names': [interpreter['type'] for interpreter in interpreters],
         }
     else:
       return None
-  
-  
+
+
   def _get_scheduler(self):
     interpreters = [{
         'type': 'oozie-workflow',
@@ -1667,14 +1664,14 @@ class ClusterConfig():
           'interpreters': interpreters,
         }
     else:
-      return None  
+      return None
 
 
   def _get_sdk_apps(self):
-    current_app, other_apps, apps_list = _get_apps(self.user)  
+    current_app, other_apps, apps_list = _get_apps(self.user)
 
     interpreters = []
-    
+
     for other in other_apps:
       interpreters.push({
         'type': other.nice_name,
@@ -1691,10 +1688,10 @@ class ClusterConfig():
         }
     else:
       return None
-  
-  
+
+
   def get_apps(self):
-    apps = OrderedDict([      
+    apps = OrderedDict([
       ('editor', self._get_editor()),
       ('dashboard', self._get_dashboard()),
       ('browser', self._get_browser()),
@@ -1702,27 +1699,6 @@ class ClusterConfig():
       ('sdkapps', self._get_sdk_apps()),
     ])
 
-    # Default action
-    # If not in user setting, first interpreter in apps
-#     default_app = None    
-#     if apps['editor'] and apps['editor']['interpreters']:
-#       if 'impala' in apps['editor']['interpreters']:
-#         default_app = apps['editor']['interpreters']['impala']
-#       elif 'hive' in apps['editor']['interpreters']:
-#         default_app = apps['editor']['interpreters']['hive']
-#       elif 'notebook' in apps['editor']['interpreters']:
-#         default_app = apps['editor']['interpreters']['notebook']
-#     elif apps['dashboard'] and apps['dashboard']['interpreters']:
-#       default_app = apps['dashboard']['interpreters'][0]
-#     elif apps['browser'] and apps['browser']['interpreters']:
-#       default_app = apps['browser']['interpreters'][0]
-#     elif apps['scheduler'] and apps['scheduler']['interpreters']:
-#       default_app = apps['scheduler']['interpreters'][0]
-#     
-#     
-#     
-#     default_app['isDefault'] = True
-    
     return apps
 
 
@@ -1741,7 +1717,7 @@ def _get_apps(user, section=None):
         current_app = app
   else:
     apps_list = []
-    
+
   return current_app, other_apps, apps_list
 
 

+ 5 - 0
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -55,6 +55,7 @@ var ApiHelper = (function () {
   var SAMPLE_API_PREFIX = "/notebook/api/sample/";
   var DOCUMENTS_API = "/desktop/api2/doc/";
   var DOCUMENTS_SEARCH_API = "/desktop/api2/docs/";
+  var FETCH_CONFIG = '/desktop/api2/get_config/';
   var HDFS_API_PREFIX = "/filebrowser/view=";
   var GIT_API_PREFIX = "/desktop/api/vcs/contents/";
   var S3_API_PREFIX = "/filebrowser/view=S3A://";
@@ -1422,6 +1423,10 @@ var ApiHelper = (function () {
     }));
   };
 
+  ApiHelper.prototype.getClusterConfig = function (data) {
+    return $.post(FETCH_CONFIG, data);
+  };
+
   /**
    * Fetches a navigator entity for the given identifierChain
    *

+ 124 - 106
desktop/core/src/desktop/templates/assist.mako

@@ -821,7 +821,7 @@ from notebook.conf import get_ordered_interpreters
 
   <script type="text/html" id="assist-panel-template">
     <div class="assist-panel">
-      <!-- ko if: availablePanels.length > 1 -->
+      <!-- ko if: availablePanels().length > 1 -->
       <div class="assist-panel-switches">
         <!-- ko foreach: availablePanels -->
         <div class="inactive-action assist-type-switch" data-bind="click: function () { $parent.visiblePanel($data); }, css: { 'blue': $parent.visiblePanel() === $data }, style: { 'float': rightAlignIcon ? 'right' : 'left' },  attr: { 'title': name }">
@@ -832,7 +832,7 @@ from notebook.conf import get_ordered_interpreters
       <!-- /ko -->
       <!-- ko with: visiblePanel -->
       <!-- ko template: { if: showNavSearch && $parent.navigatorSearch.navigatorEnabled(), name: 'assist-panel-navigator-search', data: $parent }--><!-- /ko -->
-      <div class="assist-panel-contents" data-bind="style: { 'padding-top': $parent.availablePanels.length > 1 ? '10px' : '5px' }">
+      <div class="assist-panel-contents" data-bind="style: { 'padding-top': $parent.availablePanels().length > 1 ? '10px' : '5px' }">
         <div class="assist-inner-panel">
           <div class="assist-flex-panel">
             <!-- ko template: { name: templateName, data: panelData } --><!-- /ko -->
@@ -1411,122 +1411,140 @@ from notebook.conf import get_ordered_interpreters
 
         self.tabsEnabled = '${ USE_NEW_SIDE_PANELS.get() }' === 'True';
 
-        self.availablePanels = [
-        % if get_ordered_interpreters():
-          new AssistInnerPanel({
-            panelData: new AssistDbPanel($.extend({
-              apiHelper: self.apiHelper,
-              i18n: i18n
-            }, params.sql)),
-            apiHelper: self.apiHelper,
-            name: '${ _("SQL") }',
-            type: 'sql',
-            icon: 'fa-database',
-            minHeight: 75
-          })
-        ];
-        % endif
-
-        if (self.tabsEnabled) {
-          <%
-            try:
-              apps
-            except NameError:
-              apps = appmanager.get_apps_dict(user)
-          %>
-          % if 'filebrowser' in apps:
-          self.availablePanels.push(new AssistInnerPanel({
-            panelData: new AssistHdfsPanel({
-              apiHelper: self.apiHelper
-            }),
-            apiHelper: self.apiHelper,
-            name: '${ _("HDFS") }',
-            type: 'hdfs',
-            icon: 'fa-folder-o',
-            minHeight: 50
-          }));
-          % endif
-
-          if (window.IS_S3_ENABLED) { // coming from common_header.mako
-            self.availablePanels.push(new AssistInnerPanel({
-              panelData: new AssistS3Panel({
-                apiHelper: self.apiHelper
-              }),
-              apiHelper: self.apiHelper,
-              name: '${ _("S3") }',
-              type: 's3',
-              icon: 'fa-cubes',
-              minHeight: 50
-            }));
+        self.availablePanels = ko.pureComputed(function() {
+          var panels = [];
+          var appConfig = hueDebug.viewModel($('.top-nav')[0]).clusterConfig() && hueDebug.viewModel($('.top-nav')[0]).clusterConfig()['app_config']; // TODO clean-up
+
+          if (! appConfig) { // TODO handle no panel and return []
+            return [new AssistInnerPanel({
+                panelData: new AssistDbPanel($.extend({
+                  apiHelper: self.apiHelper,
+                  i18n: i18n
+                }, params.sql)),
+                apiHelper: self.apiHelper,
+                name: '${ _("SQL") }',
+                type: 'sql',
+                icon: 'fa-database',
+                minHeight: 75
+              })];
           }
 
-          % if 'search' in apps:
-          self.availablePanels.push(new AssistInnerPanel({
-            panelData: new AssistCollectionsPanel({
-              apiHelper: self.apiHelper
-            }),
-            apiHelper: self.apiHelper,
-            name: '${ _("Collections") }',
-            type: 'collections',
-            icon: 'fa-search-plus',
-            minHeight: 50,
-            showNavSearch: false
-          }));
-          % endif
-
-          % if 'hbase' in apps:
-          self.availablePanels.push(new AssistInnerPanel({
-            panelData: new AssistHBasePanel({
-              apiHelper: self.apiHelper
-            }),
-            apiHelper: self.apiHelper,
-            name: '${ _("HBase") }',
-            type: 'hbase',
-            icon: 'fa-th-large',
-            minHeight: 50,
-            showNavSearch: false
-          }));
-          % endif
-
-          self.availablePanels.push(new AssistInnerPanel({
-            panelData: new AssistDocumentsPanel({
-              user: params.user,
-              apiHelper: self.apiHelper,
-              i18n: i18n
-            }),
-            apiHelper: self.apiHelper,
-            name: '${ _("Documents") }',
-            type: 'documents',
-            icon: 'fa-files-o',
-            minHeight: 50,
-            rightAlignIcon: true,
-            visible: params.visibleAssistPanels && params.visibleAssistPanels.indexOf('documents') !== -1
-          }));
-
-          if (${ len(VCS.keys()) } > 0) {
-            self.availablePanels.push(new AssistInnerPanel({
-              panelData: new AssistGitPanel({
-                apiHelper: self.apiHelper
+          if (appConfig['editor']) {
+            panels.push(
+              new AssistInnerPanel({
+                panelData: new AssistDbPanel($.extend({
+                  apiHelper: self.apiHelper,
+                  i18n: i18n
+                }, params.sql)),
+                apiHelper: self.apiHelper,
+                name: '${ _("SQL") }',
+                type: 'sql',
+                icon: 'fa-database',
+                minHeight: 75
+              })
+            );
+          }
+
+          if (self.tabsEnabled) {
+
+            if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('hdfs') != -1) {
+              panels.push(new AssistInnerPanel({
+                panelData: new AssistHdfsPanel({
+                  apiHelper: self.apiHelper
+                }),
+                apiHelper: self.apiHelper,
+                name: '${ _("HDFS") }',
+                type: 'hdfs',
+                icon: 'fa-folder-o',
+                minHeight: 50
+              }));
+            }
+
+            if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('s3') != -1) {
+              panels.push(new AssistInnerPanel({
+                panelData: new AssistS3Panel({
+                  apiHelper: self.apiHelper
+                }),
+                apiHelper: self.apiHelper,
+                name: '${ _("S3") }',
+                type: 's3',
+                icon: 'fa-cubes',
+                minHeight: 50
+              }));
+            }
+
+            if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('indexes') != -1) {
+              panels.push(new AssistInnerPanel({
+                panelData: new AssistCollectionsPanel({
+                  apiHelper: self.apiHelper
+                }),
+                apiHelper: self.apiHelper,
+                name: '${ _("Collections") }',
+                type: 'collections',
+                icon: 'fa-search-plus',
+                minHeight: 50,
+                showNavSearch: false
+              }));
+            }
+
+            if (appConfig['browser'] && appConfig['browser']['interpreter_names'].indexOf('hbase') != -1) {
+              panels.push(new AssistInnerPanel({
+                panelData: new AssistHBasePanel({
+                  apiHelper: self.apiHelper
+                }),
+                apiHelper: self.apiHelper,
+                name: '${ _("HBase") }',
+                type: 'hbase',
+                icon: 'fa-th-large',
+                minHeight: 50,
+                showNavSearch: false
+              }));
+            }
+
+            panels.push(new AssistInnerPanel({
+              panelData: new AssistDocumentsPanel({
+                user: params.user,
+                apiHelper: self.apiHelper,
+                i18n: i18n
               }),
               apiHelper: self.apiHelper,
-              name: '${ _("Git") }',
-              type: 'git',
-              icon: 'fa-github',
+              name: '${ _("Documents") }',
+              type: 'documents',
+              icon: 'fa-files-o',
               minHeight: 50,
-              showNavSearch: false,
-              rightAlignIcon: true
+              rightAlignIcon: true,
+              visible: params.visibleAssistPanels && params.visibleAssistPanels.indexOf('documents') !== -1
             }));
+
+            if (${ len(VCS.keys()) } > 0) {
+              panels.push(new AssistInnerPanel({
+                panelData: new AssistGitPanel({
+                  apiHelper: self.apiHelper
+                }),
+                apiHelper: self.apiHelper,
+                name: '${ _("Git") }',
+                type: 'git',
+                icon: 'fa-github',
+                minHeight: 50,
+                showNavSearch: false,
+                rightAlignIcon: true
+              }));
+            }
+
           }
-        }
 
-        var lastOpenPanelType = self.apiHelper.getFromTotalStorage('assist', 'last.open.panel', self.availablePanels[0].type);
+          return panels;
+        });
+
+        // TODO handle panel reloading
+        var lastOpenPanelType = self.apiHelper.getFromTotalStorage('assist', 'last.open.panel', self.availablePanels()[0].type);
 
-        var lastFoundPanel = self.availablePanels.filter(function (panel) { return panel.type === lastOpenPanelType });
-        var dbPanel = self.availablePanels.filter(function (panel) { return panel.type === 'sql' });
+        var lastFoundPanel = self.availablePanels().filter(function (panel) { return panel.type === lastOpenPanelType });
+        var dbPanel = self.availablePanels().filter(function (panel) { return panel.type === 'sql' });
         if (lastFoundPanel.length === 1) {
           dbPanel[0].panelData.init(); // always forces the db panel to load
         }
-        self.visiblePanel = ko.observable(lastFoundPanel.length === 1 ? lastFoundPanel[0] : self.availablePanels[0]);
+        self.visiblePanel = ko.observable(lastFoundPanel.length === 1 ? lastFoundPanel[0] : self.availablePanels()[0]);
 
         self.visiblePanel().panelData.init();
 

+ 39 - 46
desktop/core/src/desktop/templates/hue.mako

@@ -947,25 +947,19 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
         self.searchInput = ko.observable();
 
 
-        self.config = ko.observable();
-        
-        self.getConfig = function() {
-          $.post("/desktop/api2/get_config/", {
-            cluster: ko.mapping.toJSON('default'),
-          }, function (data) {
-            if (data.status == 0) {
-              self.config(data);
-            } else {
-              $(document).trigger("error", data.message);
-            }
-          });
-        };
+        self.clusterConfig = ko.observable();
 
-        self.getConfig();
+        self.apiHelper.getClusterConfig().done(function (data) {
+          if (data.status == 0) {
+            self.clusterConfig(data);
+          } else {
+            $(document).trigger("error", data.message);
+          }
+        });
 
-        self.mainQuickCreateAction = ko.computed(function() {          
-          if (self.config()) {
-            var topApp = self.config()['main_button_action'];
+        self.mainQuickCreateAction = ko.pureComputed(function() {
+          if (self.clusterConfig()) {
+            var topApp = self.clusterConfig()['main_button_action'];
             return {
               displayName: topApp.displayName,
               icon: topApp.type,
@@ -977,12 +971,12 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
             return null;
           }
         });
-        
 
-        self.quickCreateActions = ko.computed(function() {
+
+        self.quickCreateActions = ko.pureComputed(function() {
           var apps = [];
-          if (self.config()) {
-          $.each(self.config()['button_actions'], function(index, app) {
+          if (self.clusterConfig()) {
+          $.each(self.clusterConfig()['button_actions'], function(index, app) {
             var interpreters = [];
             $.each(app['interpreters'], function(index, interpreter) {
               interpreters.push({
@@ -996,7 +990,7 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
                 }
               });
             });
-            
+
             % if user.is_superuser:
             if (app.name == 'editor') {
               interpreters.push({
@@ -1008,7 +1002,7 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
              });
             }
             % endif
-          
+
             apps.push({
               displayName: app.displayName,
               icon: app.name,
@@ -1020,11 +1014,11 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
             });
           });
           }
-                                                           
+
           return apps;
         });
-        
-       
+
+
 
         self.searchAutocompleteSource = function (request, callback) {
           // TODO: Extract complete contents to common module (shared with nav search autocomplete)
@@ -1115,31 +1109,30 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
 
     (function (onePageViewModel, topNavViewModel) {
       function SideBarViewModel () {
-        //self.items = [];
-        
-        self.items = ko.computed(function() {
+
+        self.items = ko.pureComputed(function() {
           var items = [];
-          
+
           items.push({
             displayName: '${ _('Documents') }',
             click: function () {
               page('/home')
             }
           });
-          
-          var appConfig = hueDebug.viewModel($('.top-nav')[0]).config() && hueDebug.viewModel($('.top-nav')[0]).config()['app_config'];
-          
-          if (! appConfig) {
+
+          var clusterConfig = topNavViewModel.clusterConfig();
+
+          if (! clusterConfig) {
             return items;
           }
-                                              
+
           var appsItems = [];
           $.each(['editor', 'dashboard', 'scheduler'], function(index, appName) {
-            if (appConfig[appName]) {
+            if (clusterConfig[appName]) {
               appsItems.push({
-                displayName: appConfig[appName]['displayName'],
+                displayName: clusterConfig[appName]['displayName'],
                 click: function () {
-                  page(appConfig[appName]['page']);
+                  page(clusterConfig[appName]['page']);
                 }
               });
             };
@@ -1151,10 +1144,10 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
               children: appsItems
             })
           }
-          
+
           var browserItems = [];
-          if (appConfig['browser']) {
-            $.each(appConfig['browser']['interpreters'], function(index, browser) {
+          if (clusterConfig['browser']) {
+            $.each(clusterConfig['browser']['interpreters'], function(index, browser) {
               browserItems.push({
                 displayName: browser['displayName'],
                 click: function () {
@@ -1170,10 +1163,10 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
               children: browserItems
             })
           }
-                     
+
           var sdkItems = [];
-          if (appConfig['sdkapps']) {
-            $.each(appConfig['sdkapps']['interpreters'], function(index, browser) {
+          if (clusterConfig['sdkapps']) {
+            $.each(clusterConfig['sdkapps']['interpreters'], function(index, browser) {
               sdkItems.push({
                 displayName: browser['displayName'],
                 click: function () {
@@ -1185,11 +1178,11 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
           if (sdkItems.length > 0) {
             items.push({
               isCategory: true,
-              displayName: appConfig['sdkapps']['displayName'],
+              displayName: clusterConfig['sdkapps']['displayName'],
               children: sdkItems
             })
           }
-          
+
           return items;
         });
 

+ 29 - 29
desktop/libs/notebook/src/notebook/conf.py

@@ -142,12 +142,12 @@ ENABLE_BATCH_EXECUTE = Config(
 def _default_interpreters(user):
   interpreters = []
   apps = appmanager.get_apps_dict(user)
-  
+
   if 'impala' in apps:
     interpreters.append(('hive', {
       'name': 'Hive', 'interface': 'hiveserver2', 'options': {}
     }),)
-  
+
   if 'impala' in apps:
     interpreters.append(('impala', {
       'name': 'Impala', 'interface': 'hiveserver2', 'options': {}
@@ -157,37 +157,37 @@ def _default_interpreters(user):
     interpreters.append(('pig', {
       'name': 'Pig', 'interface': 'oozie', 'options': {}
     }))
-  
-  if 'oozie' in apps:  
+
+  if 'oozie' in apps:
     interpreters.extend(
-        ('pig', {
-            'name': 'Pig', 'interface': 'oozie', 'options': {}
-        }),
-        ('java', {
-            'name': 'Java', 'interface': 'oozie', 'options': {}
-        }),
-        ('sqoop1', {
-            'name': 'Sqoop 1', 'interface': 'oozie', 'options': {}
-        }),
-        ('distcp', {
-            'name': 'Distcp', 'interface': 'oozie', 'options': {}
-        }),
-        ('spark2', {
-            'name': 'Spark', 'interface': 'oozie', 'options': {}
-        }),
-        ('mapreduce', {
-            'name': 'MapReduce', 'interface': 'oozie', 'options': {}
-        }),
-        ('shell', {
-            'name': 'Shell', 'interface': 'oozie', 'options': {}
-        }),
-      )
-  
+      ('pig', {
+          'name': 'Pig', 'interface': 'oozie', 'options': {}
+      }),
+      ('java', {
+          'name': 'Java', 'interface': 'oozie', 'options': {}
+      }),
+      ('sqoop1', {
+          'name': 'Sqoop 1', 'interface': 'oozie', 'options': {}
+      }),
+      ('distcp', {
+          'name': 'Distcp', 'interface': 'oozie', 'options': {}
+      }),
+      ('spark2', {
+          'name': 'Spark', 'interface': 'oozie', 'options': {}
+      }),
+      ('mapreduce', {
+          'name': 'MapReduce', 'interface': 'oozie', 'options': {}
+      }),
+      ('shell', {
+          'name': 'Shell', 'interface': 'oozie', 'options': {}
+      }),
+    )
+
   if 'serch' in apps: # And Solr 6+
     interpreters.append(('solr', {
         'name': 'Solr SQL', 'interface': 'solr', 'options': {}
     }),)
-  
+
   if SHOW_NOTEBOOKS.get():
     interpreters.extend(
       ('spark', {
@@ -212,5 +212,5 @@ def _default_interpreters(user):
           'name': 'Markdown', 'interface': 'text', 'options': {}
       })
     )
-  
+
   INTERPRETERS.set_for_testing(OrderedDict(interpreters))