You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2019/05/23 18:22:29 UTC

[incubator-superset] branch master updated: Add link to scheduled pipeline (#7584)

This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 265e117  Add link to scheduled pipeline (#7584)
265e117 is described below

commit 265e117a4a95136f71a7806903de961fe507930e
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Thu May 23 11:22:15 2019 -0700

    Add link to scheduled pipeline (#7584)
    
    * Add link to scheduled pipeline
    
    * Split utils into separate file
    
    * Fix unit test
    
    * Fix separator recursion
---
 docs/installation.rst                              |  6 ++
 .../spec/javascripts/showSavedQuery/utils_spec.jsx | 65 ++++++++++++++++++++++
 .../src/showSavedQuery/{index.jsx => index.css}    | 27 +--------
 superset/assets/src/showSavedQuery/index.jsx       | 38 +++++++++----
 .../src/showSavedQuery/{index.jsx => utils.js}     | 45 +++++++--------
 superset/views/sql_lab.py                          |  5 +-
 6 files changed, 125 insertions(+), 61 deletions(-)

diff --git a/docs/installation.rst b/docs/installation.rst
index 329debc..3e2934a 100644
--- a/docs/installation.rst
+++ b/docs/installation.rst
@@ -911,6 +911,12 @@ To allow scheduled queries, add the following to your `config.py`:
                     'container': 'end_date',
                 },
             ],
+            # link to the scheduler; this example links to an Airflow pipeline
+            # that uses the query id and the output table as its name
+            'linkback': (
+                'https://airflow.example.com/admin/airflow/tree?'
+                'dag_id=query_${id}_${extra_json.schedule_info.output_table}'
+            ),
         },
     }
 
diff --git a/superset/assets/spec/javascripts/showSavedQuery/utils_spec.jsx b/superset/assets/spec/javascripts/showSavedQuery/utils_spec.jsx
new file mode 100644
index 0000000..d198a49
--- /dev/null
+++ b/superset/assets/spec/javascripts/showSavedQuery/utils_spec.jsx
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 { getNestedValue, interpolate } from '../../../src/showSavedQuery/utils';
+
+describe('getNestedValue', () => {
+  it('is a function', () => {
+    expect(typeof getNestedValue).toBe('function');
+  });
+
+  it('works with simple ids', () => {
+    const obj = { a: '1' };
+    const id = 'a';
+    expect(getNestedValue(obj, id)).toEqual('1');
+  });
+
+  it('works with complex ids', () => {
+    const obj = { a: { b: '1' } };
+    const id = 'a.b';
+    expect(getNestedValue(obj, id)).toEqual('1');
+  });
+
+  it('works with other separators', () => {
+    const obj = { a: { b: { c: '1' } } };
+    const id = 'a__b__c';
+    const separator = '__';
+    expect(getNestedValue(obj, id, separator)).toEqual('1');
+  });
+});
+
+
+describe('interpolate', () => {
+  it('is a function', () => {
+    expect(typeof interpolate).toBe('function');
+  });
+
+  it('works with simple ids', () => {
+    const obj = { a: '1' };
+    // eslint-disable-next-line no-template-curly-in-string
+    const str = 'value: ${a}';
+    expect(interpolate(str, obj)).toEqual('value: 1');
+  });
+
+  it('works with complex ids', () => {
+    const obj = { a: { b: '1' } };
+    // eslint-disable-next-line no-template-curly-in-string
+    const str = 'value: ${a.b}';
+    expect(interpolate(str, obj)).toEqual('value: 1');
+  });
+});
diff --git a/superset/assets/src/showSavedQuery/index.jsx b/superset/assets/src/showSavedQuery/index.css
similarity index 52%
copy from superset/assets/src/showSavedQuery/index.jsx
copy to superset/assets/src/showSavedQuery/index.css
index cd384fe..026dd78 100644
--- a/superset/assets/src/showSavedQuery/index.jsx
+++ b/superset/assets/src/showSavedQuery/index.css
@@ -16,28 +16,5 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React from 'react';
-import ReactDom from 'react-dom';
-import Form from 'react-jsonschema-form';
-
-const scheduleInfoContainer = document.getElementById('schedule-info');
-const bootstrapData = JSON.parse(scheduleInfoContainer.getAttribute('data-bootstrap'));
-const schemas = bootstrapData.common.feature_flags.SCHEDULED_QUERIES;
-const scheduleInfo = bootstrapData.common.extra_json.schedule_info;
-
-if (scheduleInfo && schemas) {
-  // hide instructions when showing schedule info
-  schemas.JSONSCHEMA.description = '';
-
-  ReactDom.render(
-    <Form
-      schema={schemas.JSONSCHEMA}
-      uiSchema={schemas.UISCHEMA}
-      formData={scheduleInfo}
-      disabled
-    >
-      <br />
-    </Form>,
-    scheduleInfoContainer,
-  );
-}
+.btn-add { display: none; }
+.linkback { padding: 0 10px 20px 2px; }
diff --git a/superset/assets/src/showSavedQuery/index.jsx b/superset/assets/src/showSavedQuery/index.jsx
index cd384fe..c64626c 100644
--- a/superset/assets/src/showSavedQuery/index.jsx
+++ b/superset/assets/src/showSavedQuery/index.jsx
@@ -19,25 +19,39 @@
 import React from 'react';
 import ReactDom from 'react-dom';
 import Form from 'react-jsonschema-form';
+import { interpolate } from 'src/showSavedQuery/utils';
+import './index.css';
 
 const scheduleInfoContainer = document.getElementById('schedule-info');
 const bootstrapData = JSON.parse(scheduleInfoContainer.getAttribute('data-bootstrap'));
-const schemas = bootstrapData.common.feature_flags.SCHEDULED_QUERIES;
-const scheduleInfo = bootstrapData.common.extra_json.schedule_info;
+const config = bootstrapData.common.feature_flags.SCHEDULED_QUERIES;
+const query = bootstrapData.common.query;
+const scheduleInfo = query.extra_json.schedule_info;
+const linkback = config.linkback
+  ? interpolate(config.linkback, query)
+  : null;
 
-if (scheduleInfo && schemas) {
+if (scheduleInfo && config) {
   // hide instructions when showing schedule info
-  schemas.JSONSCHEMA.description = '';
+  config.JSONSCHEMA.description = '';
 
   ReactDom.render(
-    <Form
-      schema={schemas.JSONSCHEMA}
-      uiSchema={schemas.UISCHEMA}
-      formData={scheduleInfo}
-      disabled
-    >
-      <br />
-    </Form>,
+    <div>
+      <Form
+        schema={config.JSONSCHEMA}
+        uiSchema={config.UISCHEMA}
+        formData={scheduleInfo}
+        disabled
+      >
+        <br />
+      </Form>
+      {linkback && <div className="linkback">
+        <a href={linkback}>
+          <i className="fa fa-link" />&nbsp;
+          Pipeline status
+        </a>
+      </div>}
+    </div>,
     scheduleInfoContainer,
   );
 }
diff --git a/superset/assets/src/showSavedQuery/index.jsx b/superset/assets/src/showSavedQuery/utils.js
similarity index 52%
copy from superset/assets/src/showSavedQuery/index.jsx
copy to superset/assets/src/showSavedQuery/utils.js
index cd384fe..9cd712b 100644
--- a/superset/assets/src/showSavedQuery/index.jsx
+++ b/superset/assets/src/showSavedQuery/utils.js
@@ -16,28 +16,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React from 'react';
-import ReactDom from 'react-dom';
-import Form from 'react-jsonschema-form';
 
-const scheduleInfoContainer = document.getElementById('schedule-info');
-const bootstrapData = JSON.parse(scheduleInfoContainer.getAttribute('data-bootstrap'));
-const schemas = bootstrapData.common.feature_flags.SCHEDULED_QUERIES;
-const scheduleInfo = bootstrapData.common.extra_json.schedule_info;
-
-if (scheduleInfo && schemas) {
-  // hide instructions when showing schedule info
-  schemas.JSONSCHEMA.description = '';
+export function getNestedValue(obj, id, separator = '.') {
+  /*
+   * Given a nested object and an id, return the nested value.
+   *
+   * > getNestedValue({a:{b:1}}, 'a.b')
+   * < 1
+   */
+  const index = id.indexOf(separator);
+  if (index === -1) {
+    return obj[id];
+  }
+  const name = id.slice(0, index);
+  const rest = id.slice(index + separator.length);
+  return getNestedValue(obj[name], rest, separator);
+}
 
-  ReactDom.render(
-    <Form
-      schema={schemas.JSONSCHEMA}
-      uiSchema={schemas.UISCHEMA}
-      formData={scheduleInfo}
-      disabled
-    >
-      <br />
-    </Form>,
-    scheduleInfoContainer,
-  );
+export function interpolate(str, obj) {
+  /*
+   * Programmatic template string for interpolation.
+   *
+   * > interpolate('foo ${a.b}', {a:{b:1}})
+   * < "foo 1"
+   */
+  return str.replace(/\$\{(.+?)\}/g, (match, id) => getNestedValue(obj, id));
 }
diff --git a/superset/views/sql_lab.py b/superset/views/sql_lab.py
index 37c84c2..98046f8 100644
--- a/superset/views/sql_lab.py
+++ b/superset/views/sql_lab.py
@@ -122,11 +122,12 @@ class SavedQueryView(SupersetModelView, DeleteMixin):
     def show(self, pk):
         pk = self._deserialize_pk_if_composite(pk)
         widgets = self._show(pk)
-        extra_json = self.datamodel.get(pk).extra_json
+        query = self.datamodel.get(pk).to_json()
+        query['extra_json'] = json.loads(query['extra_json'])
         payload = {
             'common': {
                 'feature_flags': get_feature_flags(),
-                'extra_json': json.loads(extra_json),
+                'query': query,
             },
         }