Ver código fonte

HUE-9207 [frontend] Extract optimizer logic from the ApiHelper

Johan Ahlen 5 anos atrás
pai
commit
2abca579ed

+ 0 - 183
desktop/core/src/desktop/js/api/apiHelper.js

@@ -83,39 +83,6 @@ export const LINK_SHARING_PERMS = {
   OFF: 'off'
   OFF: 'off'
 };
 };
 
 
-/**
- * Fetches the popularity for various aspects of the given tables
- *
- * @param {ApiHelper} apiHelper
- * @param {Object} options
- * @param {boolean} [options.silenceErrors]
- * @param {string[][]} options.paths
- * @param {string} url
- * @return {CancellablePromise}
- */
-const genericOptimizerMultiTableFetch = (apiHelper, options, url) => {
-  const deferred = $.Deferred();
-
-  const dbTables = {};
-  options.paths.forEach(path => {
-    dbTables[path.join('.')] = true;
-  });
-  const data = {
-    dbTables: ko.mapping.toJSON(Object.keys(dbTables))
-  };
-
-  const request = simplePost(url, data, {
-    silenceErrors: options.silenceErrors,
-    successCallback: data => {
-      data.hueTimestamp = Date.now();
-      deferred.resolve(data);
-    },
-    errorCallback: deferred.reject
-  });
-
-  return new CancellablePromise(deferred, request);
-};
-
 /**
 /**
  * Wrapper around the response from the Query API
  * Wrapper around the response from the Query API
  *
  *
@@ -1828,14 +1795,6 @@ class ApiHelper {
     return simplePost('/notebook/api/fetch_result_size', data);
     return simplePost('/notebook/api/fetch_result_size', data);
   }
   }
 
 
-  statementRisk(options) {
-    const data = {
-      notebook: options.notebookJson,
-      snippet: options.snippetJson
-    };
-    return simplePost('/notebook/api/optimizer/statement/risk', data);
-  }
-
   getLogs(options) {
   getLogs(options) {
     const data = {
     const data = {
       notebook: options.notebookJson,
       notebook: options.notebookJson,
@@ -1847,25 +1806,6 @@ class ApiHelper {
     return simplePost('/notebook/api/get_logs', data);
     return simplePost('/notebook/api/get_logs', data);
   }
   }
 
 
-  statementCompatibility(options) {
-    const data = {
-      notebook: options.notebookJson,
-      snippet: options.snippetJson,
-      sourcePlatform: options.sourcePlatform,
-      targetPlatform: options.targetPlatform
-    };
-    return simplePost('/notebook/api/optimizer/statement/compatibility', data);
-  }
-
-  statementSimilarity(options) {
-    const data = {
-      notebook: options.notebookJson,
-      snippet: options.snippetJson,
-      sourcePlatform: options.sourcePlatform
-    };
-    return simplePost('/notebook/api/optimizer/statement/similarity', data);
-  }
-
   async saveNotebook(options) {
   async saveNotebook(options) {
     const data = {
     const data = {
       notebook: options.notebookJson,
       notebook: options.notebookJson,
@@ -2465,129 +2405,6 @@ class ApiHelper {
     });
     });
   }
   }
 
 
-  /**
-   * Fetches optimizer popularity for the children of the given path
-   *
-   * @param {Object} options
-   * @param {boolean} [options.silenceErrors]
-   * @param {string[][]} options.paths
-   * @return {CancellablePromise}
-   */
-  fetchOptimizerPopularity(options) {
-    const deferred = $.Deferred();
-    let url, data;
-
-    if (options.paths.length === 1 && options.paths[0].length === 1) {
-      url = NAV_OPT_URLS.TOP_TABLES;
-      data = {
-        database: options.paths[0][0]
-      };
-    } else {
-      url = NAV_OPT_URLS.TOP_COLUMNS;
-      const dbTables = [];
-      options.paths.forEach(path => {
-        dbTables.push(path.join('.'));
-      });
-      data = {
-        dbTables: ko.mapping.toJSON(dbTables)
-      };
-    }
-
-    const request = simplePost(url, data, {
-      silenceErrors: options.silenceErrors,
-      successCallback: data => {
-        data.hueTimestamp = Date.now();
-        deferred.resolve(data);
-      },
-      errorCallback: deferred.reject
-    });
-
-    return new CancellablePromise(deferred, request);
-  }
-
-  /**
-   * Fetches the popular aggregate functions for the given tables
-   *
-   * @param {Object} options
-   * @param {boolean} [options.silenceErrors]
-   * @param {string[][]} options.paths
-   * @return {CancellablePromise}
-   */
-  fetchOptimizerTopAggs(options) {
-    return genericOptimizerMultiTableFetch(this, options, NAV_OPT_URLS.TOP_AGGS);
-  }
-
-  /**
-   * Fetches the popular columns for the given tables
-   *
-   * @param {Object} options
-   * @param {boolean} [options.silenceErrors]
-   * @param {string[][]} options.paths
-   * @return {CancellablePromise}
-   */
-  fetchOptimizerTopColumns(options) {
-    return genericOptimizerMultiTableFetch(this, options, NAV_OPT_URLS.TOP_COLUMNS);
-  }
-
-  /**
-   * Fetches the popular filters for the given tables
-   *
-   * @param {Object} options
-   * @param {boolean} [options.silenceErrors]
-   * @param {string[][]} options.paths
-   * @return {CancellablePromise}
-   */
-  fetchOptimizerTopFilters(options) {
-    return genericOptimizerMultiTableFetch(this, options, NAV_OPT_URLS.TOP_FILTERS);
-  }
-
-  /**
-   * Fetches the popular joins for the given tables
-   *
-   * @param {Object} options
-   * @param {boolean} [options.silenceErrors]
-   * @param {string[][]} options.paths
-   * @return {CancellablePromise}
-   */
-  fetchOptimizerTopJoins(options) {
-    return genericOptimizerMultiTableFetch(this, options, NAV_OPT_URLS.TOP_JOINS);
-  }
-
-  /**
-   * Fetches optimizer meta for the given path, only possible for tables atm.
-   *
-   * @param {Object} options
-   * @param {boolean} [options.silenceErrors]
-   * @param {string[]} options.path
-   *
-   * @return {CancellablePromise}
-   */
-  fetchOptimizerMeta(options) {
-    const deferred = $.Deferred();
-
-    const request = simplePost(
-      NAV_OPT_URLS.TABLE_DETAILS,
-      {
-        databaseName: options.path[0],
-        tableName: options.path[1]
-      },
-      {
-        silenceErrors: options.silenceErrors,
-        successCallback: response => {
-          if (response.status === 0 && response.details) {
-            response.details.hueTimestamp = Date.now();
-            deferred.resolve(response.details);
-          } else {
-            deferred.reject();
-          }
-        },
-        errorCallback: deferred.reject
-      }
-    );
-
-    return new CancellablePromise(deferred, request);
-  }
-
   /**
   /**
    * @param {Object} options
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
    * @param {boolean} [options.silenceErrors]

+ 28 - 30
desktop/core/src/desktop/js/apps/notebook2/snippet.js

@@ -35,10 +35,7 @@ import huePubSub from 'utils/huePubSub';
 import hueUtils from 'utils/hueUtils';
 import hueUtils from 'utils/hueUtils';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
 import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
 import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
-import {
-  ACTIVE_SNIPPET_DIALECT_CHANGED_EVENT,
-  REDRAW_FIXED_HEADERS_EVENT
-} from 'apps/notebook2/events';
+import { REDRAW_FIXED_HEADERS_EVENT } from 'apps/notebook2/events';
 import { EXECUTABLE_UPDATED_EVENT, EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
 import { EXECUTABLE_UPDATED_EVENT, EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
 import {
 import {
   ACTIVE_STATEMENT_CHANGED_EVENT,
   ACTIVE_STATEMENT_CHANGED_EVENT,
@@ -48,6 +45,11 @@ import { EXECUTE_ACTIVE_EXECUTABLE_EVENT } from 'apps/notebook2/components/ko.ex
 import { UPDATE_HISTORY_EVENT } from 'apps/notebook2/components/ko.queryHistory';
 import { UPDATE_HISTORY_EVENT } from 'apps/notebook2/components/ko.queryHistory';
 import { GET_KNOWN_CONFIG_EVENT } from 'utils/hueConfig';
 import { GET_KNOWN_CONFIG_EVENT } from 'utils/hueConfig';
 import { cancelActiveRequest } from 'api/apiUtils';
 import { cancelActiveRequest } from 'api/apiUtils';
+import {
+  analyzeCompatibility,
+  analyzeRisk,
+  analyzeSimilarity
+} from 'catalog/optimizer/optimizerApiHelper';
 
 
 // TODO: Remove for ENABLE_NOTEBOOK_2. Temporary here for debug
 // TODO: Remove for ENABLE_NOTEBOOK_2. Temporary here for debug
 window.SqlExecutable = SqlExecutable;
 window.SqlExecutable = SqlExecutable;
@@ -913,11 +915,10 @@ export default class Snippet {
           return true;
           return true;
         });
         });
         if (unknownResponse) {
         if (unknownResponse) {
-          lastComplexityRequest = apiHelper
-            .statementRisk({
-              notebookJson: await this.parentNotebook.toContextJson(),
-              snippetJson: this.toContextJson()
-            })
+          lastComplexityRequest = analyzeRisk({
+            notebookJson: await this.parentNotebook.toContextJson(),
+            snippetJson: this.toContextJson()
+          })
             .then(data => {
             .then(data => {
               knownResponses.unshift({
               knownResponses.unshift({
                 hash: hash,
                 hash: hash,
@@ -1219,20 +1220,18 @@ export default class Snippet {
   async getSimilarQueries() {
   async getSimilarQueries() {
     hueAnalytics.log('notebook', 'get_query_similarity');
     hueAnalytics.log('notebook', 'get_query_similarity');
 
 
-    apiHelper
-      .statementSimilarity({
-        notebookJson: await this.parentNotebook.toContextJson(),
-        snippetJson: this.toContextJson(),
-        sourcePlatform: this.dialect()
-      })
-      .then(data => {
-        if (data.status === 0) {
-          // eslint-disable-next-line no-restricted-syntax
-          console.log(data.statement_similarity);
-        } else {
-          $(document).trigger('error', data.message);
-        }
-      });
+    analyzeSimilarity({
+      notebookJson: await this.parentNotebook.toContextJson(),
+      snippetJson: this.toContextJson(),
+      sourcePlatform: this.dialect()
+    }).then(data => {
+      if (data.status === 0) {
+        // eslint-disable-next-line no-restricted-syntax
+        console.log(data.statement_similarity);
+      } else {
+        $(document).trigger('error', data.message);
+      }
+    });
   }
   }
 
 
   handleAjaxError(data, callback) {
   handleAjaxError(data, callback) {
@@ -1352,13 +1351,12 @@ export default class Snippet {
     this.hasSuggestion(null);
     this.hasSuggestion(null);
     const positionStatement = this.positionStatement();
     const positionStatement = this.positionStatement();
 
 
-    this.lastCompatibilityRequest = apiHelper
-      .statementCompatibility({
-        notebookJson: await this.parentNotebook.toContextJson(),
-        snippetJson: this.toContextJson(),
-        sourcePlatform: this.compatibilitySourcePlatform().value,
-        targetPlatform: this.compatibilityTargetPlatform().value
-      })
+    this.lastCompatibilityRequest = analyzeCompatibility({
+      notebookJson: await this.parentNotebook.toContextJson(),
+      snippetJson: this.toContextJson(),
+      sourcePlatform: this.compatibilitySourcePlatform().value,
+      targetPlatform: this.compatibilityTargetPlatform().value
+    })
       .then(data => {
       .then(data => {
         if (data.status === 0) {
         if (data.status === 0) {
           this.aceErrorsHolder([]);
           this.aceErrorsHolder([]);

+ 7 - 4
desktop/core/src/desktop/js/catalog/catalogUtils.js

@@ -19,14 +19,17 @@ import apiHelper from 'api/apiHelper';
 /**
 /**
  * Wrapper function around ApiHelper calls, it will also save the entry on success.
  * Wrapper function around ApiHelper calls, it will also save the entry on success.
  *
  *
- * @param {string} apiHelperFunction - The name of the ApiHelper function to call
+ * @param {string|Function} apiHelperFunction - The name of the ApiHelper function to call
  * @param {string} attributeName - The attribute to set
  * @param {string} attributeName - The attribute to set
  * @param {DataCatalogEntry|MultiTableEntry} entry - The catalog entry
  * @param {DataCatalogEntry|MultiTableEntry} entry - The catalog entry
  * @param {Object} [apiOptions]
  * @param {Object} [apiOptions]
  * @param {boolean} [apiOptions.silenceErrors]
  * @param {boolean} [apiOptions.silenceErrors]
  */
  */
-const fetchAndSave = (apiHelperFunction, attributeName, entry, apiOptions) =>
-  apiHelper[apiHelperFunction]({
+const fetchAndSave = (apiHelperFunction, attributeName, entry, apiOptions) => {
+  const func =
+    typeof apiHelperFunction === 'string' ? apiHelper[apiHelperFunction] : apiHelperFunction;
+
+  return func({
     sourceType: entry.dataCatalog.sourceType,
     sourceType: entry.dataCatalog.sourceType,
     compute: entry.compute,
     compute: entry.compute,
     path: entry.path, // Set for DataCatalogEntry
     path: entry.path, // Set for DataCatalogEntry
@@ -37,7 +40,7 @@ const fetchAndSave = (apiHelperFunction, attributeName, entry, apiOptions) =>
     entry[attributeName] = data;
     entry[attributeName] = data;
     entry.saveLater();
     entry.saveLater();
   });
   });
-
+};
 /**
 /**
  * Helper function that adds sets the silence errors option to true if not specified
  * Helper function that adds sets the silence errors option to true if not specified
  *
  *

+ 5 - 6
desktop/core/src/desktop/js/catalog/dataCatalog.js

@@ -17,12 +17,12 @@
 import $ from 'jquery';
 import $ from 'jquery';
 import localforage from 'localforage';
 import localforage from 'localforage';
 
 
-import apiHelper from 'api/apiHelper';
 import CancellablePromise from 'api/cancellablePromise';
 import CancellablePromise from 'api/cancellablePromise';
 import catalogUtils from 'catalog/catalogUtils';
 import catalogUtils from 'catalog/catalogUtils';
 import DataCatalogEntry from 'catalog/dataCatalogEntry';
 import DataCatalogEntry from 'catalog/dataCatalogEntry';
 import GeneralDataCatalog from 'catalog/generalDataCatalog';
 import GeneralDataCatalog from 'catalog/generalDataCatalog';
 import MultiTableEntry from 'catalog/multiTableEntry';
 import MultiTableEntry from 'catalog/multiTableEntry';
+import { fetchPopularity } from './optimizer/optimizerApiHelper';
 
 
 const STORAGE_POSTFIX = window.LOGGED_USERNAME;
 const STORAGE_POSTFIX = window.LOGGED_USERNAME;
 const DATA_CATALOG_VERSION = 5;
 const DATA_CATALOG_VERSION = 5;
@@ -313,11 +313,10 @@ class DataCatalog {
       const loadDeferred = $.Deferred();
       const loadDeferred = $.Deferred();
       if (pathsToLoad.length) {
       if (pathsToLoad.length) {
         cancellablePromises.push(
         cancellablePromises.push(
-          apiHelper
-            .fetchOptimizerPopularity({
-              silenceErrors: options.silenceErrors,
-              paths: pathsToLoad
-            })
+          fetchPopularity({
+            silenceErrors: options.silenceErrors,
+            paths: pathsToLoad
+          })
             .done(data => {
             .done(data => {
               const perTable = {};
               const perTable = {};
 
 

+ 7 - 7
desktop/core/src/desktop/js/catalog/dataCatalogEntry.js

@@ -22,6 +22,7 @@ import CancellablePromise from 'api/cancellablePromise';
 import catalogUtils from 'catalog/catalogUtils';
 import catalogUtils from 'catalog/catalogUtils';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 import I18n from 'utils/i18n';
+import { fetchOptimizerMeta, fetchPopularity } from './optimizer/optimizerApiHelper';
 
 
 /**
 /**
  * Helper function to reload the source meta for the given entry
  * Helper function to reload the source meta for the given entry
@@ -154,7 +155,7 @@ const reloadOptimizerMeta = function(dataCatalogEntry, apiOptions) {
   if (dataCatalogEntry.dataCatalog.canHaveOptimizerMeta()) {
   if (dataCatalogEntry.dataCatalog.canHaveOptimizerMeta()) {
     return dataCatalogEntry.trackedPromise(
     return dataCatalogEntry.trackedPromise(
       'optimizerMetaPromise',
       'optimizerMetaPromise',
-      catalogUtils.fetchAndSave('fetchOptimizerMeta', 'optimizerMeta', dataCatalogEntry, apiOptions)
+      catalogUtils.fetchAndSave(fetchOptimizerMeta, 'optimizerMeta', dataCatalogEntry, apiOptions)
     );
     );
   }
   }
   dataCatalogEntry.optimizerMetaPromise = $.Deferred.reject().promise();
   dataCatalogEntry.optimizerMetaPromise = $.Deferred.reject().promise();
@@ -752,12 +753,11 @@ class DataCatalogEntry {
       );
       );
     } else if (self.isDatabase() || self.isTableOrView()) {
     } else if (self.isDatabase() || self.isTableOrView()) {
       cancellablePromises.push(
       cancellablePromises.push(
-        apiHelper
-          .fetchOptimizerPopularity({
-            silenceErrors: options && options.silenceErrors,
-            refreshCache: options && options.refreshCache,
-            paths: [self.path]
-          })
+        fetchPopularity({
+          silenceErrors: options && options.silenceErrors,
+          refreshCache: options && options.refreshCache,
+          paths: [self.path]
+        })
           .done(data => {
           .done(data => {
             cancellablePromises.push(
             cancellablePromises.push(
               self
               self

+ 12 - 24
desktop/core/src/desktop/js/catalog/multiTableEntry.js

@@ -17,6 +17,12 @@
 import $ from 'jquery';
 import $ from 'jquery';
 
 
 import catalogUtils from 'catalog/catalogUtils';
 import catalogUtils from 'catalog/catalogUtils';
+import {
+  fetchTopAggs,
+  fetchTopColumns,
+  fetchTopFilters,
+  fetchTopJoins
+} from './optimizer/optimizerApiHelper';
 
 
 /**
 /**
  * Helper function to reload a Optimizer multi table attribute, like topAggs or topFilters
  * Helper function to reload a Optimizer multi table attribute, like topAggs or topFilters
@@ -26,7 +32,7 @@ import catalogUtils from 'catalog/catalogUtils';
  * @param {boolean} [options.silenceErrors] - Default true
  * @param {boolean} [options.silenceErrors] - Default true
  * @param {string} promiseAttribute
  * @param {string} promiseAttribute
  * @param {string} dataAttribute
  * @param {string} dataAttribute
- * @param {string} apiHelperFunction
+ * @param {Function} apiHelperFunction
  * @return {CancellablePromise}
  * @return {CancellablePromise}
  */
  */
 const genericOptimizerReload = function(
 const genericOptimizerReload = function(
@@ -57,7 +63,7 @@ const genericOptimizerReload = function(
  * @param {boolean} [options.cancellable] - Default false
  * @param {boolean} [options.cancellable] - Default false
  * @param {string} promiseAttribute
  * @param {string} promiseAttribute
  * @param {string} dataAttribute
  * @param {string} dataAttribute
- * @param {string} apiHelperFunction
+ * @param {Function} apiHelperFunction
  * @return {CancellablePromise}
  * @return {CancellablePromise}
  */
  */
 const genericOptimizerGet = function(
 const genericOptimizerGet = function(
@@ -180,7 +186,7 @@ class MultiTableEntry {
    */
    */
   getTopAggs(options) {
   getTopAggs(options) {
     const self = this;
     const self = this;
-    return genericOptimizerGet(self, options, 'topAggsPromise', 'topAggs', 'fetchOptimizerTopAggs');
+    return genericOptimizerGet(self, options, 'topAggsPromise', 'topAggs', fetchTopAggs);
   }
   }
 
 
   /**
   /**
@@ -196,13 +202,7 @@ class MultiTableEntry {
    */
    */
   getTopColumns(options) {
   getTopColumns(options) {
     const self = this;
     const self = this;
-    return genericOptimizerGet(
-      self,
-      options,
-      'topColumnsPromise',
-      'topColumns',
-      'fetchOptimizerTopColumns'
-    );
+    return genericOptimizerGet(self, options, 'topColumnsPromise', 'topColumns', fetchTopColumns);
   }
   }
 
 
   /**
   /**
@@ -218,13 +218,7 @@ class MultiTableEntry {
    */
    */
   getTopFilters(options) {
   getTopFilters(options) {
     const self = this;
     const self = this;
-    return genericOptimizerGet(
-      self,
-      options,
-      'topFiltersPromise',
-      'topFilters',
-      'fetchOptimizerTopFilters'
-    );
+    return genericOptimizerGet(self, options, 'topFiltersPromise', 'topFilters', fetchTopFilters);
   }
   }
 
 
   /**
   /**
@@ -240,13 +234,7 @@ class MultiTableEntry {
    */
    */
   getTopJoins(options) {
   getTopJoins(options) {
     const self = this;
     const self = this;
-    return genericOptimizerGet(
-      self,
-      options,
-      'topJoinsPromise',
-      'topJoins',
-      'fetchOptimizerTopJoins'
-    );
+    return genericOptimizerGet(self, options, 'topJoinsPromise', 'topJoins', fetchTopJoins);
   }
   }
 }
 }
 
 

+ 205 - 0
desktop/core/src/desktop/js/catalog/optimizer/optimizerApiHelper.js

@@ -0,0 +1,205 @@
+// 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.
+
+import $ from 'jquery';
+import * as ko from 'knockout';
+
+import CancellablePromise from 'api/cancellablePromise';
+import { simplePost } from 'api/apiUtils';
+
+const OPTIMIZER_URLS = {
+  COMPATIBILITY: '/notebook/api/optimizer/statement/compatibility',
+  RISK: '/notebook/api/optimizer/statement/risk',
+  SIMILARITY: '/notebook/api/optimizer/statement/similarity',
+  TOP_AGGS: '/metadata/api/optimizer/top_aggs',
+  TOP_COLUMNS: '/metadata/api/optimizer/top_columns',
+  TOP_FILTERS: '/metadata/api/optimizer/top_filters',
+  TOP_JOINS: '/metadata/api/optimizer/top_joins',
+  TOP_TABLES: '/metadata/api/optimizer/top_tables',
+  TABLE_DETAILS: '/metadata/api/optimizer/table_details'
+};
+
+/**
+ * Fetches the popularity for various aspects of the given tables
+ *
+ * @param {Object} options
+ * @param {boolean} [options.silenceErrors]
+ * @param {string[][]} options.paths
+ * @param {string} url
+ * @return {CancellablePromise}
+ */
+const genericOptimizerMultiTableFetch = (options, url) => {
+  const deferred = $.Deferred();
+
+  const dbTables = {};
+  options.paths.forEach(path => {
+    dbTables[path.join('.')] = true;
+  });
+  const data = {
+    dbTables: ko.mapping.toJSON(Object.keys(dbTables))
+  };
+
+  const request = simplePost(url, data, {
+    silenceErrors: options.silenceErrors,
+    successCallback: data => {
+      data.hueTimestamp = Date.now();
+      deferred.resolve(data);
+    },
+    errorCallback: deferred.reject
+  });
+
+  return new CancellablePromise(deferred, request);
+};
+
+export const analyzeRisk = options =>
+  simplePost(OPTIMIZER_URLS.RISK, {
+    notebook: options.notebookJson,
+    snippet: options.snippetJson
+  });
+
+export const analyzeCompatibility = options =>
+  simplePost(OPTIMIZER_URLS.COMPATIBILITY, {
+    notebook: options.notebookJson,
+    snippet: options.snippetJson,
+    sourcePlatform: options.sourcePlatform,
+    targetPlatform: options.targetPlatform
+  });
+
+export const analyzeSimilarity = options =>
+  simplePost(OPTIMIZER_URLS.SIMILARITY, {
+    notebook: options.notebookJson,
+    snippet: options.snippetJson,
+    sourcePlatform: options.sourcePlatform
+  });
+
+/**
+ * Fetches optimizer popularity for the children of the given path
+ *
+ * @param {Object} options
+ * @param {boolean} [options.silenceErrors]
+ * @param {string[][]} options.paths
+ * @return {CancellablePromise}
+ */
+export const fetchPopularity = options => {
+  const deferred = $.Deferred();
+  let url, data;
+
+  if (options.paths.length === 1 && options.paths[0].length === 1) {
+    url = OPTIMIZER_URLS.TOP_TABLES;
+    data = {
+      database: options.paths[0][0]
+    };
+  } else {
+    url = OPTIMIZER_URLS.TOP_COLUMNS;
+    const dbTables = [];
+    options.paths.forEach(path => {
+      dbTables.push(path.join('.'));
+    });
+    data = {
+      dbTables: ko.mapping.toJSON(dbTables)
+    };
+  }
+
+  const request = simplePost(url, data, {
+    silenceErrors: options.silenceErrors,
+    successCallback: data => {
+      data.hueTimestamp = Date.now();
+      deferred.resolve(data);
+    },
+    errorCallback: deferred.reject
+  });
+
+  return new CancellablePromise(deferred, request);
+};
+
+/**
+ * Fetches the popular aggregate functions for the given tables
+ *
+ * @param {Object} options
+ * @param {boolean} [options.silenceErrors]
+ * @param {string[][]} options.paths
+ * @return {CancellablePromise}
+ */
+export const fetchTopAggs = options =>
+  genericOptimizerMultiTableFetch(options, OPTIMIZER_URLS.TOP_AGGS);
+
+/**
+ * Fetches the popular columns for the given tables
+ *
+ * @param {Object} options
+ * @param {boolean} [options.silenceErrors]
+ * @param {string[][]} options.paths
+ * @return {CancellablePromise}
+ */
+export const fetchTopColumns = options =>
+  genericOptimizerMultiTableFetch(options, OPTIMIZER_URLS.TOP_COLUMNS);
+
+/**
+ * Fetches the popular filters for the given tables
+ *
+ * @param {Object} options
+ * @param {boolean} [options.silenceErrors]
+ * @param {string[][]} options.paths
+ * @return {CancellablePromise}
+ */
+export const fetchTopFilters = options =>
+  genericOptimizerMultiTableFetch(options, OPTIMIZER_URLS.TOP_FILTERS);
+
+/**
+ * Fetches the popular joins for the given tables
+ *
+ * @param {Object} options
+ * @param {boolean} [options.silenceErrors]
+ * @param {string[][]} options.paths
+ * @return {CancellablePromise}
+ */
+export const fetchTopJoins = options =>
+  genericOptimizerMultiTableFetch(options, OPTIMIZER_URLS.TOP_JOINS);
+
+/**
+ * Fetches optimizer meta for the given path, only possible for tables atm.
+ *
+ * @param {Object} options
+ * @param {boolean} [options.silenceErrors]
+ * @param {string[]} options.path
+ *
+ * @return {CancellablePromise}
+ */
+export const fetchOptimizerMeta = options => {
+  const deferred = $.Deferred();
+
+  const request = simplePost(
+    OPTIMIZER_URLS.TABLE_DETAILS,
+    {
+      databaseName: options.path[0],
+      tableName: options.path[1]
+    },
+    {
+      silenceErrors: options.silenceErrors,
+      successCallback: response => {
+        if (response.status === 0 && response.details) {
+          response.details.hueTimestamp = Date.now();
+          deferred.resolve(response.details);
+        } else {
+          deferred.reject();
+        }
+      },
+      errorCallback: deferred.reject
+    }
+  );
+
+  return new CancellablePromise(deferred, request);
+};