-
Visualization package display order (drag and drop to reorder)
+
+
Bundle package display order (drag and drop to reorder)
-
+
+ ng-show="bundleOrderChanged"
+ ng-click="saveBundleOrder()">
save
diff --git a/zeppelin-web/src/components/helium/helium-type.js b/zeppelin-web/src/components/helium/helium-type.js
new file mode 100644
index 00000000000..7608aa31dd2
--- /dev/null
+++ b/zeppelin-web/src/components/helium/helium-type.js
@@ -0,0 +1,4 @@
+export const HeliumType = {
+ VISUALIZATION: 'VISUALIZATION',
+ FRONTEND_INTERPRETER: 'FRONTEND_INTERPRETER',
+}
diff --git a/zeppelin-web/src/components/helium/helium.service.js b/zeppelin-web/src/components/helium/helium.service.js
index ae44425acae..e202aedbd67 100644
--- a/zeppelin-web/src/components/helium/helium.service.js
+++ b/zeppelin-web/src/components/helium/helium.service.js
@@ -12,39 +12,89 @@
* limitations under the License.
*/
-(function() {
+import { HeliumType, } from './helium-type';
+import {
+ AbstractFrontendInterpreter,
+ DefaultDisplayType
+} from '../../app/frontend-interpreter'
+(function() {
angular.module('zeppelinWebApp').service('heliumService', heliumService);
heliumService.$inject = ['$http', 'baseUrlSrv', 'ngToast'];
function heliumService($http, baseUrlSrv, ngToast) {
- var url = baseUrlSrv.getRestApiBase() + '/helium/visualizations/load';
- if (process.env.HELIUM_VIS_DEV) {
+ var url = baseUrlSrv.getRestApiBase() + '/helium/bundle/load';
+ if (process.env.HELIUM_BUNDLE_DEV) {
url = url + '?refresh=true';
}
- var visualizations = [];
+ // name `heliumBundles` should be same as `HelumBundleFactory.HELIUM_BUNDLES_VAR`
+ var heliumBundles = [];
+ // map for `{ magic: interpreter }`
+ let frontendIntpWithMagic = {};
+ // map for `{ displayType: interpreter }`
+ let frontendIntpWithDisplayType = {};
+ let visualizationBundles = [];
// load should be promise
this.load = $http.get(url).success(function(response) {
if (response.substring(0, 'ERROR:'.length) !== 'ERROR:') {
+ // evaluate bundles
eval(response);
+
+ // extract bundles by type
+ heliumBundles.map(b => {
+ if (b.type === HeliumType.FRONTEND_INTERPRETER) {
+ const interpreter = new b.class(); // eslint-disable-line new-cap
+
+ // add frontend interpreter if `interpret` method is available
+ if (AbstractFrontendInterpreter.useInterpret(interpreter)) {
+ frontendIntpWithMagic[interpreter.getMagic()] = interpreter;
+ }
+
+ // add frontend interpreter if `display` method is available
+ // and its display type is in default display types.
+ if (AbstractFrontendInterpreter.useDisplay(interpreter) &&
+ DefaultDisplayType[interpreter.getDisplayType()]) {
+ frontendIntpWithDisplayType[interpreter.getDisplayType()] = interpreter;
+ }
+
+ } else if (b.type === HeliumType.VISUALIZATION) {
+ visualizationBundles.push(b);
+ }
+ });
} else {
console.error(response);
}
});
- this.get = function() {
- return visualizations;
+ /**
+ * @param magic {string} e.g `%flowchart`
+ * @returns {FrontendInterpreterBase} undefined for non-available magic
+ */
+ this.getFrontendInterpreterUsingMagic = function(magic) {
+ return frontendIntpWithMagic[magic];
+ };
+
+ /**
+ * @param displayType {string} e.g `ELEMENT`
+ * @returns {FrontendInterpreterBase} undefined for non-available displayType
+ */
+ this.getFrontendInterpreterWithDisplayType = function(displayType) {
+ return frontendIntpWithDisplayType[displayType];
+ };
+
+ this.getVisualizationBundles = function() {
+ return visualizationBundles;
};
- this.getVisualizationOrder = function() {
- return $http.get(baseUrlSrv.getRestApiBase() + '/helium/visualizationOrder');
+ this.getBundleOrder = function() {
+ return $http.get(baseUrlSrv.getRestApiBase() + '/helium/bundleOrder');
};
- this.setVisualizationOrder = function(list) {
- return $http.post(baseUrlSrv.getRestApiBase() + '/helium/visualizationOrder', list);
+ this.setBundleOrder = function(list) {
+ return $http.post(baseUrlSrv.getRestApiBase() + '/helium/bundleOrder', list);
};
this.getAllPackageInfo = function() {
From 247d00f316407c86aea51328d5682036b67b218f Mon Sep 17 00:00:00 2001
From: 1ambda <1amb4a@gmail.com>
Date: Tue, 17 Jan 2017 19:41:32 +0900
Subject: [PATCH 03/29] feat: Add frontend interpreter framework
---
.../src/app/frontend-interpreter/.npmignore | 1 +
.../frontend-interpreter-framework.js | 97 +++++++++++++++++++
.../frontend-interpreter-result.js | 69 +++++++++++++
.../src/app/frontend-interpreter/index.js | 25 +++++
.../src/app/frontend-interpreter/package.json | 13 +++
5 files changed, 205 insertions(+)
create mode 100644 zeppelin-web/src/app/frontend-interpreter/.npmignore
create mode 100644 zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-framework.js
create mode 100644 zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-result.js
create mode 100644 zeppelin-web/src/app/frontend-interpreter/index.js
create mode 100644 zeppelin-web/src/app/frontend-interpreter/package.json
diff --git a/zeppelin-web/src/app/frontend-interpreter/.npmignore b/zeppelin-web/src/app/frontend-interpreter/.npmignore
new file mode 100644
index 00000000000..0b84df0f025
--- /dev/null
+++ b/zeppelin-web/src/app/frontend-interpreter/.npmignore
@@ -0,0 +1 @@
+*.html
\ No newline at end of file
diff --git a/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-framework.js b/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-framework.js
new file mode 100644
index 00000000000..0e3c4d57a7d
--- /dev/null
+++ b/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-framework.js
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+/*eslint-disable no-unused-vars */
+import {
+ DefaultDisplayType,
+ FrontendInterpreterResult,
+} from './frontend-interpreter-result';
+/*eslint-enable no-unused-vars */
+
+export class AbstractFrontendInterpreter {
+ constructor(magic, displayType) {
+ this.magic = magic;
+ this.displayType = displayType;
+ }
+
+ /**
+ * @param paragraphText which includes magic
+ * @returns {string} if magic exists, otherwise return undefined
+ */
+ static extractMagic(allParagraphText) {
+ const intpNameRegexp = /^\s*%(\S+)\s*/g;
+ try {
+ let match = intpNameRegexp.exec(allParagraphText);
+ if (match) { return `%${match[1].trim()}`; }
+ } catch (error) {
+ // failed to parse, ignore
+ }
+
+ return undefined;
+}
+
+ static useInterpret(interpreter) {
+ return (interpreter.__proto__.hasOwnProperty('interpret'));
+ }
+
+ static useDisplay(interpreter) {
+ return (interpreter.__proto__.hasOwnProperty('display'));
+ }
+
+ /**
+ * Consumes text and return multiple interpreter results.
+ * This method should handle error properly to provide precise error message.
+ *
+ * @param paragraphText {string} which doesn't include magic
+ * @return {Array
}
+ */
+ interpret(paragraphText) {
+ /** implement this if you want to add a frontend interpreter */
+ }
+
+ /**
+ * Consumes text and return a single interpreter result.
+ * This method should handle error properly to provide precise error message.
+ *
+ * Currently, `display` only allows DefaultDisplayType
+ * as a type of result to avoid to recursive evaluation of display results.
+ *
+ * @param paragraphText {string}
+ * @return {FrontendInterpreterResult}
+ */
+ display(paragraphText) {
+ /** implement this if you want to add a display system */
+ }
+
+ /**
+ * return magic for this frontend interpreter.
+ * (e.g `%flowchart`)
+ * @return {string}
+ */
+ getMagic() {
+ return this.magic;
+ }
+
+ /**
+ * return display type for this frontend interpreter.
+ * (e.g `DefaultDisplayType.TEXT`)
+ * @return {string}
+ */
+ getDisplayType() {
+ return this.displayType;
+ }
+}
diff --git a/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-result.js b/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-result.js
new file mode 100644
index 00000000000..2af6efc4790
--- /dev/null
+++ b/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-result.js
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+export const DefaultDisplayType = {
+ ELEMENT: 'ELEMENT',
+ TABLE: 'TABLE',
+ HTML: 'HTML',
+ ANGULAR: 'ANGULAR',
+ TEXT: 'TEXT',
+};
+
+export class FrontendInterpreterResult {
+ constructor(resultDisplayType, resultDataGenerator) {
+ /**
+ * backend interpreter uses `data` and `type` as field names.
+ * let's use the same field to keep consistency.
+ */
+ this.type = resultDisplayType;
+ this.data = resultDataGenerator;
+ }
+
+ static isFunctionGenerator(generator) {
+ return (generator && typeof generator === 'function');
+ }
+
+ static isPromiseGenerator(generator) {
+ return (generator && typeof generator.then === 'function');
+ }
+
+ static isObjectGenerator(generator) {
+ return (
+ !FrontendInterpreterResult.isFunctionGenerator(generator) &&
+ !FrontendInterpreterResult.isPromiseGenerator(generator));
+ }
+
+ /**
+ * @returns {string} display type for this frontend interpreter result.
+ */
+ getType() {
+ return this.type;
+ }
+
+ /**
+ * generator (`data`) can be promise, function or just object
+ * - if data is an object, it will be used directly.
+ * - if data is a function, it will be called with DOM element id
+ * where the final output is rendered.
+ * - if data is a promise, the post processing logic
+ * will be called in `then()` of this promise.
+ * @returns {*} `data` which can be object, function or promise.
+ */
+ getData() {
+ return this.data;
+ }
+}
diff --git a/zeppelin-web/src/app/frontend-interpreter/index.js b/zeppelin-web/src/app/frontend-interpreter/index.js
new file mode 100644
index 00000000000..7390041c811
--- /dev/null
+++ b/zeppelin-web/src/app/frontend-interpreter/index.js
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+export {
+ DefaultDisplayType,
+ FrontendInterpreterResult,
+} from './frontend-interpreter-result';
+
+export {
+ AbstractFrontendInterpreter
+} from './frontend-interpreter-framework';
diff --git a/zeppelin-web/src/app/frontend-interpreter/package.json b/zeppelin-web/src/app/frontend-interpreter/package.json
new file mode 100644
index 00000000000..bc7b19fca1e
--- /dev/null
+++ b/zeppelin-web/src/app/frontend-interpreter/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "zeppelin-frontend-interpreter",
+ "description": "Frontend Interpreter API",
+ "version": "0.7.0-SNAPSHOT",
+ "main": "index",
+ "dependencies": {
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/apache/zeppelin"
+ },
+ "license": "Apache-2.0"
+}
From a163044d61de3db8c5c20e2007a417afb7964b97 Mon Sep 17 00:00:00 2001
From: 1ambda <1amb4a@gmail.com>
Date: Tue, 17 Jan 2017 19:41:59 +0900
Subject: [PATCH 04/29] feat: Add flowchart, translator examples
---
zeppelin-examples/pom.xml | 2 +
.../index.js | 109 ++++++++++++++++
.../package.json | 13 ++
.../pom.xml | 116 ++++++++++++++++++
...xample-frontend-interpreter-flowchart.json | 24 ++++
.../index.js | 64 ++++++++++
.../package.json | 12 ++
.../pom.xml | 116 ++++++++++++++++++
...ample-frontend-interpreter-translator.json | 24 ++++
9 files changed, 480 insertions(+)
create mode 100644 zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/index.js
create mode 100644 zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/package.json
create mode 100644 zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/pom.xml
create mode 100644 zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/zeppelin-example-frontend-interpreter-flowchart.json
create mode 100644 zeppelin-examples/zeppelin-example-frontend-interpreter-translator/index.js
create mode 100644 zeppelin-examples/zeppelin-example-frontend-interpreter-translator/package.json
create mode 100644 zeppelin-examples/zeppelin-example-frontend-interpreter-translator/pom.xml
create mode 100644 zeppelin-examples/zeppelin-example-frontend-interpreter-translator/zeppelin-example-frontend-interpreter-translator.json
diff --git a/zeppelin-examples/pom.xml b/zeppelin-examples/pom.xml
index 300ba57808a..52e631752b8 100644
--- a/zeppelin-examples/pom.xml
+++ b/zeppelin-examples/pom.xml
@@ -36,6 +36,8 @@
zeppelin-example-clock
zeppelin-example-horizontalbar
+ zeppelin-example-frontend-interpreter-flowchart
+ zeppelin-example-frontend-interpreter-translator
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/index.js b/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/index.js
new file mode 100644
index 00000000000..25ef89eb2d1
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/index.js
@@ -0,0 +1,109 @@
+/*
+ * 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 {
+ AbstractFrontendInterpreter,
+ FrontendInterpreterResult,
+ DefaultDisplayType,
+} from 'zeppelin-frontend-interpreter';
+
+import flowchart from 'flowchart.js';
+
+export default class FlowchartInterpreter extends AbstractFrontendInterpreter {
+ constructor() {
+ super("%flowchart", DefaultDisplayType.ELEMENT);
+ }
+
+ interpret(paragraphText) {
+ /**
+ * `flowchart` library requires an existing DOM to render.
+ * but the DOM is not created yet when `interpret` is called.
+ * so Zeppelin allows to return callback function which accept a DOM element id.
+ * the callback function will executed when the DOM is ready.
+ */
+ const callback = (targetElemId) => {
+ let diagram = flowchart.parse(paragraphText);
+ diagram.drawSVG(targetElemId, this.getOption());
+ };
+
+ /**
+ * `interpret` method can return multiple results.
+ * But now, we return just 1 result which is wrapped in an array.
+ */
+ return [new FrontendInterpreterResult(
+ DefaultDisplayType.ELEMENT,
+ callback
+ )];
+ }
+
+ getOption() {
+ return {
+ 'x': 0,
+ 'y': 0,
+ 'line-width': 3,
+ 'line-length': 50,
+ 'text-margin': 10,
+ 'font-size': 14,
+ 'font-color': 'black',
+ 'line-color': 'black',
+ 'element-color': 'black',
+ 'fill': 'white',
+ 'yes-text': 'yes',
+ 'no-text': 'no',
+ 'arrow-end': 'block',
+ 'scale': 1,
+ // style symbol types
+ 'symbols': {
+ 'start': {
+ 'font-color': 'red',
+ 'element-color': 'green',
+ 'fill': 'yellow'
+ },
+ 'end':{
+ 'class': 'end-element'
+ }
+ },
+ // even flowstate support ;-)
+ 'flowstate' : {
+ 'past' : { 'fill' : '#CCCCCC', 'font-size' : 12},
+ 'current' : {'fill' : 'yellow', 'font-color' : 'red', 'font-weight' : 'bold'},
+ 'future' : { 'fill' : '#FFFF99'},
+ 'request' : { 'fill' : 'blue'},
+ 'invalid': {'fill' : '#444444'},
+ 'approved' : { 'fill' : '#58C4A3', 'font-size' : 12, 'yes-text' : 'APPROVED', 'no-text' : 'n/a' },
+ 'rejected' : { 'fill' : '#C45879', 'font-size' : 12, 'yes-text' : 'n/a', 'no-text' : 'REJECTED' }
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/package.json b/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/package.json
new file mode 100644
index 00000000000..b64cbe25de4
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "flowchart-frontend-interpreter",
+ "description": "Frontend Flowchart Interpreter (example)",
+ "version": "1.0.0",
+ "main": "index",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "raphael": "2.2.0",
+ "flowchart.js": "^1.6.5",
+ "zeppelin-frontend-interpreter": "*"
+ }
+}
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/pom.xml b/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/pom.xml
new file mode 100644
index 00000000000..8f465172166
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/pom.xml
@@ -0,0 +1,116 @@
+
+
+
+
+ 4.0.0
+
+
+ zeppelin-examples
+ org.apache.zeppelin
+ 0.7.0-SNAPSHOT
+ ..
+
+
+ org.apache.zeppelin
+ zeppelin-example-frontend-interpreter-flowchart
+ jar
+ 0.7.0-SNAPSHOT
+ Zeppelin: Example application - Frontend Flowchart Interpreter
+
+
+
+ ${project.groupId}
+ zeppelin-interpreter
+ ${project.version}
+
+
+
+ ${project.groupId}
+ helium-dev
+ ${project.version}
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 2.7
+
+ true
+
+
+
+
+ maven-clean-plugin
+
+
+
+ ${project.basedir}/../../helium
+
+ ${project.artifactId}.json
+
+
+
+
+
+
+
+ maven-resources-plugin
+ 2.7
+
+
+ generate-resources
+
+ copy-resources
+
+
+
+ ${project.basedir}/../../helium/
+
+
+ ${project.basedir}
+
+ ${project.artifactId}.json
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/zeppelin-example-frontend-interpreter-flowchart.json b/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/zeppelin-example-frontend-interpreter-flowchart.json
new file mode 100644
index 00000000000..73e1150636d
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart/zeppelin-example-frontend-interpreter-flowchart.json
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+{
+ "type" : "FRONTEND_INTERPRETER",
+ "name" : "flowchart-frontend-interpreter",
+ "description" : "with Flowchart.js (example)",
+ "artifact" : "./zeppelin-examples/zeppelin-example-frontend-interpreter-flowchart",
+ "license" : "Apache-2.0",
+ "icon" : ""
+}
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/index.js b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/index.js
new file mode 100644
index 00000000000..60db4712a80
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/index.js
@@ -0,0 +1,64 @@
+/*
+ * 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 {
+ AbstractFrontendInterpreter,
+ FrontendInterpreterResult,
+ DefaultDisplayType,
+} from 'zeppelin-frontend-interpreter';
+
+import 'whatwg-fetch';
+
+export default class TranslatorInterpreter extends AbstractFrontendInterpreter {
+ constructor() {
+ super("%translator", DefaultDisplayType.TEXT);
+ }
+
+ interpret(paragraphText) {
+ return [new FrontendInterpreterResult(
+ DefaultDisplayType.TEXT,
+ this.translate(paragraphText)
+ )];
+ }
+
+ translate(text) {
+ return fetch('https://translation.googleapis.com/language/translate/v2', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer YOUR_ACCESS_KEY_HERE',
+ },
+ body: JSON.stringify({
+ 'q': text,
+ 'source': 'en',
+ 'target': 'ko',
+ 'format': 'text'
+ })
+ }).then((response) => {
+ if (response.status === 200) {
+ return response.json()
+ }
+ throw new Error(`https://translation.googleapis.com/language/translate/v2 ${response.status} (${response.statusText})`);
+ }).then((json) => {
+ const extracted = json.data.translations.map(t => {
+ return t.translatedText;
+ });
+ return extracted.join('\n');
+ });
+ }
+}
+
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/package.json b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/package.json
new file mode 100644
index 00000000000..838a65525ec
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "translator-frontend-interpreter",
+ "description": "Frontend Translator Interpreter (example)",
+ "version": "1.0.0",
+ "main": "index",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "whatwg-fetch": "^2.0.1",
+ "zeppelin-frontend-interpreter": "*"
+ }
+}
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/pom.xml b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/pom.xml
new file mode 100644
index 00000000000..84ec74e58b8
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/pom.xml
@@ -0,0 +1,116 @@
+
+
+
+
+ 4.0.0
+
+
+ zeppelin-examples
+ org.apache.zeppelin
+ 0.7.0-SNAPSHOT
+ ..
+
+
+ org.apache.zeppelin
+ zeppelin-example-frontend-interpreter-translator
+ jar
+ 0.7.0-SNAPSHOT
+ Zeppelin: Example application - Frontend Translator Interpreter
+
+
+
+ ${project.groupId}
+ zeppelin-interpreter
+ ${project.version}
+
+
+
+ ${project.groupId}
+ helium-dev
+ ${project.version}
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 2.7
+
+ true
+
+
+
+
+ maven-clean-plugin
+
+
+
+ ${project.basedir}/../../helium
+
+ ${project.artifactId}.json
+
+
+
+
+
+
+
+ maven-resources-plugin
+ 2.7
+
+
+ generate-resources
+
+ copy-resources
+
+
+
+ ${project.basedir}/../../helium/
+
+
+ ${project.basedir}
+
+ ${project.artifactId}.json
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/zeppelin-example-frontend-interpreter-translator.json b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/zeppelin-example-frontend-interpreter-translator.json
new file mode 100644
index 00000000000..9c447a532fd
--- /dev/null
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/zeppelin-example-frontend-interpreter-translator.json
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+{
+ "type" : "FRONTEND_INTERPRETER",
+ "name" : "translator-frontend-interpreter",
+ "description" : "with Google Translation API (examaple)",
+ "artifact" : "./zeppelin-examples/zeppelin-example-frontend-interpreter-translator",
+ "license" : "Apache-2.0",
+ "icon" : ""
+}
From 5810bf1de5cd5b7e00c502da57cd1862b837b3b9 Mon Sep 17 00:00:00 2001
From: 1ambda <1amb4a@gmail.com>
Date: Tue, 17 Jan 2017 19:42:26 +0900
Subject: [PATCH 05/29] feat: Apply frontend interpreter to paragraph
---
.../index.js | 12 +-
.../frontend-interpreter-framework.js | 2 +-
.../src/app/notebook/notebook.controller.js | 6 +-
.../notebook/paragraph/paragraph-control.html | 2 +-
.../paragraph/paragraph.controller.js | 103 +++++---
.../src/app/notebook/paragraph/paragraph.html | 4 +-
.../paragraph/result/result.controller.js | 241 ++++++++++++------
.../app/notebook/paragraph/result/result.html | 10 +-
.../src/components/helium/helium.service.js | 2 +-
.../test/spec/controllers/paragraph.js | 4 -
10 files changed, 254 insertions(+), 132 deletions(-)
diff --git a/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/index.js b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/index.js
index 60db4712a80..5e2106ba373 100644
--- a/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/index.js
+++ b/zeppelin-examples/zeppelin-example-frontend-interpreter-translator/index.js
@@ -40,7 +40,7 @@ export default class TranslatorInterpreter extends AbstractFrontendInterpreter {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- 'Authorization': 'Bearer YOUR_ACCESS_KEY_HERE',
+ 'Authorization': 'Bearer YOUR_ACCESS_KEY',
},
body: JSON.stringify({
'q': text,
@@ -48,16 +48,16 @@ export default class TranslatorInterpreter extends AbstractFrontendInterpreter {
'target': 'ko',
'format': 'text'
})
- }).then((response) => {
+ }).then(response => {
if (response.status === 200) {
return response.json()
}
throw new Error(`https://translation.googleapis.com/language/translate/v2 ${response.status} (${response.statusText})`);
}).then((json) => {
- const extracted = json.data.translations.map(t => {
- return t.translatedText;
- });
- return extracted.join('\n');
+ const extracted = json.data.translations.map(t => {
+ return t.translatedText;
+ });
+ return extracted.join('\n');
});
}
}
diff --git a/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-framework.js b/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-framework.js
index 0e3c4d57a7d..a7013d1c291 100644
--- a/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-framework.js
+++ b/zeppelin-web/src/app/frontend-interpreter/frontend-interpreter-framework.js
@@ -70,7 +70,7 @@ export class AbstractFrontendInterpreter {
* Currently, `display` only allows DefaultDisplayType
* as a type of result to avoid to recursive evaluation of display results.
*
- * @param paragraphText {string}
+ * @param paragraphText {string} which doesn't include magic
* @return {FrontendInterpreterResult}
*/
display(paragraphText) {
diff --git a/zeppelin-web/src/app/notebook/notebook.controller.js b/zeppelin-web/src/app/notebook/notebook.controller.js
index ccf64b7b907..b434b642113 100644
--- a/zeppelin-web/src/app/notebook/notebook.controller.js
+++ b/zeppelin-web/src/app/notebook/notebook.controller.js
@@ -28,12 +28,14 @@ NotebookCtrl.$inject = [
'ngToast',
'noteActionSrv',
'noteVarShareService',
- 'TRASH_FOLDER_ID'
+ 'TRASH_FOLDER_ID',
+ 'heliumService',
];
function NotebookCtrl($scope, $route, $routeParams, $location, $rootScope,
$http, websocketMsgSrv, baseUrlSrv, $timeout, saveAsService,
- ngToast, noteActionSrv, noteVarShareService, TRASH_FOLDER_ID) {
+ ngToast, noteActionSrv, noteVarShareService, TRASH_FOLDER_ID,
+ heliumService) {
ngToast.dismiss();
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html b/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
index 644761ea3f8..351fb5ffecc 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph-control.html
@@ -25,7 +25,7 @@
Number.MAX_SAFE_INTEGER || Number(cell) < Number.MIN_SAFE_INTEGER) {
@@ -1092,7 +1135,7 @@ function ParagraphCtrl($scope, $rootScope, $route, $window, $routeParams, $locat
// move focus to next paragraph
$scope.$emit('moveFocusToNextParagraph', paragraphId);
} else if (keyEvent.shiftKey && keyCode === 13) { // Shift + Enter
- $scope.run($scope.paragraph, $scope.getEditorValue());
+ $scope.runParagraphFromShortcut($scope.getEditorValue());
} else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 67) { // Ctrl + Alt + c
$scope.cancelParagraph($scope.paragraph);
} else if (keyEvent.ctrlKey && keyEvent.altKey && keyCode === 68) { // Ctrl + Alt + d
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.html b/zeppelin-web/src/app/notebook/paragraph/paragraph.html
index 95ad9eb5a38..0de5e6468ec 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.html
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.html
@@ -58,9 +58,7 @@
ng-init="init(result, paragraph.config.results[$index], paragraph, $index)"
ng-include src="'app/notebook/paragraph/result/result.html'">
-
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
index 6d56fe40a37..d17b42f536f 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
@@ -19,6 +19,10 @@ import PiechartVisualization from '../../../visualization/builtins/visualization
import AreachartVisualization from '../../../visualization/builtins/visualization-areachart';
import LinechartVisualization from '../../../visualization/builtins/visualization-linechart';
import ScatterchartVisualization from '../../../visualization/builtins/visualization-scatterchart';
+import {
+ DefaultDisplayType,
+ FrontendInterpreterResult,
+} from '../../../frontend-interpreter'
angular.module('zeppelinWebApp').controller('ResultCtrl', ResultCtrl);
@@ -149,14 +153,12 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
// image data
$scope.imageData;
+ $scope.textRendererInitialized = false;
$scope.init = function(result, config, paragraph, index) {
- console.log('result controller init %o %o %o', result, config, index);
-
// register helium plugin vis
- var heliumVis = heliumService.get();
- console.log('Helium visualizations %o', heliumVis);
- heliumVis.forEach(function(vis) {
+ var visBundles = heliumService.getVisualizationBundles();
+ visBundles.forEach(function(vis) {
$scope.builtInTableDataVisualizationList.push({
id: vis.id,
name: vis.name,
@@ -166,7 +168,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
class: vis.class
};
});
-
+
updateData(result, config, paragraph, index);
renderResult($scope.type);
};
@@ -259,92 +261,170 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
if (activeApp) {
- var app = _.find($scope.apps, {id: activeApp});
- renderApp(app);
+ const app = _.find($scope.apps, {id: activeApp});
+ renderApp(app, `p${appState.id}`);
} else {
- if (type === 'TABLE') {
+
+ /**
+ * find proper interpreter which can handle the non-default display type
+ * the displayed type which is generated from `display()`
+ * should be one of the default interpreter type
+ */
+ // const isDefaultDisplayType = DefaultDisplayType[type];
+ // if (!isDefaultDisplayType) {
+ // const intp = heliumService.getFrontendInterpreterWithDisplayType(type);
+ //
+ // if (intp) {
+ // // currently display only accepts `Object` data not
+ // // doesn't support function and promise
+ // const result = intp.display(data);
+ // type = result.getType();
+ // data = result.getData();
+ // }
+ // }
+
+ if (type === DefaultDisplayType.TABLE) {
$scope.renderGraph($scope.graphMode, refresh);
- } else if (type === 'HTML') {
- renderHtml();
- } else if (type === 'ANGULAR') {
- renderAngular();
- } else if (type === 'TEXT') {
- renderText();
+ } else if (type === DefaultDisplayType.HTML) {
+ renderHtml(`p${$scope.id}_html`, data);
+ } else if (type === DefaultDisplayType.ANGULAR) {
+ renderAngular(`p${$scope.id}_angular`, data);
+ } else if (type === DefaultDisplayType.TEXT) {
+ renderText(`p${$scope.id}_text`, data);
+ } else if (type === DefaultDisplayType.ELEMENT) {
+ renderElem(`p${$scope.id}_elem`, data);
+ } else {
+ console.error(`Unknown Display Type: ${type}`);
}
}
};
- var renderHtml = function() {
- var retryRenderer = function() {
- var htmlEl = angular.element('#p' + $scope.id + '_html');
- if (htmlEl.length) {
- try {
- htmlEl.html(data);
+ /**
+ * generates actually object which will be consumed from `data` property
+ * feed it to the success callback.
+ * if error occurs, the error is passed to the failure callback
+ *
+ * @param generator {Object or Promise or Function}
+ * @param type {string} Display Type
+ * @param successCallback
+ * @param failureCallback
+ */
+ const generateData = function(generator, type, successCallback, failureCallback) {
+ if (FrontendInterpreterResult.isFunctionGenerator(generator)) {
+ try {
+ successCallback(generator());
+ } catch (error) {
+ failureCallback(error);
+ console.error(`Failed to handle ${type} type, function generator\n`, error);
+ }
+ } else if (FrontendInterpreterResult.isPromiseGenerator(generator)) {
+ generator
+ .then((generated) => { successCallback(generated); })
+ .catch((error) => {
+ failureCallback(error);
+ console.error(`Failed to handle ${type} type, promise generator\n`, error);
+ });
+ } else if (FrontendInterpreterResult.isObjectGenerator(generator)) {
+ try {
+ successCallback(generator);
+ } catch (error) {
+ console.error(`Failed to handle ${type} type, object generator\n`, error);
+ }
+ }
+ };
- htmlEl.find('pre code').each(function(i, e) {
- hljs.highlightBlock(e);
- });
- /*eslint new-cap: [2, {"capIsNewExceptions": ["MathJax.Hub.Queue"]}]*/
- MathJax.Hub.Queue(['Typeset', MathJax.Hub, htmlEl[0]]);
- } catch (err) {
- console.log('HTML rendering error %o', err);
- }
- } else {
+ const renderElem = function(targetElemId, generator) {
+ function retryRenderer() {
+ const elem = angular.element(`#${targetElemId}`);
+ if (!elem.length) {
$timeout(retryRenderer, 10);
+ return;
}
- };
+
+ generateData(() => { generator(targetElemId) }, DefaultDisplayType.ELEMENT,
+ () => {}, /** HTML element will be filled in generator . thus pass empty success callback */
+ (error) => { elem.html(`${error.stack}`); }
+ );
+ }
+
$timeout(retryRenderer);
};
- var renderAngular = function() {
- var retryRenderer = function() {
- if (angular.element('#p' + $scope.id + '_angular').length) {
- try {
- angular.element('#p' + $scope.id + '_angular').html(data);
-
- var paragraphScope = noteVarShareService.get(paragraph.id + '_paragraphScope');
- $compile(angular.element('#p' + $scope.id + '_angular').contents())(paragraphScope);
- } catch (err) {
- console.log('ANGULAR rendering error %o', err);
- }
- } else {
+ const renderHtml = function(targetElemId, generator) {
+ function retryRenderer() {
+ const elem = angular.element(`#${targetElemId}`);
+ if (!elem.length) {
$timeout(retryRenderer, 10);
+ return;
}
- };
+
+ generateData(generator, DefaultDisplayType.HTML,
+ (generated) => {
+ elem.html(generated);
+ elem.find('pre code').each(function(i, e) {
+ hljs.highlightBlock(e);
+ });
+ /*eslint new-cap: [2, {"capIsNewExceptions": ["MathJax.Hub.Queue"]}]*/
+ MathJax.Hub.Queue(['Typeset', MathJax.Hub, elem[0]]);
+ },
+ (error) => { elem.html(`${error.stack}`); }
+ );
+ }
+
$timeout(retryRenderer);
};
- var getTextEl = function (paragraphId) {
- return angular.element('#p' + $scope.id + '_text');
- }
+ const renderAngular = function(targetElemId, generator) {
+ function retryRenderer() {
+ const elem = angular.element(`#${targetElemId}`);
+ if (!elem.length) {
+ $timeout(retryRenderer, 10);
+ return;
+ }
- var textRendererInitialized = false;
- var renderText = function() {
- var retryRenderer = function() {
- var textEl = getTextEl($scope.id);
- if (textEl.length) {
- // clear all lines before render
- clearTextOutput();
- textRendererInitialized = true;
+ const paragraphScope = noteVarShareService.get(`${paragraph.id}_paragraphScope`);
+ generateData(generator, DefaultDisplayType.ANGULAR,
+ (generated) => {
+ elem.html(generated);
+ $compile(elem.contents())(paragraphScope);
+ },
+ (error) => { elem.html(`${error.stack}`); }
+ );
+ }
- if (data) {
- appendTextOutput(data);
- } else {
- flushAppendQueue();
- }
+ $timeout(retryRenderer);
+ };
- getTextEl($scope.id).bind('mousewheel', function(e) {
- $scope.keepScrollDown = false;
- });
- } else {
+ const getTextResultElem = function (resultId) {
+ return angular.element('#p' + resultId + '_text');
+ };
+
+ const renderText = function(targetElemId, generator) {
+ function retryRenderer() {
+ const elem = angular.element(`#${targetElemId}`);
+ if (!elem.length) {
$timeout(retryRenderer, 10);
+ return;
}
- };
+
+ generateData(generator, DefaultDisplayType.TEXT,
+ (generated) => {
+ // clear all lines before render
+ clearTextOutput();
+ $scope.textRendererInitialized = true;
+ if (generated) { appendTextOutput(generated); }
+ else { flushAppendQueue(); }
+ elem.bind('mousewheel', (e) => { $scope.keepScrollDown = false; });
+ },
+ (error) => { elem.html(`${error.stack}`); }
+ );
+ }
+
$timeout(retryRenderer);
- };
+ }
var clearTextOutput = function() {
- var textEl = getTextEl($scope.id);
+ var textEl = getTextResultElem($scope.id);
if (textEl.length) {
textEl.children().remove();
}
@@ -359,11 +439,11 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
};
var appendTextOutput = function(msg) {
- if (!textRendererInitialized) {
+ if (!$scope.textRendererInitialized) {
textAppendQueueBeforeInitialize.push(msg);
} else {
flushAppendQueue();
- var textEl = getTextEl($scope.id);
+ var textEl = getTextResultElem($scope.id);
if (textEl.length) {
var lines = msg.split('\n');
for (var i = 0; i < lines.length; i++) {
@@ -371,7 +451,7 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
}
}
if ($scope.keepScrollDown) {
- var doc = getTextEl($scope.id);
+ var doc = getTextResultElem($scope.id);
doc[0].scrollTop = doc[0].scrollHeight;
}
}
@@ -725,22 +805,23 @@ function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location
});
};
- var renderApp = function(appState) {
- var retryRenderer = function() {
- var targetEl = angular.element(document.getElementById('p' + appState.id));
- console.log('retry renderApp %o', targetEl);
- if (targetEl.length) {
+ const renderApp = function(targetElemId, appState) {
+ function retryRenderer() {
+ var elem = angular.element(document.getElementById(elememId));
+ console.log('retry renderApp %o', elem);
+ if (!elem.length) {
+ $timeout(retryRenderer, 1000);
+ } else {
try {
console.log('renderApp %o', appState);
- targetEl.html(appState.output);
- $compile(targetEl.contents())(getAppScope(appState));
+ elem.html(appState.output);
+ $compile(elem.contents())(getAppScope(appState));
} catch (err) {
console.log('App rendering error %o', err);
}
- } else {
- $timeout(retryRenderer, 1000);
}
- };
+ }
+
$timeout(retryRenderer);
};
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.html b/zeppelin-web/src/app/notebook/paragraph/result/result.html
index df09c4d5f59..90fa9bc6c0e 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.html
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.html
@@ -67,13 +67,15 @@
tooltip="Scroll Top">
-