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" />
+ 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,
},
}