diff --git a/docs/assets/themes/zeppelin/img/docs-img/ipython_kernel.png b/docs/assets/themes/zeppelin/img/docs-img/ipython_kernel.png
new file mode 100644
index 00000000000..eefe97e3612
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/ipython_kernel.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/ir_kernel.png b/docs/assets/themes/zeppelin/img/docs-img/ir_kernel.png
new file mode 100644
index 00000000000..a1bf5ec188c
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/ir_kernel.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/julia_kernel.png b/docs/assets/themes/zeppelin/img/docs-img/julia_kernel.png
new file mode 100644
index 00000000000..5c075dc28f0
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/julia_kernel.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/spark_SPARK_HOME16.png b/docs/assets/themes/zeppelin/img/docs-img/spark_SPARK_HOME16.png
new file mode 100644
index 00000000000..f925d47c17e
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/spark_SPARK_HOME16.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/spark_SPARK_HOME24.png b/docs/assets/themes/zeppelin/img/docs-img/spark_SPARK_HOME24.png
new file mode 100644
index 00000000000..0eaa063d608
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/spark_SPARK_HOME24.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/spark_inline_configuration.png b/docs/assets/themes/zeppelin/img/docs-img/spark_inline_configuration.png
new file mode 100644
index 00000000000..c02785b62b8
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/spark_inline_configuration.png differ
diff --git a/docs/assets/themes/zeppelin/img/docs-img/spark_user_impersonation.png b/docs/assets/themes/zeppelin/img/docs-img/spark_user_impersonation.png
new file mode 100644
index 00000000000..f16f402f811
Binary files /dev/null and b/docs/assets/themes/zeppelin/img/docs-img/spark_user_impersonation.png differ
diff --git a/docs/interpreter/jupyter.md b/docs/interpreter/jupyter.md
new file mode 100644
index 00000000000..89586e6bf00
--- /dev/null
+++ b/docs/interpreter/jupyter.md
@@ -0,0 +1,134 @@
+---
+layout: page
+title: "Jupyter Interpreter for Apache Zeppelin"
+description: "Project Jupyter exists to develop open-source software, open-standards, and services for interactive computing across dozens of programming languages."
+group: interpreter
+---
+
+{% include JB/setup %}
+
+# Jupyter Interpreter for Apache Zeppelin
+
+
+
+## Overview
+
+Project [Jupyter](https://jupyter.org/) exists to develop open-source software, open-standards, and services for interactive computing across dozens of programming languages.
+Zeppelin's Jupyter interpreter is a bridge/adapter between Zeppelin interpreter and Jupyter kernel. You can use any of jupyter kernel as long as you installed the necessary dependencies.
+
+## Configuration
+
+To run any Jupyter kernel in Zeppelin you first need to install the following prerequisite:
+
+* pip install jupyter-client
+* pip install grpcio
+* pip install protobuf
+
+Then you need install the jupyter kernel you want to use. In the following sections, we will talk about how to use the following 3 jupyter kernels in Zeppelin:
+
+* ipython
+* ir
+* julia
+
+## Jupyter Python kernel
+
+In order to use Jupyter Python kernel in Zeppelin, you need to install `ipykernel` first.
+
+```bash
+
+pip install ipykernel
+```
+
+Then you can run python code in Jupyter interpreter like following.
+
+```python
+
+%jupyter(kernel=python)
+
+%matplotlib inline
+import matplotlib.pyplot as plt
+plt.plot([1, 2, 3])
+```
+
+
+
+## Jupyter R kernel
+
+In order to use [IRKernel](https://github.com/IRkernel/IRkernel), you need to first install `IRkernel` package in R.
+
+```r
+install.packages('IRkernel')
+IRkernel::installspec() # to register the kernel in the current R installation
+```
+
+Then you can run r code in Jupyter interpreter like following.
+
+```r
+%jupyter(kernel=ir)
+
+library(ggplot2)
+ggplot(mpg, aes(x = displ, y = hwy)) +
+ geom_point()
+```
+
+
+
+
+## Jupyter Julia kernel
+
+In order to use Julia in Zeppelin, you first need to install [IJulia](https://github.com/JuliaLang/IJulia.jl) first
+
+```julia
+using Pkg
+Pkg.add("IJulia")
+
+```
+
+Then you can run julia code in Jupyter interpreter like following.
+
+```julia
+
+%jupyter(kernel=julia-1.3)
+
+using Pkg
+Pkg.add("Plots")
+using Plots
+plotly() # Choose the Plotly.jl backend for web interactivity
+plot(rand(5,5),linewidth=2,title="My Plot")
+Pkg.add("PyPlot") # Install a different backend
+pyplot() # Switch to using the PyPlot.jl backend
+plot(rand(5,5),linewidth=2,title="My Plot")
+```
+
+
+
+
+## Use any other kernel
+
+For any other jupyter kernel, you can follow the below steps to use it in Zeppelin.
+
+1. Install the specified jupyter kernel. you can find all the available jupyter kernels [here](https://github.com/jupyter/jupyter/wiki/Jupyter-kernels)
+2. Find its kernel name by run the following command
+ ```bash
+ jupyter kernelspec list
+ ```
+3. Run the kernel as following
+
+```python
+
+%jupyter(kernel=kernel_name)
+
+code
+```
\ No newline at end of file
diff --git a/docs/interpreter/shell.md b/docs/interpreter/shell.md
index 9da2f091ad8..c38391b8e14 100644
--- a/docs/interpreter/shell.md
+++ b/docs/interpreter/shell.md
@@ -91,7 +91,7 @@ For changing the default behavior of when to renew Kerberos ticket following cha
```bash
# Change Kerberos refresh interval (default value is 1d). Allowed postfix are ms, s, m, min, h, and d.
-export LAUNCH_KERBEROS_REFRESH_INTERVAL=4h
+export KERBEROS_REFRESH_INTERVAL=4h
# Change kinit number retries (default value is 5), which means if the kinit command fails for 5 retries consecutively it will close the interpreter.
export KINIT_FAIL_THRESHOLD=10
```
diff --git a/docs/interpreter/spark.md b/docs/interpreter/spark.md
index bd50cb017b9..ef799593d26 100644
--- a/docs/interpreter/spark.md
+++ b/docs/interpreter/spark.md
@@ -37,18 +37,18 @@ Apache Spark is supported in Zeppelin with Spark interpreter group which consist
%spark
SparkInterpreter
-
Creates a SparkContext and provides a Scala environment
-
-
-
%spark.kotlin
-
KotlinSparkInterpreter
-
Provides a Kotlin environment
+
Creates a SparkContext/SparkSession and provides a Scala environment
%spark.pyspark
PySparkInterpreter
Provides a Python environment
+
+
%spark.ipyspark
+
IPySparkInterpreter
+
Provides a IPython environment
+
%spark.r
SparkRInterpreter
@@ -60,9 +60,9 @@ Apache Spark is supported in Zeppelin with Spark interpreter group which consist
Provides a SQL environment
-
%spark.dep
-
DepInterpreter
-
Dependency loader
+
%spark.kotlin
+
KotlinSparkInterpreter
+
Provides a Kotlin environment
@@ -76,42 +76,58 @@ You can also set other Spark properties which are not listed in the table. For a
Description
-
args
+
`SPARK_HOME`
-
Spark commandline args
-
+
Location of spark distribution
+
+
master
local[*]
-
Spark master uri. ex) spark://masterhost:7077
+
Spark master uri. e.g. spark://master_host:7077
spark.app.name
Zeppelin
The name of spark application.
-
spark.cores.max
-
-
Total number of cores to use. Empty value uses all available core.
+
spark.driver.cores
+
1
+
Number of cores to use for the driver process, only in cluster mode.
-
spark.executor.memory
+
spark.driver.memory
1g
-
Executor memory per worker instance. ex) 512m, 32g
+
Amount of memory to use for the driver process, i.e. where SparkContext is initialized, in the same format as JVM memory strings with a size unit suffix ("k", "m", "g" or "t") (e.g. 512m, 2g).
A list of `id,remote-repository-URL,is-snapshot;` for each remote repository.
+
spark.executor.cores
+
1
+
The number of cores to use on each executor
-
zeppelin.dep.localrepo
-
local-repo
-
Local repository for dependency loader
+
spark.executor.memory
+
1g
+
Executor memory per worker instance. e.g. 512m, 32g
+
+
+
spark.files
+
+
Comma-separated list of files to be placed in the working directory of each executor. Globs are allowed.
+
+
+
spark.jars
+
+
Comma-separated list of jars to include on the driver and executor classpaths. Globs are allowed.
+
+
+
spark.jars.packages
+
+
Comma-separated list of Maven coordinates of jars to include on the driver and executor classpaths. The coordinates should be groupId:artifactId:version. If spark.jars.ivySettings is given artifacts will be resolved according to the configuration in the file, otherwise artifacts will be searched for in the local maven repo, then maven central and finally any additional remote repositories given by the command-line option --repositories.
`PYSPARK_PYTHON`
python
-
Python binary executable to use for PySpark in both driver and workers (default is python).
+
Python binary executable to use for PySpark in both driver and executors (default is python).
Property spark.pyspark.python take precedence if it is set
@@ -120,6 +136,16 @@ You can also set other Spark properties which are not listed in the table. For a
Python binary executable to use for PySpark in driver only (default is `PYSPARK_PYTHON`).
Property spark.pyspark.driver.python take precedence if it is set
+
+
zeppelin.pyspark.useIPython
+
false
+
Whether use IPython when the ipython prerequisites are met in `%spark.pyspark`
+
+
+
zeppelin.R.cmd
+
R
+
R binary executable path.
+
zeppelin.spark.concurrentSQL
false
@@ -133,22 +159,17 @@ You can also set other Spark properties which are not listed in the table. For a
zeppelin.spark.maxResult
1000
-
Max number of Spark SQL result to display.
+
Max number rows of Spark SQL result to display.
zeppelin.spark.printREPLOutput
true
-
Print REPL output
+
Print scala REPL output
zeppelin.spark.useHiveContext
true
-
Use HiveContext instead of SQLContext if it is true.
-
-
-
zeppelin.spark.importImplicit
-
true
-
Import implicits, UDF collection, and sql if set true.
+
Use HiveContext instead of SQLContext if it is true. Enable hive for SparkSession
zeppelin.spark.enableSupportedVersionCheck
@@ -158,47 +179,68 @@ You can also set other Spark properties which are not listed in the table. For a
zeppelin.spark.sql.interpolation
false
-
Enable ZeppelinContext variable interpolation into paragraph text
+
Enable ZeppelinContext variable interpolation into spark sql
zeppelin.spark.uiWebUrl
Overrides Spark UI default URL. Value should be a full URL (ex: http://{hostName}/{uniquePath}
-
zeppelin.spark.scala.color
-
true
-
Whether to enable color output of spark scala interpreter
-
Without any configuration, Spark interpreter works out of box in local mode. But if you want to connect to your Spark cluster, you'll need to follow below two simple steps.
-### 1. Export SPARK_HOME
-In `conf/zeppelin-env.sh`, export `SPARK_HOME` environment variable with your Spark installation path.
+### Export SPARK_HOME
-For example,
+There are several options for setting `SPARK_HOME`.
+
+* Set `SPARK_HOME` in `zeppelin-env.sh`
+* Set `SPARK_HOME` in Interpreter setting page
+* Set `SPARK_HOME` via [inline generic configuration](../usage/interpreter/overview.html#inline-generic-confinterpreter)
+
+#### 1. Set `SPARK_HOME` in `zeppelin-env.sh`
+
+If you work with only one version of spark, then you can set `SPARK_HOME` in `zeppelin-env.sh` because any setting in `zeppelin-env.sh` is globally applied.
+
+e.g.
```bash
export SPARK_HOME=/usr/lib/spark
```
-You can optionally set more environment variables
+You can optionally set more environment variables in `zeppelin-env.sh`
```bash
# set hadoop conf dir
export HADOOP_CONF_DIR=/usr/lib/hadoop
-# set options to pass spark-submit command
-export SPARK_SUBMIT_OPTIONS="--packages com.databricks:spark-csv_2.10:1.2.0"
-
-# extra classpath. e.g. set classpath for hive-site.xml
-export ZEPPELIN_INTP_CLASSPATH_OVERRIDES=/etc/hive/conf
```
-For Windows, ensure you have `winutils.exe` in `%HADOOP_HOME%\bin`. Please see [Problems running Hadoop on Windows](https://wiki.apache.org/hadoop/WindowsProblems) for the details.
-### 2. Set master in Interpreter menu
-After start Zeppelin, go to **Interpreter** menu and edit **master** property in your Spark interpreter setting. The value may vary depending on your Spark cluster deployment type.
+#### 2. Set `SPARK_HOME` in Interpreter setting page
+
+If you want to use multiple versions of spark, then you need create multiple spark interpreters and set `SPARK_HOME` for each of them. e.g.
+Create a new spark interpreter `spark24` for spark 2.4 and set `SPARK_HOME` in interpreter setting page
+
+
+
+
+Create a new spark interpreter `spark16` for spark 1.6 and set `SPARK_HOME` in interpreter setting page
+
+
+
+
+
+#### 3. Set `SPARK_HOME` via [inline generic configuration](../usage/interpreter/overview.html#inline-generic-confinterpreter)
+
+Besides setting `SPARK_HOME` in interpreter setting page, you can also use inline generic configuration to put the
+configuration with code together for more flexibility. e.g.
+
+
+
+
+### Set master in Interpreter menu
+After starting Zeppelin, go to **Interpreter** menu and edit **master** property in your Spark interpreter setting. The value may vary depending on your Spark cluster deployment type.
For example,
@@ -213,93 +255,132 @@ For the further information about Spark & Zeppelin version compatibility, please
> Note that without exporting `SPARK_HOME`, it's running in local mode with included version of Spark. The included version may vary depending on the build profile.
-### 3. Yarn mode
-Zeppelin support both yarn client and yarn cluster mode (yarn cluster mode is supported from 0.8.0). For yarn mode, you must specify `SPARK_HOME` & `HADOOP_CONF_DIR`.
-You can either specify them in `zeppelin-env.sh`, or in interpreter setting page. Specifying them in `zeppelin-env.sh` means you can use only one version of `spark` & `hadoop`. Specifying them
-in interpreter setting page means you can use multiple versions of `spark` & `hadoop` in one zeppelin instance.
-
-### 4. New Version of SparkInterpreter
-Starting from 0.9, we totally removed the old spark interpreter implementation, and make the new spark interpreter as the official spark interpreter.
-
## SparkContext, SQLContext, SparkSession, ZeppelinContext
-SparkContext, SQLContext and ZeppelinContext are automatically created and exposed as variable names `sc`, `sqlContext` and `z`, respectively, in Scala, Kotlin, Python and R environments.
-Staring from 0.6.1 SparkSession is available as variable `spark` when you are using Spark 2.x.
-
-> Note that Scala/Python/R environment shares the same SparkContext, SQLContext and ZeppelinContext instance.
-
+SparkContext, SQLContext, SparkSession (for spark 2.x) and ZeppelinContext are automatically created and exposed as variable names `sc`, `sqlContext`, `spark` and `z`, respectively, in Scala, Kotlin, Python and R environments.
-### How to pass property to SparkConf
-There're 2 kinds of properties that would be passed to SparkConf
+> Note that Scala/Python/R environment shares the same SparkContext, SQLContext, SparkSession and ZeppelinContext instance.
- * Standard spark property (prefix with `spark.`). e.g. `spark.executor.memory` will be passed to `SparkConf`
- * Non-standard spark property (prefix with `zeppelin.spark.`). e.g. `zeppelin.spark.property_1`, `property_1` will be passed to `SparkConf`
+## YARN Mode
+Zeppelin support both yarn client and yarn cluster mode (yarn cluster mode is supported from 0.8.0). For yarn mode, you must specify `SPARK_HOME` & `HADOOP_CONF_DIR`.
+Usually you only have one hadoop cluster, so you can set `HADOOP_CONF_DIR` in `zeppelin-env.sh` which is applied to all spark interpreters. If you want to use spark against multiple hadoop cluster, then you need to define
+`HADOOP_CONF_DIR` in interpreter setting or via inline generic configuration.
## Dependency Management
-For spark interpreter, you should not use Zeppelin's [Dependency Management](../usage/interpreter/dependency_management.html) for managing
-third party dependencies, (`%spark.dep` also is not the recommended approach starting from Zeppelin 0.8). Instead you should set spark properties (`spark.jars`, `spark.files`, `spark.jars.packages`) in 2 ways.
+For spark interpreter, it is not recommended to use Zeppelin's [Dependency Management](../usage/interpreter/dependency_management.html) for managing
+third party dependencies (`%spark.dep` is removed from Zeppelin 0.9 as well). Instead you should set the standard Spark properties.
-
spark-defaults.conf
-
SPARK_SUBMIT_OPTIONS
+
Spark Property
+
Spark Submit Argument
Description
+
+
spark.files
+
--files
+
Comma-separated list of files to be placed in the working directory of each executor. Globs are allowed.
+
spark.jars
--jars
-
Comma-separated list of local jars to include on the driver and executor classpaths.
+
Comma-separated list of jars to include on the driver and executor classpaths. Globs are allowed.
spark.jars.packages
--packages
-
Comma-separated list of maven coordinates of jars to include on the driver and executor classpaths. Will search the local maven repo, then maven central and any additional remote repositories given by --repositories. The format for the coordinates should be groupId:artifactId:version.
-
-
-
spark.files
-
--files
-
Comma-separated list of files to be placed in the working directory of each executor.
+
Comma-separated list of Maven coordinates of jars to include on the driver and executor classpaths. The coordinates should be groupId:artifactId:version. If spark.jars.ivySettings is given artifacts will be resolved according to the configuration in the file, otherwise artifacts will be searched for in the local maven repo, then maven central and finally any additional remote repositories given by the command-line option --repositories.
-### 1. Set spark properties in zeppelin side.
+You can either set Spark properties in interpreter setting page or set Spark submit arguments in `zeppelin-env.sh` via environment variable `SPARK_SUBMIT_OPTIONS`.
+For examples:
+
+```bash
+export SPARK_SUBMIT_OPTIONS="--files --jars --packages "
+```
+
+But it is not recommended to set them in `SPARK_SUBMIT_OPTIONS`. Because it will be shared by all spark interpreters, which means you can not set different dependencies for different users.
-In zeppelin side, you can either set them in spark interpreter setting page or via [Generic ConfInterpreter](../usage/interpreter/overview.html).
-It is not recommended to set them in `SPARK_SUBMIT_OPTIONS`. Because it will be shared by all spark interpreters, you can not set different dependencies for different users.
-### 2. Set spark properties in spark side.
+## PySpark
-In spark side, you can set them in `spark-defaults.conf`.
+There're 2 ways to use PySpark in Zeppelin:
-e.g.
+* Vanilla PySpark
+* IPySpark
- ```
- spark.jars /path/mylib1.jar,/path/mylib2.jar
- spark.jars.packages com.databricks:spark-csv_2.10:1.2.0
- spark.files /path/mylib1.py,/path/mylib2.egg,/path/mylib3.zip
- ```
+### Vanilla PySpark (Not Recommended)
+Vanilla PySpark interpreter is almost the same as vanilla Python interpreter except Zeppelin inject SparkContext, SQLContext, SparkSession via variables `sc`, `sqlContext`, `spark`.
+By default, Zeppelin would use IPython in `%spark.pyspark` when IPython is available, Otherwise it would fall back to the original PySpark implementation.
+If you don't want to use IPython, then you can set `zeppelin.pyspark.useIPython` as `false` in interpreter setting. For the IPython features, you can refer doc
+[Python Interpreter](python.html)
-## ZeppelinContext
-Zeppelin automatically injects `ZeppelinContext` as variable `z` in your Scala/Python environment. `ZeppelinContext` provides some additional functions and utilities.
-See [Zeppelin-Context](../usage/other_features/zeppelin_context.html) for more details.
+### IPySpark (Recommended)
+You can use `IPySpark` explicitly via `%spark.ipyspark`. IPySpark interpreter is almost the same as IPython interpreter except Zeppelin inject SparkContext, SQLContext, SparkSession via variables `sc`, `sqlContext`, `spark`.
+For the IPython features, you can refer doc [Python Interpreter](python.html)
+
+## SparkR
+
+Zeppelin support SparkR via `%spark.r`. Here's configuration for SparkR Interpreter.
+
+
+
+
Spark Property
+
Default
+
Description
+
+
+
zeppelin.R.cmd
+
R
+
R binary executable path.
+
+
+
zeppelin.R.knitr
+
true
+
Whether use knitr or not. (It is recommended to install knitr and use it in Zeppelin)
+ `,
+ styles: [`
+ pre {
+ background: #fff7e7;
+ padding: 10px;
+ border: 1px solid #ffd278;
+ color: #fa7e14;
+ border-radius: 3px;
+ }
+ `],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class JsonVisComponent implements OnInit {
+ tableData: TableData;
+ constructor(@Inject(VISUALIZATION) public visualization: Visualization, private cdr: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ }
+
+ render(): void {
+ this.tableData = this.visualization.transformed;
+ }
+
+}
diff --git a/zeppelin-web-angular/projects/helium-vis-example/src/json-vis.module.ts b/zeppelin-web-angular/projects/helium-vis-example/src/json-vis.module.ts
new file mode 100644
index 00000000000..2578f26df39
--- /dev/null
+++ b/zeppelin-web-angular/projects/helium-vis-example/src/json-vis.module.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { JsonVisComponent } from './json-vis.component';
+import { CommonModule } from '@angular/common';
+
+@NgModule({
+ imports: [CommonModule],
+ declarations: [JsonVisComponent],
+ entryComponents: [JsonVisComponent],
+ exports: [JsonVisComponent]
+})
+export class JsonVisModule { }
diff --git a/zeppelin-web-angular/projects/helium-vis-example/src/json-visualization.ts b/zeppelin-web-angular/projects/helium-vis-example/src/json-visualization.ts
new file mode 100644
index 00000000000..9f97db4598f
--- /dev/null
+++ b/zeppelin-web-angular/projects/helium-vis-example/src/json-visualization.ts
@@ -0,0 +1,64 @@
+/*
+ * Licensed 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 { CdkPortalOutlet } from '@angular/cdk/portal';
+import { ComponentFactoryResolver, ViewContainerRef } from '@angular/core';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import {
+ TableTransformation,
+ Transformation,
+ Visualization,
+ VisualizationComponentPortal
+} from '@zeppelin/visualization';
+
+import { JsonVisComponent } from './json-vis.component';
+
+export class JsonVisualization extends Visualization {
+ tableTransformation = new TableTransformation(this.getConfig());
+ componentPortal = new VisualizationComponentPortal(
+ this,
+ JsonVisComponent,
+ this.portalOutlet,
+ this.viewContainerRef,
+ this.componentFactoryResolver
+ );
+ constructor(config: GraphConfig,
+ private portalOutlet: CdkPortalOutlet,
+ private viewContainerRef: ViewContainerRef,
+ private componentFactoryResolver?: ComponentFactoryResolver) {
+ super(config);
+ }
+
+ destroy(): void {
+ if (this.componentRef) {
+ this.componentRef.destroy();
+ this.componentRef = null;
+ }
+ this.configChange$.complete();
+ this.configChange$ = null;
+ }
+
+ getTransformation(): Transformation {
+ return this.tableTransformation;
+ }
+
+ refresh(): void {}
+
+ render(data): void {
+ this.transformed = data;
+ if (!this.componentRef) {
+ this.componentRef = this.componentPortal.attachComponentPortal();
+ }
+ this.componentRef.instance.render();
+ }
+}
diff --git a/zeppelin-web-angular/projects/helium-vis-example/src/public-api.ts b/zeppelin-web-angular/projects/helium-vis-example/src/public-api.ts
new file mode 100644
index 00000000000..82ba8d0ce42
--- /dev/null
+++ b/zeppelin-web-angular/projects/helium-vis-example/src/public-api.ts
@@ -0,0 +1,30 @@
+/*
+ * Licensed 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.
+ */
+
+/*
+ * Public API Surface of helium-vis-example
+ */
+
+import { createHeliumPackage, HeliumPackageType } from '@zeppelin/helium';
+import { JsonVisComponent } from './json-vis.component';
+import { JsonVisModule } from './json-vis.module';
+import { JsonVisualization } from './json-visualization';
+
+export default createHeliumPackage({
+ name: 'helium-vis-example',
+ id: 'heliumVisExample',
+ icon: 'appstore',
+ type: HeliumPackageType.Visualization,
+ module: JsonVisModule,
+ component: JsonVisComponent,
+ visualization: JsonVisualization
+});
diff --git a/zeppelin-web-angular/projects/helium-vis-example/src/test.ts b/zeppelin-web-angular/projects/helium-vis-example/src/test.ts
new file mode 100644
index 00000000000..9be59f628dd
--- /dev/null
+++ b/zeppelin-web-angular/projects/helium-vis-example/src/test.ts
@@ -0,0 +1,33 @@
+/*
+ * Licensed 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.
+ */
+
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone';
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/zeppelin-web-angular/projects/helium-vis-example/tsconfig.lib.json b/zeppelin-web-angular/projects/helium-vis-example/tsconfig.lib.json
new file mode 100644
index 00000000000..bd23948e591
--- /dev/null
+++ b/zeppelin-web-angular/projects/helium-vis-example/tsconfig.lib.json
@@ -0,0 +1,26 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/lib",
+ "target": "es2015",
+ "declaration": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": [
+ "dom",
+ "es2018"
+ ]
+ },
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true
+ },
+ "exclude": [
+ "src/test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/zeppelin-web-angular/projects/helium-vis-example/tsconfig.spec.json b/zeppelin-web-angular/projects/helium-vis-example/tsconfig.spec.json
new file mode 100644
index 00000000000..16da33db072
--- /dev/null
+++ b/zeppelin-web-angular/projects/helium-vis-example/tsconfig.spec.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "src/test.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/zeppelin-web-angular/projects/helium-vis-example/tslint.json b/zeppelin-web-angular/projects/helium-vis-example/tslint.json
new file mode 100644
index 00000000000..124133f8499
--- /dev/null
+++ b/zeppelin-web-angular/projects/helium-vis-example/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "lib",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "lib",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/README.md b/zeppelin-web-angular/projects/zeppelin-helium/README.md
new file mode 100644
index 00000000000..16ea0ea2bd6
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/README.md
@@ -0,0 +1,36 @@
+
+
+# ZeppelinHelium
+
+This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.8.
+
+## Code scaffolding
+
+Run `ng generate component component-name --project zeppelin-helium` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project zeppelin-helium`.
+> Note: Don't forget to add `--project zeppelin-helium` or else it will be added to the default project in your `angular.json` file.
+
+## Build
+
+Run `ng build zeppelin-helium` to build the project. The build artifacts will be stored in the `dist/` directory.
+
+## Publishing
+
+After building your library with `ng build zeppelin-helium`, go to the dist folder `cd dist/zeppelin-helium` and run `npm publish`.
+
+## Running unit tests
+
+Run `ng test zeppelin-helium` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/karma.conf.js b/zeppelin-web-angular/projects/zeppelin-helium/karma.conf.js
new file mode 100644
index 00000000000..3ddb74a1a49
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/karma.conf.js
@@ -0,0 +1,44 @@
+/*
+ * Licensed 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.
+ */
+
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../../coverage/zeppelin-helium'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false,
+ restartOnFileChange: true
+ });
+};
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/ng-package.json b/zeppelin-web-angular/projects/zeppelin-helium/ng-package.json
new file mode 100644
index 00000000000..da717624c15
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../dist/zeppelin-helium",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ }
+}
\ No newline at end of file
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/package.json b/zeppelin-web-angular/projects/zeppelin-helium/package.json
new file mode 100644
index 00000000000..cd0b499afa9
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "@zeppelin/helium",
+ "version": "0.0.0",
+ "peerDependencies": {
+ "@angular/common": "~8.2.8",
+ "@angular/core": "~8.2.8",
+ "@angular/forms": "~8.2.7",
+ "@angular/router": "~8.2.7",
+ "rxjs": "~6.5.3",
+ "ng-zorro-antd": "^8.3.0"
+ }
+}
\ No newline at end of file
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/src/common-deps.ts b/zeppelin-web-angular/projects/zeppelin-helium/src/common-deps.ts
new file mode 100644
index 00000000000..677b3d7d1cf
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/src/common-deps.ts
@@ -0,0 +1,43 @@
+/*
+ * Licensed 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 * as common from '@angular/common';
+import * as core from '@angular/core';
+import * as forms from '@angular/forms';
+import * as router from '@angular/router';
+import * as rxjs from 'rxjs';
+
+import * as dataSet from '@antv/data-set';
+import * as g2 from '@antv/g2';
+import * as sdk from '@zeppelin/sdk';
+import * as visualization from '@zeppelin/visualization';
+import * as lodash from 'lodash';
+
+import * as ngZorro from 'ng-zorro-antd';
+import * as tslib from 'tslib';
+import * as zeppelinHelium from './public-api';
+
+export const COMMON_DEPS = {
+ '@angular/core': core,
+ '@angular/common': common,
+ '@angular/forms': forms,
+ '@angular/router': router,
+ '@antv/data-set': dataSet,
+ '@antv/g2': g2,
+ '@zeppelin/sdk': sdk,
+ '@zeppelin/visualization': visualization,
+ '@zeppelin/helium': zeppelinHelium,
+ 'lodash': lodash,
+ 'ng-zorro-antd': ngZorro,
+ rxjs,
+ tslib
+};
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/src/index.ts b/zeppelin-web-angular/projects/zeppelin-helium/src/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/src/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/src/public-api.ts b/zeppelin-web-angular/projects/zeppelin-helium/src/public-api.ts
new file mode 100644
index 00000000000..4b9b89fd7db
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/src/public-api.ts
@@ -0,0 +1,18 @@
+/*
+ * Licensed 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.
+ */
+
+/*
+ * Public API Surface of zeppelin-helium
+ */
+
+export * from './zeppelin-helium.service';
+export * from './zeppelin-helium.module';
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/src/test.ts b/zeppelin-web-angular/projects/zeppelin-helium/src/test.ts
new file mode 100644
index 00000000000..9be59f628dd
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/src/test.ts
@@ -0,0 +1,33 @@
+/*
+ * Licensed 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.
+ */
+
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone';
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/src/zeppelin-helium.module.ts b/zeppelin-web-angular/projects/zeppelin-helium/src/zeppelin-helium.module.ts
new file mode 100644
index 00000000000..e24ac800a78
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/src/zeppelin-helium.module.ts
@@ -0,0 +1,16 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+
+@NgModule({})
+export class ZeppelinHeliumModule { }
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/src/zeppelin-helium.service.ts b/zeppelin-web-angular/projects/zeppelin-helium/src/zeppelin-helium.service.ts
new file mode 100644
index 00000000000..10f8997dbf7
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/src/zeppelin-helium.service.ts
@@ -0,0 +1,96 @@
+/*
+ * Licensed 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 { Injectable, Type } from '@angular/core';
+import { Visualization } from '@zeppelin/visualization';
+import { COMMON_DEPS } from './common-deps';
+import { ZeppelinHeliumModule } from './zeppelin-helium.module';
+
+// tslint:disable-next-line:no-any
+const SystemJs = (window as any).System;
+
+// tslint:disable-next-line:no-any
+export class ZeppelinHeliumPackage {
+ constructor(
+ public name: string,
+ public id: string,
+ // tslint:disable-next-line:no-any
+ public module: Type,
+ // tslint:disable-next-line:no-any
+ public component: Type,
+ // tslint:disable-next-line:no-any
+ public visualization?: any,
+ public icon = 'build'
+ ) {
+ }
+}
+
+export enum HeliumPackageType {
+ Visualization
+}
+
+// tslint:disable-next-line:no-any
+export function createHeliumPackage(config: {
+ name: string;
+ id: string;
+ icon?: string;
+ type: HeliumPackageType;
+ // tslint:disable-next-line:no-any
+ module: Type;
+ // tslint:disable-next-line:no-any
+ component: Type;
+ // tslint:disable-next-line:no-any
+ visualization?: any
+}) {
+ return new ZeppelinHeliumPackage(
+ config.name,
+ config.id,
+ config.module,
+ config.component,
+ config.visualization,
+ config.icon
+ );
+}
+
+@Injectable({
+ providedIn: ZeppelinHeliumModule
+})
+export class ZeppelinHeliumService {
+
+ depsDefined = false;
+
+ constructor() { }
+
+ defineDeps() {
+ if (this.depsDefined) {
+ return;
+ }
+ Object.keys(COMMON_DEPS).forEach(externalKey =>
+ // tslint:disable-next-line:no-any
+ (window as any).define(externalKey, [], () => COMMON_DEPS[ externalKey ])
+ );
+ this.depsDefined = true;
+ }
+
+ loadPackage(name: string): Promise {
+ this.defineDeps();
+ return SystemJs.import(`./assets/helium-packages/${name}.umd.js`)
+ .then(() => SystemJs.import(name))
+ .then(plugin => {
+ if (plugin instanceof ZeppelinHeliumPackage) {
+ return Promise.resolve(plugin);
+ } else {
+ throw new TypeError('This module is not a valid helium package');
+ }
+ });
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/tsconfig.lib.json b/zeppelin-web-angular/projects/zeppelin-helium/tsconfig.lib.json
new file mode 100644
index 00000000000..45b781973db
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/tsconfig.lib.json
@@ -0,0 +1,27 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/lib",
+ "target": "es2015",
+ "declaration": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": [
+ "dom",
+ "es2018"
+ ]
+ },
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true,
+ "flatModuleId": "@zeppelin/helium"
+ },
+ "exclude": [
+ "src/test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/tsconfig.spec.json b/zeppelin-web-angular/projects/zeppelin-helium/tsconfig.spec.json
new file mode 100644
index 00000000000..16da33db072
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/tsconfig.spec.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "src/test.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-helium/tslint.json b/zeppelin-web-angular/projects/zeppelin-helium/tslint.json
new file mode 100644
index 00000000000..124133f8499
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-helium/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "lib",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "lib",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/README.md b/zeppelin-web-angular/projects/zeppelin-sdk/README.md
new file mode 100644
index 00000000000..91a87204e35
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/README.md
@@ -0,0 +1,36 @@
+
+
+# ZeppelinSdk
+
+This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.9.
+
+## Code scaffolding
+
+Run `ng generate component component-name --project zeppelin-sdk` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project zeppelin-sdk`.
+> Note: Don't forget to add `--project zeppelin-sdk` or else it will be added to the default project in your `angular.json` file.
+
+## Build
+
+Run `ng build zeppelin-sdk` to build the project. The build artifacts will be stored in the `dist/` directory.
+
+## Publishing
+
+After building your library with `ng build zeppelin-sdk`, go to the dist folder `cd dist/zeppelin-sdk` and run `npm publish`.
+
+## Running unit tests
+
+Run `ng test zeppelin-sdk` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/karma.conf.js b/zeppelin-web-angular/projects/zeppelin-sdk/karma.conf.js
new file mode 100644
index 00000000000..bf69ea2ebb1
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/karma.conf.js
@@ -0,0 +1,44 @@
+/*
+ * Licensed 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.
+ */
+
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../../coverage/zeppelin-sdk'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false,
+ restartOnFileChange: true
+ });
+};
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/ng-package.json b/zeppelin-web-angular/projects/zeppelin-sdk/ng-package.json
new file mode 100644
index 00000000000..41dc5a032d3
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../dist/zeppelin-sdk",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ }
+}
\ No newline at end of file
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/package.json b/zeppelin-web-angular/projects/zeppelin-sdk/package.json
new file mode 100644
index 00000000000..9be3b66d006
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@zeppelin/sdk",
+ "version": "0.0.1",
+ "peerDependencies": {
+ "@angular/common": "^8.2.9",
+ "@angular/core": "^8.2.9"
+ }
+}
\ No newline at end of file
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/index.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/index.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-common.interface.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-common.interface.ts
new file mode 100644
index 00000000000..0a5ad6dadd5
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-common.interface.ts
@@ -0,0 +1,129 @@
+/*
+ * Licensed 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 type EditorMode =
+ | 'ace/mode/scala'
+ | 'ace/mode/python'
+ | 'ace/mode/r'
+ | 'ace/mode/sql'
+ | 'ace/mode/markdown'
+ | 'ace/mode/sh';
+
+export type EditorCompletionKey = 'TAB' | string;
+export type EditorLanguage = 'scala' | 'python' | 'r' | 'sql' | 'markdown' | 'sh' | string;
+
+export interface Ticket {
+ principal: string;
+ ticket: string;
+ redirectURL?: string;
+ roles: string;
+}
+
+export interface ConfigurationsInfo {
+ configurations: {
+ 'zeppelin.war.tempdir': string;
+ 'zeppelin.notebook.azure.user': string;
+ 'zeppelin.helium.npm.installer.url': string;
+ 'zeppelin.notebook.git.remote.username': string;
+ 'zeppelin.interpreter.remoterunner': string;
+ 'zeppelin.notebook.s3.user': string;
+ 'zeppelin.server.port': string;
+ 'zeppelin.plugins.dir': string;
+ 'zeppelin.notebook.new_format.delete_old': string;
+ 'zeppelin.ssl.truststore.type': string;
+ 'zeppelin.ssl.keystore.path': string;
+ 'zeppelin.notebook.s3.bucket': string;
+ 'zeppelin.notebook.git.remote.access-token': string;
+ 'zeppelin.recovery.dir': string;
+ 'zeppelin.notebook.s3.timeout': string;
+ 'zeppelin.notebook.cron.enable': string;
+ 'zeppelin.server.addr': string;
+ 'zeppelin.username.force.lowercase': string;
+ 'zeppelin.ssl.keystore.type': string;
+ 'zeppelin.ssl.truststore.path': string;
+ 'zeppelin.notebook.dir': string;
+ 'zeppelin.interpreter.lifecyclemanager.class': string;
+ 'zeppelin.notebook.gcs.dir': string;
+ 'zeppelin.notebook.s3.sse': string;
+ 'zeppelin.websocket.max.text.message.size': string;
+ 'zeppelin.notebook.git.remote.origin': string;
+ 'zeppelin.server.authorization.header.clear': string;
+ isRevisionSupported: string;
+ 'zeppelin.interpreter.dep.mvnRepo': string;
+ 'zeppelin.ssl': string;
+ 'zeppelin.notebook.autoInterpreterBinding': string;
+ 'zeppelin.config.storage.class': string;
+ 'zeppelin.helium.node.installer.url': string;
+ 'zeppelin.cluster.heartbeat.interval': string;
+ 'zeppelin.notebook.storage': string;
+ 'zeppelin.notebook.new_format.convert': string;
+ 'zeppelin.interpreter.dir': string;
+ 'zeppelin.anonymous.allowed': string;
+ 'zeppelin.credentials.persist': string;
+ 'zeppelin.notebook.mongo.uri': string;
+ 'zeppelin.config.fs.dir': string;
+ 'zeppelin.server.allowed.origins': string;
+ 'zeppelin.notebook.mongo.database': string;
+ 'zeppelin.encoding': string;
+ 'zeppelin.server.jetty.request.header.size': string;
+ 'zeppelin.search.temp.path': string;
+ 'zeppelin.cluster.heartbeat.timeout': string;
+ 'zeppelin.notebook.s3.endpoint': string;
+ 'zeppelin.notebook.homescreen.hide': string;
+ 'zeppelin.scheduler.threadpool.size': string;
+ 'zeppelin.notebook.azure.share': string;
+ 'zeppelin.helium.yarnpkg.installer.url': string;
+ 'zeppelin.server.strict.transport': string;
+ 'zeppelin.interpreter.setting': string;
+ 'zeppelin.server.xxss.protection': string;
+ 'zeppelin.server.rpc.portRange': string;
+ 'zeppelin.war': string;
+ 'zeppelin.interpreter.output.limit': string;
+ 'zeppelin.dep.localrepo': string;
+ 'zeppelin.interpreter.max.poolsize': string;
+ 'zeppelin.server.ssl.port': string;
+ 'zeppelin.notebook.mongo.collection': string;
+ 'zeppelin.notebook.public': string;
+ 'zeppelin.helium.registry': string;
+ 'zeppelin.server.kerberos.principal': string;
+ 'zeppelin.server.default.dir.allowed': string;
+ 'zeppelin.ssl.client.auth': string;
+ 'zeppelin.server.context.path': string;
+ 'zeppelin.recovery.storage.class': string;
+ 'zeppelin.notebook.default.owner.username': string;
+ 'zeppelin.home': string;
+ 'zeppelin.interpreter.lifecyclemanager.timeout.threshold': string;
+ 'zeppelin.cluster.addr': string;
+ 'zeppelin.notebook.git.remote.url': string;
+ 'zeppelin.notebook.mongo.autoimport': string;
+ 'zeppelin.notebook.one.way.sync': string;
+ 'zeppelin.notebook.homescreen': string;
+ 'zeppelin.interpreter.connect.timeout': string;
+ 'zeppelin.server.xframe.options': string;
+ 'zeppelin.interpreter.lifecyclemanager.timeout.checkinterval': string;
+ 'zeppelin.server.kerberos.keytab': string;
+ 'zeppelin.interpreter.rpc.portRange': string;
+ 'zeppelin.interpreter.group.default': string;
+ 'zeppelin.conf.dir': string;
+ 'zeppelin.interpreter.localRepo': string;
+ 'zeppelin.notebook.collaborative.mode.enable': string;
+ 'zeppelin.search.use.disk': string;
+ };
+}
+
+export interface ErrorInfo {
+ info?: string;
+}
+
+export interface AuthInfo {
+ info?: string;
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
new file mode 100644
index 00000000000..ddf934e2433
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
@@ -0,0 +1,164 @@
+/*
+ * Licensed 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 { AuthInfo, ConfigurationsInfo, ErrorInfo } from './message-common.interface';
+import {
+ CheckpointNote,
+ CloneNote,
+ CollaborativeModeStatus,
+ DeleteNote,
+ EditorSettingReceived,
+ EditorSettingSend,
+ FolderRename,
+ GetInterpreterBindings,
+ GetNode,
+ ListRevision,
+ ListRevisionHistory,
+ MoveFolderToTrash,
+ MoveNoteToTrash,
+ NewNote,
+ Note,
+ NotesInfo,
+ NoteRename,
+ NoteRevision,
+ NoteRevisionForCompare,
+ NoteRunningStatus,
+ NoteUpdate,
+ NoteUpdated,
+ ParagraphAdded,
+ ParagraphMoved,
+ RemoveFolder,
+ RemoveNoteForms,
+ RestoreFolder,
+ RestoreNote,
+ SaveNoteFormsReceived,
+ SaveNoteFormsSend,
+ SetNoteRevision,
+ SetNoteRevisionStatus,
+ UpdateParagraph,
+ UpdatePersonalizedMode
+} from './message-notebook.interface';
+import {
+ AngularObjectClientBind,
+ AngularObjectClientUnbind,
+ AngularObjectRemove,
+ AngularObjectUpdate,
+ AngularObjectUpdated,
+ CancelParagraph,
+ CommitParagraph,
+ Completion,
+ CompletionReceived,
+ CopyParagraph,
+ InsertParagraph,
+ MoveParagraph,
+ ParagraphClearAllOutput,
+ ParagraphClearOutput,
+ ParagraphRemove,
+ ParagraphRemoved,
+ ParasInfo,
+ PatchParagraphReceived,
+ PatchParagraphSend,
+ Progress,
+ RunAllParagraphs,
+ RunParagraph
+} from './message-paragraph.interface';
+
+import { ListNoteJobs, ListUpdateNoteJobs } from './message-job.interface';
+
+import { InterpreterBindings, InterpreterSetting } from './message-interpreter.interface';
+import { OP } from './message-operator.interface';
+
+export type MixMessageDataTypeMap = MessageSendDataTypeMap & MessageReceiveDataTypeMap;
+
+export interface MessageReceiveDataTypeMap {
+ [OP.COMPLETION_LIST]: CompletionReceived;
+ [OP.NOTES_INFO]: NotesInfo;
+ [OP.CONFIGURATIONS_INFO]: ConfigurationsInfo;
+ [OP.NOTE]: Note;
+ [OP.NOTE_REVISION]: NoteRevision;
+ [OP.ERROR_INFO]: ErrorInfo;
+ [OP.LIST_NOTE_JOBS]: ListNoteJobs;
+ [OP.LIST_UPDATE_NOTE_JOBS]: ListUpdateNoteJobs;
+ [OP.INTERPRETER_SETTINGS]: InterpreterSetting;
+ [OP.LIST_REVISION_HISTORY]: ListRevision;
+ [OP.INTERPRETER_BINDINGS]: InterpreterBindings;
+ [OP.COLLABORATIVE_MODE_STATUS]: CollaborativeModeStatus;
+ [OP.SET_NOTE_REVISION]: SetNoteRevisionStatus;
+ [OP.PARAGRAPH_ADDED]: ParagraphAdded;
+ [OP.NOTE_RUNNING_STATUS]: NoteRunningStatus;
+ [OP.NEW_NOTE]: NoteRevision;
+ [OP.SAVE_NOTE_FORMS]: SaveNoteFormsSend;
+ [OP.PARAGRAPH]: UpdateParagraph;
+ [OP.PATCH_PARAGRAPH]: PatchParagraphSend;
+ [OP.PARAGRAPH_REMOVED]: ParagraphRemoved;
+ [OP.EDITOR_SETTING]: EditorSettingReceived;
+ [OP.PROGRESS]: Progress;
+ [OP.PARAGRAPH_MOVED]: ParagraphMoved;
+ [OP.AUTH_INFO]: AuthInfo;
+ [OP.NOTE_UPDATED]: NoteUpdated;
+ [OP.ANGULAR_OBJECT_UPDATE]: AngularObjectUpdate;
+ [OP.ANGULAR_OBJECT_REMOVE]: AngularObjectRemove;
+ [OP.PARAS_INFO]: ParasInfo;
+}
+
+export interface MessageSendDataTypeMap {
+ [OP.PING]: undefined;
+ [OP.LIST_CONFIGURATIONS]: undefined;
+ [OP.LIST_NOTES]: undefined;
+ [OP.GET_HOME_NOTE]: undefined;
+ [OP.RESTORE_ALL]: undefined;
+ [OP.EMPTY_TRASH]: undefined;
+ [OP.RELOAD_NOTES_FROM_REPO]: undefined;
+ [OP.GET_NOTE]: GetNode;
+ [OP.NEW_NOTE]: NewNote;
+ [OP.MOVE_NOTE_TO_TRASH]: MoveNoteToTrash;
+ [OP.MOVE_FOLDER_TO_TRASH]: MoveFolderToTrash;
+ [OP.RESTORE_NOTE]: RestoreNote;
+ [OP.RESTORE_FOLDER]: RestoreFolder;
+ [OP.REMOVE_FOLDER]: RemoveFolder;
+ [OP.DEL_NOTE]: DeleteNote;
+ [OP.CLONE_NOTE]: CloneNote;
+ [OP.NOTE_UPDATE]: NoteUpdate;
+ [OP.UPDATE_PERSONALIZED_MODE]: UpdatePersonalizedMode;
+ [OP.NOTE_RENAME]: NoteRename;
+ [OP.FOLDER_RENAME]: FolderRename;
+ [OP.MOVE_PARAGRAPH]: MoveParagraph;
+ [OP.INSERT_PARAGRAPH]: InsertParagraph;
+ [OP.COPY_PARAGRAPH]: CopyParagraph;
+ [OP.ANGULAR_OBJECT_UPDATED]: AngularObjectUpdated;
+ [OP.ANGULAR_OBJECT_CLIENT_BIND]: AngularObjectClientBind;
+ [OP.ANGULAR_OBJECT_CLIENT_UNBIND]: AngularObjectClientUnbind;
+ [OP.CANCEL_PARAGRAPH]: CancelParagraph;
+ [OP.PARAGRAPH_EXECUTED_BY_SPELL]: {}; // TODO(hsuanxyz)
+ [OP.RUN_PARAGRAPH]: RunParagraph;
+ [OP.RUN_ALL_PARAGRAPHS]: RunAllParagraphs;
+ [OP.PARAGRAPH_REMOVE]: ParagraphRemove;
+ [OP.PARAGRAPH_CLEAR_OUTPUT]: ParagraphClearOutput;
+ [OP.PARAGRAPH_CLEAR_ALL_OUTPUT]: ParagraphClearAllOutput;
+ [OP.COMPLETION]: Completion;
+ [OP.COMMIT_PARAGRAPH]: CommitParagraph;
+ [OP.PATCH_PARAGRAPH]: PatchParagraphReceived;
+ [OP.IMPORT_NOTE]: {}; // TODO(hsuanxyz)
+ [OP.CHECKPOINT_NOTE]: CheckpointNote;
+ [OP.SET_NOTE_REVISION]: SetNoteRevision;
+ [OP.LIST_REVISION_HISTORY]: ListRevisionHistory;
+ [OP.NOTE_REVISION]: NoteRevision;
+ [OP.NOTE_REVISION_FOR_COMPARE]: NoteRevisionForCompare;
+ [OP.EDITOR_SETTING]: EditorSettingSend;
+ [OP.LIST_NOTE_JOBS]: undefined;
+ [OP.UNSUBSCRIBE_UPDATE_NOTE_JOBS]: undefined;
+ [OP.LIST_UPDATE_NOTE_JOBS]: undefined;
+ [OP.GET_INTERPRETER_BINDINGS]: GetInterpreterBindings;
+ [OP.GET_INTERPRETER_SETTINGS]: undefined;
+ [OP.SAVE_NOTE_FORMS]: SaveNoteFormsReceived;
+ [OP.REMOVE_NOTE_FORMS]: RemoveNoteForms;
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-interpreter.interface.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-interpreter.interface.ts
new file mode 100644
index 00000000000..c59e459410e
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-interpreter.interface.ts
@@ -0,0 +1,70 @@
+/*
+ * Licensed 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 interface InterpreterSetting {
+ interpreterSettings: InterpreterItem[];
+}
+
+export interface InterpreterItem {
+ id: string;
+ name: string;
+ group: string;
+ properties: Properties;
+ status: string;
+ interpreterGroup: InterpreterGroupItem[];
+ dependencies: string[];
+ option: Option;
+}
+
+export interface InterpreterBindings {
+ interpreterBindings: InterpreterBindingItem[];
+}
+
+export interface InterpreterBindingItem {
+ id: string;
+ name: string;
+ selected: boolean;
+ interpreters: InterpreterGroupItem[];
+}
+
+interface Properties {
+ [name: string]: {
+ name: string;
+ value: boolean;
+ type: string;
+ };
+}
+
+interface InterpreterGroupItem {
+ name: string;
+ class: string;
+ defaultInterpreter: boolean;
+ editor?: Editor;
+}
+
+interface Editor {
+ language?: string;
+ editOnDblClick?: boolean;
+ completionKey?: string;
+ completionSupport?: boolean;
+}
+
+interface Option {
+ remote: boolean;
+ port: number;
+ isExistingProcess: boolean;
+ setPermission: boolean;
+ owners: string[];
+ isUserImpersonate: boolean;
+ perNote?: string;
+ perUser?: string;
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-job.interface.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-job.interface.ts
new file mode 100644
index 00000000000..c59122b3a38
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-job.interface.ts
@@ -0,0 +1,48 @@
+/*
+ * Licensed 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 interface ListNoteJobs {
+ noteJobs: NoteJobs;
+}
+
+export interface ListUpdateNoteJobs {
+ noteRunningJobs: NoteJobs;
+}
+
+export interface NoteJobs {
+ lastResponseUnixTime: number;
+ jobs: JobsItem[];
+}
+export interface JobsItem {
+ noteId: string;
+ noteName: string;
+ noteType: string;
+ interpreter: string;
+ isRunningJob: boolean;
+ isRemoved: boolean;
+ unixTimeLastRun: number;
+ paragraphs: JobItemParagraphItem[];
+}
+export interface JobItemParagraphItem {
+ id: string;
+ name: string;
+ status: JobStatus;
+}
+
+export enum JobStatus {
+ READY = 'READY',
+ FINISHED = 'FINISHED',
+ ABORT = 'ABORT',
+ ERROR = 'ERROR',
+ PENDING = 'PENDING',
+ RUNNING = 'RUNNING'
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
new file mode 100644
index 00000000000..649a312561a
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
@@ -0,0 +1,210 @@
+/*
+ * Licensed 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 { ParagraphItem } from './message-paragraph.interface';
+
+interface ID {
+ id: string;
+}
+
+interface Name {
+ name: string;
+}
+
+export type GetNode = ID;
+export type MoveNoteToTrash = ID;
+export type MoveFolderToTrash = ID;
+export type RestoreNote = ID;
+export type RestoreFolder = ID;
+export type DeleteNote = ID;
+export type RemoveFolder = ID;
+export type CloneNote = ID & Name;
+export type FolderRename = ID & Name;
+export type PersonalizedMode = 'true' | 'false';
+
+export interface NoteRename extends Name, ID {
+ relative: boolean;
+}
+
+export interface SendNote {
+ id: string;
+ noteParams: NoteParams;
+}
+
+export interface NoteUpdated {
+ config: NoteConfig;
+ info: NoteInfo;
+ name: string;
+}
+
+export interface Note {
+ note?: {
+ paragraphs: ParagraphItem[];
+ name: string;
+ id: string;
+ defaultInterpreterGroup: string;
+ noteParams: NoteParams;
+ noteForms: NoteForms;
+ angularObjects: NoteAngularObjects;
+ config: NoteConfig;
+ info: NoteInfo;
+ };
+}
+
+export interface NoteAngularObjects {
+ // tslint:disable-next-line no-any
+ [key: string]: any;
+}
+
+export interface NoteInfo {
+ // tslint:disable-next-line no-any
+ [key: string]: any;
+}
+
+export interface NoteParams {
+ // tslint:disable-next-line no-any
+ [key: string]: any;
+}
+
+export interface NoteForms {
+ // tslint:disable-next-line no-any
+ [key: string]: any;
+}
+
+export interface RemoveNoteForms {
+ noteId: string;
+ formName: string;
+}
+
+export interface SaveNoteFormsReceived {
+ noteId: string;
+ noteParams: NoteParams;
+}
+
+export interface GetInterpreterBindings {
+ noteId: string;
+}
+
+export interface EditorSettingSend {
+ paragraphId: string;
+ magic: string;
+}
+
+export interface EditorSettingReceived {
+ paragraphId: string;
+ editor: {
+ completionSupport: boolean;
+ editOnDblClick: boolean;
+ language: string;
+ };
+}
+
+export interface NoteRevisionForCompare {
+ noteId: string;
+ revisionId: string;
+ position: string;
+}
+
+export interface CollaborativeModeStatus {
+ status: boolean;
+ users: string[];
+}
+
+export interface ParagraphMoved {
+ index: number;
+ id: string;
+}
+
+export interface UpdateParagraph {
+ paragraph: ParagraphItem;
+}
+
+export interface SaveNoteFormsSend {
+ formsData: {
+ forms: NoteForms;
+ params: NoteParams;
+ };
+}
+
+export interface NoteRunningStatus {
+ status: boolean;
+}
+
+export interface ParagraphAdded {
+ index: number;
+ paragraph: ParagraphItem;
+}
+
+export interface SetNoteRevisionStatus {
+ status: boolean;
+}
+
+export interface ListRevision {
+ revisionList: RevisionListItem[];
+}
+
+export interface RevisionListItem {
+ id: string;
+ message: string;
+ time?: number;
+}
+
+export interface NoteRevision {
+ note?: Note['note'];
+ noteId: string;
+ revisionId: string;
+}
+
+export interface ListRevisionHistory {
+ noteId: string;
+}
+
+export interface SetNoteRevision {
+ noteId: string;
+ revisionId: string;
+}
+
+export interface CheckpointNote {
+ noteId: string;
+ commitMessage: string;
+}
+
+export interface NoteUpdate extends Name, ID {
+ config: NoteConfig;
+}
+
+export interface NewNote extends Name {
+ defaultInterpreterGroup: string;
+}
+
+export interface NotesInfo {
+ notes: NotesInfoItem[];
+}
+
+export interface NotesInfoItem extends ID {
+ path: string;
+}
+
+export interface NoteConfig {
+ cron?: string;
+ releaseresource: boolean;
+ noteFormTitle?: string;
+ cronExecutingRoles?: string;
+ cronExecutingUser?: string;
+ isZeppelinNotebookCronEnable: boolean;
+ looknfeel: 'report' | 'default' | 'simple';
+ personalizedMode: PersonalizedMode;
+}
+
+export interface UpdatePersonalizedMode extends ID {
+ personalized: PersonalizedMode;
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-operator.interface.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-operator.interface.ts
new file mode 100644
index 00000000000..d3ce82b6029
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-operator.interface.ts
@@ -0,0 +1,482 @@
+/*
+ * Licensed 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.
+ */
+
+// tslint:disable:no-redundant-jsdoc
+/**
+ * Representation of event type.
+ */
+export enum OP {
+ /**
+ * [c-s]
+ * load note for home screen
+ */
+ GET_HOME_NOTE = 'GET_HOME_NOTE',
+
+ /**
+ * [c-s]
+ * client load note
+ * @param id note id
+ */
+ GET_NOTE = 'GET_NOTE',
+
+ /**
+ * [s-c]
+ * note info
+ * @param note serialized SendNote object
+ */
+ NOTE = 'NOTE',
+
+ /**
+ * [s-c]
+ * paragraph info
+ * @param paragraph serialized paragraph object
+ */
+ PARAGRAPH = 'PARAGRAPH',
+
+ /**
+ * [s-c]
+ * progress update
+ * @param id paragraph id
+ * @param progress percentage progress
+ */
+ PROGRESS = 'PROGRESS',
+
+ /**
+ * [c-s]
+ * create new notebook
+ */
+ NEW_NOTE = 'NEW_NOTE',
+
+ /**
+ * [c-s]
+ * delete notebook
+ * @param id note id
+ */
+ DEL_NOTE = 'DEL_NOTE',
+ REMOVE_FOLDER = 'REMOVE_FOLDER',
+ MOVE_NOTE_TO_TRASH = 'MOVE_NOTE_TO_TRASH',
+ MOVE_FOLDER_TO_TRASH = 'MOVE_FOLDER_TO_TRASH',
+ RESTORE_FOLDER = 'RESTORE_FOLDER',
+ RESTORE_NOTE = 'RESTORE_NOTE',
+ RESTORE_ALL = 'RESTORE_ALL',
+ EMPTY_TRASH = 'EMPTY_TRASH',
+
+ /**
+ * [c-s]
+ * clone new notebook
+ * @param id id of note to clone
+ * @param name name for the cloned note
+ */
+ CLONE_NOTE = 'CLONE_NOTE',
+
+ /**
+ * [c-s]
+ * import notebook
+ * @param object notebook
+ */
+ IMPORT_NOTE = 'IMPORT_NOTE',
+ NOTE_UPDATE = 'NOTE_UPDATE',
+ NOTE_RENAME = 'NOTE_RENAME',
+
+ /**
+ * [c-s]
+ * update personalized mode (boolean)
+ * @param note id and boolean personalized mode value
+ */
+ UPDATE_PERSONALIZED_MODE = 'UPDATE_PERSONALIZED_MODE',
+ FOLDER_RENAME = 'FOLDER_RENAME',
+
+ /**
+ * [c-s]
+ * run paragraph
+ * @param id paragraph id
+ * @param paragraph paragraph content.ie. script
+ * @param config paragraph config
+ * @param params paragraph params
+ */
+ RUN_PARAGRAPH = 'RUN_PARAGRAPH',
+
+ /**
+ * [c-s]
+ * commit paragraph
+ * @param id paragraph id
+ * @param title paragraph title
+ * @param paragraph paragraph content.ie. script
+ * @param config paragraph config
+ * @param params paragraph params
+ */
+ COMMIT_PARAGRAPH = 'COMMIT_PARAGRAPH',
+
+ /**
+ * [c-s]
+ * cancel paragraph run
+ * @param id paragraph id
+ */
+ CANCEL_PARAGRAPH = 'CANCEL_PARAGRAPH',
+
+ /**
+ * [c-s]
+ * move paragraph order
+ * @param id paragraph id
+ * @param index index the paragraph want to go
+ */
+ MOVE_PARAGRAPH = 'MOVE_PARAGRAPH',
+
+ /**
+ * [c-s]
+ * create new paragraph below current paragraph
+ * @param target index
+ */
+ INSERT_PARAGRAPH = 'INSERT_PARAGRAPH',
+
+ /**
+ * [c-s]
+ * create new para below current para as a copy of current para
+ * @param target index
+ * @param title paragraph title
+ * @param paragraph paragraph content.ie. script
+ * @param config paragraph config
+ * @param params paragraph params
+ */
+ COPY_PARAGRAPH = 'COPY_PARAGRAPH',
+
+ /**
+ * [c-s]
+ * ask paragraph editor setting
+ * @param magic magic keyword written in paragraph
+ * ex) spark.spark or spark
+ */
+ EDITOR_SETTING = 'EDITOR_SETTING',
+
+ /**
+ * [c-s]
+ * ask completion candidates
+ * @param id
+ * @param buf current code
+ * @param cursor cursor position in code
+ */
+ COMPLETION = 'COMPLETION',
+
+ /**
+ * [s-c]
+ * send back completion candidates list
+ * @param id
+ * @param completions list of string
+ */
+ COMPLETION_LIST = 'COMPLETION_LIST',
+
+ /**
+ * [c-s]
+ * ask list of note
+ */
+ LIST_NOTES = 'LIST_NOTES',
+
+ /**
+ * [c-s]
+ * reload notes from repo
+ */
+ RELOAD_NOTES_FROM_REPO = 'RELOAD_NOTES_FROM_REPO',
+
+ /**
+ * [s-c]
+ * list of note infos
+ * @param notes serialized List object
+ */
+ NOTES_INFO = 'NOTES_INFO',
+ PARAGRAPH_REMOVE = 'PARAGRAPH_REMOVE',
+
+ /**
+ * [c-s]
+ * clear output of paragraph
+ */
+ PARAGRAPH_CLEAR_OUTPUT = 'PARAGRAPH_CLEAR_OUTPUT',
+
+ /** [c-s]
+ * clear output of all paragraphs
+ */
+ PARAGRAPH_CLEAR_ALL_OUTPUT = 'PARAGRAPH_CLEAR_ALL_OUTPUT',
+
+ /**
+ * [s-c]
+ * ppend output
+ */
+ PARAGRAPH_APPEND_OUTPUT = 'PARAGRAPH_APPEND_OUTPUT',
+
+ /**
+ * [s-c]
+ * update (replace) output
+ */
+ PARAGRAPH_UPDATE_OUTPUT = 'PARAGRAPH_UPDATE_OUTPUT',
+ PING = 'PING',
+ AUTH_INFO = 'AUTH_INFO',
+
+ /**
+ * [s-c]
+ * add/update angular object
+ */
+ ANGULAR_OBJECT_UPDATE = 'ANGULAR_OBJECT_UPDATE',
+
+ /** [s-c]
+ * add angular object del
+ */
+ ANGULAR_OBJECT_REMOVE = 'ANGULAR_OBJECT_REMOVE',
+
+ /**
+ * [c-s]
+ * angular object value updated
+ */
+ ANGULAR_OBJECT_UPDATED = 'ANGULAR_OBJECT_UPDATED',
+
+ /**
+ * [c-s]
+ * angular object updated from AngularJS z object
+ */
+ ANGULAR_OBJECT_CLIENT_BIND = 'ANGULAR_OBJECT_CLIENT_BIND',
+
+ /**
+ * [c-s]
+ * angular object unbind from AngularJS z object
+ */
+ ANGULAR_OBJECT_CLIENT_UNBIND = 'ANGULAR_OBJECT_CLIENT_UNBIND',
+
+ /**
+ * [c-s]
+ * ask all key/value pairs of configurations
+ */
+ LIST_CONFIGURATIONS = 'LIST_CONFIGURATIONS',
+
+ /**
+ * [s-c]
+ * all key/value pairs of configurations
+ * @param settings serialized Map object
+ */
+ CONFIGURATIONS_INFO = 'CONFIGURATIONS_INFO',
+
+ /**
+ * [c-s]
+ * checkpoint note to storage repository
+ * @param noteId
+ * @param checkpointName
+ */
+ CHECKPOINT_NOTE = 'CHECKPOINT_NOTE',
+
+ /**
+ * [c-s]
+ * list revision history of the notebook
+ * @param noteId
+ */
+ LIST_REVISION_HISTORY = 'LIST_REVISION_HISTORY',
+
+ /**
+ * [c-s]
+ * get certain revision of note
+ * @param noteId
+ * @param revisionId
+ */
+ NOTE_REVISION = 'NOTE_REVISION',
+
+ /**
+ * [c-s]
+ * set current notebook head to this revision
+ * @param noteId
+ * @param revisionId
+ */
+ SET_NOTE_REVISION = 'SET_NOTE_REVISION',
+
+ /**
+ * [c-s]
+ * get certain revision of note for compare
+ * @param noteId
+ * @param revisionId
+ * @param position
+ */
+ NOTE_REVISION_FOR_COMPARE = 'NOTE_REVISION_FOR_COMPARE',
+
+ /**
+ * [s-c]
+ * append output
+ */
+ APP_APPEND_OUTPUT = 'APP_APPEND_OUTPUT',
+
+ /**
+ * [s-c]
+ * update (replace) output
+ */
+ APP_UPDATE_OUTPUT = 'APP_UPDATE_OUTPUT',
+
+ /**
+ * [s-c]
+ * on app load
+ */
+ APP_LOAD = 'APP_LOAD',
+
+ /**
+ * [s-c]
+ * on app status change
+ */
+ APP_STATUS_CHANGE = 'APP_STATUS_CHANGE',
+
+ /**
+ * [s-c]
+ * get note job management information
+ */
+ LIST_NOTE_JOBS = 'LIST_NOTE_JOBS',
+
+ /**
+ * [c-s]
+ * get job management information for until unixtime
+ */
+ LIST_UPDATE_NOTE_JOBS = 'LIST_UPDATE_NOTE_JOBS',
+
+ /**
+ * [c-s]
+ * unsubscribe job information for job management
+ * @param unixTime
+ */
+ UNSUBSCRIBE_UPDATE_NOTE_JOBS = 'UNSUBSCRIBE_UPDATE_NOTE_JOBS',
+
+ /**
+ * [c-s]
+ * get interpreter bindings
+ */
+ GET_INTERPRETER_BINDINGS = 'GET_INTERPRETER_BINDINGS',
+
+ /**
+ * [s-c]
+ * interpreter bindings
+ */
+ INTERPRETER_BINDINGS = 'INTERPRETER_BINDINGS',
+
+ /**
+ * [c-s]
+ * get interpreter settings
+ */
+ GET_INTERPRETER_SETTINGS = 'GET_INTERPRETER_SETTINGS',
+
+ /**
+ * [s-c]
+ * interpreter settings
+ */
+ INTERPRETER_SETTINGS = 'INTERPRETER_SETTINGS',
+
+ /**
+ * [s-c]
+ * error information to be sent
+ */
+ ERROR_INFO = 'ERROR_INFO',
+
+ /**
+ * [s-c]
+ * error information to be sent
+ */
+ SESSION_LOGOUT = 'SESSION_LOGOUT',
+
+ /**
+ * [s-c]
+ * Change websocket to watcher mode.
+ */
+ WATCHER = 'WATCHER',
+
+ /**
+ * [s-c]
+ * paragraph is added
+ */
+ PARAGRAPH_ADDED = 'PARAGRAPH_ADDED',
+
+ /**
+ * [s-c]
+ * paragraph deleted
+ */
+ PARAGRAPH_REMOVED = 'PARAGRAPH_REMOVED',
+
+ /**
+ * [s-c]
+ * paragraph moved
+ */
+ PARAGRAPH_MOVED = 'PARAGRAPH_MOVED',
+
+ /**
+ * [s-c]
+ * paragraph updated(name, config)
+ */
+ NOTE_UPDATED = 'NOTE_UPDATED',
+
+ /**
+ * [c-s]
+ * run all paragraphs
+ */
+ RUN_ALL_PARAGRAPHS = 'RUN_ALL_PARAGRAPHS',
+
+ /**
+ * [c-s]
+ * paragraph was executed by spell
+ */
+ PARAGRAPH_EXECUTED_BY_SPELL = 'PARAGRAPH_EXECUTED_BY_SPELL',
+
+ /**
+ * [s-c]
+ * run paragraph using spell
+ */
+ RUN_PARAGRAPH_USING_SPELL = 'RUN_PARAGRAPH_USING_SPELL',
+
+ /**
+ * [s-c]
+ * paragraph runtime infos
+ */
+ PARAS_INFO = 'PARAS_INFO',
+
+ /**
+ * save note forms
+ */
+ SAVE_NOTE_FORMS = 'SAVE_NOTE_FORMS',
+
+ /**
+ * remove note forms
+ */
+ REMOVE_NOTE_FORMS = 'REMOVE_NOTE_FORMS',
+
+ /**
+ * [s-c]
+ * start to download an interpreter
+ */
+ INTERPRETER_INSTALL_STARTED = 'INTERPRETER_INSTALL_STARTED',
+
+ /**
+ * [s-c]
+ * Status of an interpreter installation
+ */
+ INTERPRETER_INSTALL_RESULT = 'INTERPRETER_INSTALL_RESULT',
+
+ /**
+ * [s-c]
+ * collaborative mode status
+ */
+ COLLABORATIVE_MODE_STATUS = 'COLLABORATIVE_MODE_STATUS',
+
+ /**
+ * [c-s][s-c]
+ * patch editor text
+ */
+ PATCH_PARAGRAPH = 'PATCH_PARAGRAPH',
+
+ /**
+ * [s-c]
+ * sequential run status will be change
+ */
+ NOTE_RUNNING_STATUS = 'NOTE_RUNNING_STATUS',
+
+ /**
+ * [s-c]
+ * Notice
+ */
+ NOTICE = 'NOTICE'
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
new file mode 100644
index 00000000000..2e04b225adb
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
@@ -0,0 +1,468 @@
+/*
+ * Licensed 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 { EditorCompletionKey, EditorLanguage, EditorMode } from './message-common.interface';
+
+export enum DynamicFormsType {
+ TextBox = 'TextBox',
+ Password = 'Password',
+ Select = 'Select',
+ CheckBox = 'CheckBox'
+}
+
+export interface DynamicFormsItem {
+ defaultValue: string | string[];
+ hidden: boolean;
+ name: string;
+ displayName?: string;
+ type: DynamicFormsType;
+ argument?: string;
+ options?: Array<{ value: string; displayName?: string }>;
+}
+
+export interface DynamicForms {
+ [key: string]: DynamicFormsItem;
+}
+
+export interface DynamicFormParams {
+ [key: string]: string | string[];
+}
+
+export interface ParagraphEditorSetting {
+ language?: EditorLanguage;
+ editOnDblClick?: boolean;
+ isOutputHidden?: boolean;
+ completionKey?: EditorCompletionKey;
+ completionSupport?: boolean;
+ params?: DynamicFormParams;
+ forms?: DynamicForms;
+}
+
+// TODO(hsuanxyz)
+export interface ParagraphParams {
+ // tslint:disable-next-line no-any
+ [key: string]: any;
+}
+
+export interface ParagraphConfigResults {
+ [index: string]: ParagraphConfigResult;
+}
+
+export interface ParagraphConfigResult {
+ graph: GraphConfig;
+}
+
+export interface ParagraphConfig {
+ editorSetting?: ParagraphEditorSetting;
+ colWidth?: number;
+ editorMode?: EditorMode;
+ fontSize?: number;
+ results?: ParagraphConfigResults;
+ enabled?: boolean;
+ tableHide?: boolean;
+ lineNumbers?: boolean;
+ editorHide?: boolean;
+ title?: boolean;
+ runOnSelectionChange?: boolean;
+ isZeppelinNotebookCronEnable?: boolean;
+}
+
+export interface ParagraphResults {
+ code?: string;
+ msg?: ParagraphIResultsMsgItem[];
+
+ [index: number]: {};
+}
+
+export enum DatasetType {
+ NETWORK = 'NETWORK',
+ TABLE = 'TABLE',
+ HTML = 'HTML',
+ TEXT = 'TEXT',
+ ANGULAR = 'ANGULAR',
+ IMG = 'IMG'
+}
+
+export class ParagraphIResultsMsgItem {
+ type: DatasetType = DatasetType.TEXT;
+ data = '';
+}
+
+export interface ParasInfo {
+ id: string;
+ infos: RuntimeInfos;
+}
+
+export interface RuntimeInfos {
+ jobUrl: RuntimeInfosJobUrl;
+}
+
+interface RuntimeInfosJobUrl {
+ propertyName: string;
+ label: string;
+ tooltip: string;
+ group: string;
+ values: RuntimeInfosValuesItem[];
+ interpreterSettingId: string;
+}
+
+interface RuntimeInfosValuesItem {
+ jobUrl: string;
+}
+
+export interface ParagraphItem {
+ text: string;
+ user: string;
+ dateUpdated: string;
+ config: ParagraphConfig;
+ settings: ParagraphEditorSetting;
+ results?: ParagraphResults;
+ // tslint:disable-next-line no-any
+ apps: any[];
+ progressUpdateIntervalMs: number;
+ jobName: string;
+ id: string;
+ dateCreated: string;
+ dateStarted?: string;
+ dateFinished?: string;
+ errorMessage?: string;
+ runtimeInfos?: RuntimeInfos;
+ status: string;
+ title?: string;
+ focus?: boolean;
+ // tslint:disable-next-line no-any TODO(hsuanxyz)
+ aborted: any;
+ // tslint:disable-next-line no-any TODO(hsuanxyz)
+ lineNumbers: any;
+ // tslint:disable-next-line no-any TODO(hsuanxyz)
+ fontSize: any;
+}
+
+export interface SendParagraph {
+ id: string;
+ title?: string;
+ paragraph: string;
+ config: ParagraphConfig;
+ params: ParagraphParams;
+}
+
+export interface CopyParagraph {
+ index: number;
+ title?: string;
+ paragraph: string;
+ config: ParagraphConfig;
+ params: ParagraphParams;
+}
+
+export interface RunParagraph extends SendParagraph {
+ // tslint:disable-next-line no-any
+ [key: string]: any;
+}
+
+export interface CommitParagraph extends SendParagraph {
+ noteId: string;
+}
+
+export interface RunAllParagraphs {
+ noteId: string;
+ paragraphs: string;
+}
+
+export interface InsertParagraph {
+ index: number;
+}
+
+export interface MoveParagraph {
+ id: string;
+ index: number;
+}
+
+export interface AngularObjectUpdated {
+ noteId: string;
+ paragraphId: string;
+ name: string;
+ value: string;
+ interpreterGroupId: string;
+}
+
+export interface AngularObjectRemove {
+ noteId: string;
+ paragraphId: string;
+ name: string;
+}
+
+export interface AngularObjectUpdate {
+ noteId: string;
+ paragraphId: string;
+ interpreterGroupId: string;
+ angularObject: {
+ name: string;
+ // tslint:disable-next-line:no-any
+ object: any;
+ noteId: string;
+ paragraphId: string;
+ };
+}
+
+export interface AngularObjectClientBind {
+ noteId: string;
+ name: string;
+ value: string;
+ paragraphId: string;
+}
+
+export interface AngularObjectClientUnbind {
+ noteId: string;
+ name: string;
+ paragraphId: string;
+}
+
+export interface CancelParagraph {
+ id: string;
+}
+
+export interface ParagraphRemove {
+ id: string;
+}
+
+export interface ParagraphClearOutput {
+ id: string;
+}
+
+export interface ParagraphClearAllOutput {
+ id: string;
+}
+
+export interface Completion {
+ id: string;
+ buf: string;
+ cursor: number;
+}
+
+export interface CompletionItem {
+ meta: string;
+ value: string;
+ name: string;
+}
+
+export interface CompletionReceived {
+ completions: CompletionItem[];
+ id: string;
+}
+
+export interface PatchParagraphReceived {
+ id: string;
+ noteId: string;
+ patch: string;
+}
+
+export interface PatchParagraphSend {
+ paragraphId: string;
+ patch: string;
+}
+
+export interface ParagraphRemoved {
+ id: string;
+}
+
+export type VisualizationMode =
+ | 'table'
+ | 'lineChart'
+ | 'stackedAreaChart'
+ | 'multiBarChart'
+ | 'scatterChart'
+ | 'pieChart'
+ | string;
+
+export class GraphConfig {
+ mode: VisualizationMode = 'table';
+ height = 300;
+ optionOpen = false;
+ setting: GraphConfigSetting = {};
+ keys: GraphConfigKeysItem[] = [];
+ groups: GraphConfigGroupsItem[] = [];
+ values: GraphConfigValuesItem[] = [];
+ commonSetting: GraphConfigCommonSetting;
+}
+
+export interface Progress {
+ id: string;
+ progress: number;
+}
+
+interface GraphConfigSetting {
+ table?: VisualizationTable;
+ lineChart?: VisualizationLineChart;
+ stackedAreaChart?: VisualizationStackedAreaChart;
+ multiBarChart?: VisualizationMultiBarChart;
+ scatterChart?: VisualizationScatterChart;
+}
+
+interface VisualizationTable {
+ tableGridState: TableGridState;
+ tableColumnTypeState: TableColumnTypeState;
+ updated: boolean;
+ initialized: boolean;
+ tableOptionSpecHash: string;
+ tableOptionValue: TableOptionValue;
+}
+
+interface TableGridState {
+ columns: ColumnsItem[];
+ scrollFocus: ScrollFocus;
+ // tslint:disable-next-line
+ selection: any[];
+ grouping: Grouping;
+ treeView: TreeView;
+ pagination: Pagination;
+}
+
+interface ColumnsItem {
+ name: string;
+ visible: boolean;
+ width: string;
+ sort: Sort;
+ filters: FiltersItem[];
+ pinned: string;
+}
+
+interface Sort {
+ // tslint:disable-next-line
+ [key: string]: any;
+}
+
+interface FiltersItem {
+ // tslint:disable-next-line
+ [key: string]: any;
+}
+
+interface ScrollFocus {
+ // tslint:disable-next-line
+ [key: string]: any;
+}
+
+interface Grouping {
+ // tslint:disable-next-line
+ grouping: any[];
+ // tslint:disable-next-line
+ aggregations: any[];
+ rowExpandedStates: RowExpandedStates;
+}
+
+interface RowExpandedStates {
+ // tslint:disable-next-line
+ [key: string]: any;
+}
+
+interface TreeView {
+ // tslint:disable-next-line
+ [key: string]: any;
+}
+
+interface Pagination {
+ paginationCurrentPage: number;
+ paginationPageSize: number;
+}
+
+interface TableColumnTypeState {
+ updated: boolean;
+ names: Names;
+}
+
+interface Names {
+ index: string;
+ value: string;
+ random: string;
+ count: string;
+}
+
+interface TableOptionValue {
+ useFilter: boolean;
+ showPagination: boolean;
+ showAggregationFooter: boolean;
+}
+
+export type XLabelStatus = 'default' | 'rotate' | 'hide';
+
+export class XAxisSetting {
+ rotate = { degree: '-45' };
+ xLabelStatus: XLabelStatus = 'default';
+}
+
+export class VisualizationLineChart extends XAxisSetting {
+ forceY = false;
+ lineWithFocus = false;
+ isDateFormat = false;
+ dateFormat = '';
+}
+
+export class VisualizationStackedAreaChart extends XAxisSetting {
+ style: 'stream' | 'expand' | 'stack' = 'stack';
+}
+
+export class VisualizationMultiBarChart extends XAxisSetting {
+ stacked = false;
+}
+
+export class VisualizationScatterChart {
+ xAxis?: XAxis;
+ yAxis?: YAxis;
+ group?: Group;
+ size?: Size;
+}
+
+interface XAxis {
+ name: string;
+ index: number;
+ aggr: string;
+}
+
+interface YAxis {
+ name: string;
+ index: number;
+ aggr: string;
+}
+
+interface Group {
+ name: string;
+ index: number;
+ aggr: string;
+}
+
+interface Size {
+ name: string;
+ index: number;
+ aggr: string;
+}
+
+interface GraphConfigKeysItem {
+ name: string;
+ index: number;
+ aggr: string;
+}
+
+interface GraphConfigGroupsItem {
+ name: string;
+ index: number;
+ aggr: string;
+}
+
+interface GraphConfigValuesItem {
+ name: string;
+ index: number;
+ aggr: string;
+}
+
+interface GraphConfigCommonSetting {
+ // tslint:disable-next-line
+ [key: string]: any;
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/public-api.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/public-api.ts
new file mode 100644
index 00000000000..4160fa7f035
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/public-api.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed 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 * from './message-common.interface';
+export * from './message-data-type-map.interface';
+export * from './message-notebook.interface';
+export * from './message-operator.interface';
+export * from './message-paragraph.interface';
+export * from './websocket-message.interface';
+export * from './message-job.interface';
+export * from './message-interpreter.interface';
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/websocket-message.interface.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/websocket-message.interface.ts
new file mode 100644
index 00000000000..bdc71e1f428
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/websocket-message.interface.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed 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 { MixMessageDataTypeMap } from './message-data-type-map.interface';
+
+export interface WebSocketMessage {
+ op: K;
+ data?: MixMessageDataTypeMap[K];
+ ticket?: string; // default 'anonymous'
+ principal?: string; // default 'anonymous'
+ roles?: string; // default '[]'
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/message.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/message.ts
new file mode 100644
index 00000000000..4505e365726
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/message.ts
@@ -0,0 +1,506 @@
+/*
+ * Licensed 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 { interval, Observable, Subject, Subscription } from 'rxjs';
+import { delay, filter, map, mergeMap, retryWhen, take } from 'rxjs/operators';
+import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
+
+import { Ticket } from './interfaces/message-common.interface';
+import {
+ MessageReceiveDataTypeMap,
+ MessageSendDataTypeMap,
+ MixMessageDataTypeMap
+} from './interfaces/message-data-type-map.interface';
+import { NoteConfig, PersonalizedMode, SendNote } from './interfaces/message-notebook.interface';
+import { OP } from './interfaces/message-operator.interface';
+import { ParagraphConfig, ParagraphParams, SendParagraph } from './interfaces/message-paragraph.interface';
+import { WebSocketMessage } from './interfaces/websocket-message.interface';
+
+export type ArgumentsType = T extends (...args: infer U) => void ? U : never;
+
+export type SendArgumentsType = MessageSendDataTypeMap[K] extends undefined
+ ? ArgumentsType<(op: K) => void>
+ : ArgumentsType<(op: K, data: MessageSendDataTypeMap[K]) => void>;
+
+export type ReceiveArgumentsType<
+ K extends keyof MessageReceiveDataTypeMap
+> = MessageReceiveDataTypeMap[K] extends undefined ? () => void : (data?: MessageReceiveDataTypeMap[K]) => void;
+
+export class Message {
+ public connectedStatus = false;
+ public connectedStatus$ = new Subject();
+ private ws: WebSocketSubject>;
+ private open$ = new Subject();
+ private close$ = new Subject();
+ private sent$ = new Subject>();
+ private received$ = new Subject>();
+ private pingIntervalSubscription = new Subscription();
+ private wsUrl: string;
+ private ticket: Ticket;
+
+ constructor() {
+ this.open$.subscribe(() => {
+ this.connectedStatus = true;
+ this.connectedStatus$.next(this.connectedStatus);
+ this.pingIntervalSubscription.unsubscribe();
+ this.pingIntervalSubscription = interval(1000 * 10).subscribe(() => this.ping());
+ });
+ this.close$.subscribe(() => {
+ this.connectedStatus = false;
+ this.connectedStatus$.next(this.connectedStatus);
+ this.pingIntervalSubscription.unsubscribe();
+ });
+ }
+
+ bootstrap(ticket: Ticket, wsUrl: string) {
+ this.setTicket(ticket);
+ this.setWsUrl(wsUrl);
+ this.connect();
+ }
+
+ getWsInstance(): WebSocketSubject> {
+ return this.ws;
+ }
+
+ setWsUrl(wsUrl: string): void {
+ this.wsUrl = wsUrl;
+ }
+
+ setTicket(ticket: Ticket): void {
+ this.ticket = ticket;
+ }
+
+ interceptReceived(
+ data: WebSocketMessage
+ ): WebSocketMessage {
+ return data;
+ }
+
+ connect() {
+ this.ws = webSocket({
+ url: this.wsUrl,
+ openObserver: this.open$,
+ closeObserver: this.close$
+ });
+
+ this.ws
+ .pipe(
+ // reconnect
+ retryWhen(errors =>
+ errors.pipe(
+ mergeMap(() =>
+ this.close$.pipe(
+ take(1),
+ delay(4000)
+ )
+ )
+ )
+ )
+ )
+ .subscribe((e: WebSocketMessage) => {
+ console.log('Receive:', e);
+ this.received$.next(this.interceptReceived(e));
+ });
+ }
+
+ ping() {
+ this.send(OP.PING);
+ }
+
+ close() {
+ this.close$.next();
+ }
+
+ opened(): Observable {
+ return this.open$.asObservable();
+ }
+
+ closed(): Observable {
+ return this.close$.asObservable();
+ }
+
+ sent(): Observable> {
+ return this.sent$.asObservable();
+ }
+
+ received(): Observable> {
+ return this.received$.asObservable();
+ }
+
+ send(...args: SendArgumentsType): void {
+ const [op, data] = args;
+ const message: WebSocketMessage = {
+ op,
+ data: data as MixMessageDataTypeMap[K],
+ ...this.ticket
+ };
+ console.log('Send:', message);
+
+ this.ws.next(message);
+ this.sent$.next(message);
+ }
+
+ receive(op: K): Observable[K]> {
+ return this.received$.pipe(
+ filter(message => message.op === op),
+ map(message => message.data)
+ ) as Observable[K]>;
+ }
+
+ destroy(): void {
+ this.ws.complete();
+ this.ws = null;
+ }
+
+ getHomeNote(): void {
+ this.send(OP.GET_HOME_NOTE);
+ }
+
+ newNote(noteName: string, defaultInterpreterGroup: string): void {
+ this.send(OP.NEW_NOTE, {
+ name: noteName,
+ defaultInterpreterGroup
+ });
+ }
+
+ moveNoteToTrash(noteId: string): void {
+ this.send(OP.MOVE_NOTE_TO_TRASH, {
+ id: noteId
+ });
+ }
+
+ restoreNote(noteId: string): void {
+ this.send(OP.RESTORE_NOTE, {
+ id: noteId
+ });
+ }
+
+ deleteNote(noteId): void {
+ this.send(OP.DEL_NOTE, {
+ id: noteId
+ });
+ }
+
+ restoreFolder(folderPath: string): void {
+ this.send(OP.RESTORE_FOLDER, {
+ id: folderPath
+ });
+ }
+
+ removeFolder(folderPath: string): void {
+ this.send(OP.REMOVE_FOLDER, {
+ id: folderPath
+ });
+ }
+
+ moveFolderToTrash(folderPath: string): void {
+ this.send(OP.MOVE_FOLDER_TO_TRASH, {
+ id: folderPath
+ });
+ }
+
+ restoreAll(): void {
+ this.send(OP.RESTORE_ALL);
+ }
+
+ emptyTrash(): void {
+ this.send(OP.EMPTY_TRASH);
+ }
+
+ cloneNote(noteIdToClone, newNoteName): void {
+ this.send(OP.CLONE_NOTE, { id: noteIdToClone, name: newNoteName });
+ }
+
+ /**
+ * get nodes list
+ */
+ listNodes(): void {
+ this.send(OP.LIST_NOTES);
+ }
+
+ reloadAllNotesFromRepo(): void {
+ this.send(OP.RELOAD_NOTES_FROM_REPO);
+ }
+
+ getNote(noteId: string): void {
+ this.send(OP.GET_NOTE, { id: noteId });
+ }
+
+ updateNote(noteId: string, noteName: string, noteConfig: NoteConfig): void {
+ this.send(OP.NOTE_UPDATE, { id: noteId, name: noteName, config: noteConfig });
+ }
+
+ updatePersonalizedMode(noteId: string, modeValue: PersonalizedMode): void {
+ this.send(OP.UPDATE_PERSONALIZED_MODE, { id: noteId, personalized: modeValue });
+ }
+
+ noteRename(noteId: string, noteName: string, relative: boolean): void {
+ this.send(OP.NOTE_RENAME, { id: noteId, name: noteName, relative: relative });
+ }
+
+ folderRename(folderId: string, folderPath: string): void {
+ this.send(OP.FOLDER_RENAME, { id: folderId, name: folderPath });
+ }
+
+ moveParagraph(paragraphId: string, newIndex: number): void {
+ this.send(OP.MOVE_PARAGRAPH, { id: paragraphId, index: newIndex });
+ }
+
+ insertParagraph(newIndex: number): void {
+ this.send(OP.INSERT_PARAGRAPH, { index: newIndex });
+ }
+
+ copyParagraph(
+ newIndex: number,
+ paragraphTitle: string,
+ paragraphData: string,
+ paragraphConfig: ParagraphConfig,
+ paragraphParams: ParagraphParams
+ ): void {
+ this.send(OP.COPY_PARAGRAPH, {
+ index: newIndex,
+ title: paragraphTitle,
+ paragraph: paragraphData,
+ config: paragraphConfig,
+ params: paragraphParams
+ });
+ }
+
+ angularObjectUpdate(
+ noteId: string,
+ paragraphId: string,
+ name: string,
+ value: string,
+ interpreterGroupId: string
+ ): void {
+ this.send(OP.ANGULAR_OBJECT_UPDATED, {
+ noteId: noteId,
+ paragraphId: paragraphId,
+ name: name,
+ value: value,
+ interpreterGroupId: interpreterGroupId
+ });
+ }
+
+ // tslint:disable-next-line:no-any
+ angularObjectClientBind(noteId: string, name: string, value: any, paragraphId: string): void {
+ this.send(OP.ANGULAR_OBJECT_CLIENT_BIND, {
+ noteId: noteId,
+ name: name,
+ value: value,
+ paragraphId: paragraphId
+ });
+ }
+
+ angularObjectClientUnbind(noteId: string, name: string, paragraphId: string): void {
+ this.send(OP.ANGULAR_OBJECT_CLIENT_UNBIND, {
+ noteId: noteId,
+ name: name,
+ paragraphId: paragraphId
+ });
+ }
+
+ cancelParagraph(paragraphId): void {
+ this.send(OP.CANCEL_PARAGRAPH, { id: paragraphId });
+ }
+
+ paragraphExecutedBySpell(
+ paragraphId,
+ paragraphTitle,
+ paragraphText,
+ paragraphResultsMsg,
+ paragraphStatus,
+ paragraphErrorMessage,
+ paragraphConfig,
+ paragraphParams,
+ paragraphDateStarted,
+ paragraphDateFinished
+ ): void {
+ this.send(OP.PARAGRAPH_EXECUTED_BY_SPELL, {
+ id: paragraphId,
+ title: paragraphTitle,
+ paragraph: paragraphText,
+ results: {
+ code: paragraphStatus,
+ msg: paragraphResultsMsg.map(dataWithType => {
+ const serializedData = dataWithType.data;
+ return { type: dataWithType.type, serializedData };
+ })
+ },
+ status: paragraphStatus,
+ errorMessage: paragraphErrorMessage,
+ config: paragraphConfig,
+ params: paragraphParams,
+ dateStarted: paragraphDateStarted,
+ dateFinished: paragraphDateFinished
+ });
+ }
+
+ runParagraph(
+ paragraphId: string,
+ paragraphTitle: string,
+ paragraphData: string,
+ paragraphConfig: ParagraphConfig,
+ paragraphParams: ParagraphParams
+ ): void {
+ this.send(OP.RUN_PARAGRAPH, {
+ id: paragraphId,
+ title: paragraphTitle,
+ paragraph: paragraphData,
+ config: paragraphConfig,
+ params: paragraphParams
+ });
+ }
+
+ runAllParagraphs(noteId: string, paragraphs: SendParagraph[]): void {
+ this.send(OP.RUN_ALL_PARAGRAPHS, {
+ noteId: noteId,
+ paragraphs: JSON.stringify(paragraphs)
+ });
+ }
+
+ paragraphRemove(paragraphId: string): void {
+ this.send(OP.PARAGRAPH_REMOVE, { id: paragraphId });
+ }
+
+ paragraphClearOutput(paragraphId: string): void {
+ this.send(OP.PARAGRAPH_CLEAR_OUTPUT, { id: paragraphId });
+ }
+
+ paragraphClearAllOutput(noteId: string): void {
+ this.send(OP.PARAGRAPH_CLEAR_ALL_OUTPUT, { id: noteId });
+ }
+
+ completion(paragraphId: string, buf: string, cursor: number): void {
+ this.send(OP.COMPLETION, {
+ id: paragraphId,
+ buf: buf,
+ cursor: cursor
+ });
+ }
+
+ commitParagraph(
+ paragraphId: string,
+ paragraphTitle: string,
+ paragraphData: string,
+ paragraphConfig: ParagraphConfig,
+ paragraphParams: ParagraphConfig,
+ noteId: string
+ ): void {
+ return this.send(OP.COMMIT_PARAGRAPH, {
+ id: paragraphId,
+ noteId: noteId,
+ title: paragraphTitle,
+ paragraph: paragraphData,
+ config: paragraphConfig,
+ params: paragraphParams
+ });
+ }
+
+ patchParagraph(paragraphId: string, noteId: string, patch: string): void {
+ // javascript add "," if change contains several patches
+ // but java library requires patch list without ","
+ const normalPatch = patch.replace(/,@@/g, '@@');
+ return this.send(OP.PATCH_PARAGRAPH, {
+ id: paragraphId,
+ noteId: noteId,
+ patch: normalPatch
+ });
+ }
+
+ importNote(note: SendNote): void {
+ this.send(OP.IMPORT_NOTE, {
+ note: note
+ });
+ }
+
+ checkpointNote(noteId: string, commitMessage: string): void {
+ this.send(OP.CHECKPOINT_NOTE, {
+ noteId: noteId,
+ commitMessage: commitMessage
+ });
+ }
+
+ setNoteRevision(noteId: string, revisionId: string): void {
+ this.send(OP.SET_NOTE_REVISION, {
+ noteId: noteId,
+ revisionId: revisionId
+ });
+ }
+
+ listRevisionHistory(noteId: string): void {
+ this.send(OP.LIST_REVISION_HISTORY, {
+ noteId: noteId
+ });
+ }
+
+ noteRevision(noteId: string, revisionId: string): void {
+ this.send(OP.NOTE_REVISION, {
+ noteId: noteId,
+ revisionId: revisionId
+ });
+ }
+
+ noteRevisionForCompare(noteId: string, revisionId: string, position: string): void {
+ this.send(OP.NOTE_REVISION_FOR_COMPARE, {
+ noteId: noteId,
+ revisionId: revisionId,
+ position: position
+ });
+ }
+
+ editorSetting(paragraphId: string, replName: string): void {
+ this.send(OP.EDITOR_SETTING, {
+ paragraphId: paragraphId,
+ magic: replName
+ });
+ }
+
+ listNoteJobs(): void {
+ this.send(OP.LIST_NOTE_JOBS);
+ }
+
+ unsubscribeUpdateNoteJobs(): void {
+ this.send(OP.UNSUBSCRIBE_UPDATE_NOTE_JOBS);
+ }
+
+ getInterpreterBindings(noteId: string): void {
+ this.send(OP.GET_INTERPRETER_BINDINGS, { noteId: noteId });
+ }
+
+ saveInterpreterBindings(noteId, selectedSettingIds): void {
+ // this.send(OP.SAVE_INTERPRETER_BINDINGS,
+ // {noteId: noteId, selectedSettingIds: selectedSettingIds});
+ }
+
+ listConfigurations(): void {
+ this.send(OP.LIST_CONFIGURATIONS);
+ }
+
+ getInterpreterSettings(): void {
+ this.send(OP.GET_INTERPRETER_SETTINGS);
+ }
+
+ saveNoteForms(note: SendNote): void {
+ this.send(OP.SAVE_NOTE_FORMS, {
+ noteId: note.id,
+ noteParams: note.noteParams
+ });
+ }
+
+ removeNoteForms(note, formName): void {
+ this.send(OP.REMOVE_NOTE_FORMS, {
+ noteId: note.id,
+ formName: formName
+ });
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts b/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
new file mode 100644
index 00000000000..c5417873004
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
@@ -0,0 +1,15 @@
+/*
+ * Licensed 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 * from './message';
+// https://github.com/ng-packagr/ng-packagr/issues/1093
+export * from './interfaces/public-api';
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/tsconfig.lib.json b/zeppelin-web-angular/projects/zeppelin-sdk/tsconfig.lib.json
new file mode 100644
index 00000000000..784751866f8
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/tsconfig.lib.json
@@ -0,0 +1,27 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/lib",
+ "target": "es2015",
+ "declaration": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": [
+ "dom",
+ "es2018"
+ ]
+ },
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true,
+ "flatModuleId": "@zeppelin/sdk"
+ },
+ "exclude": [
+ "src/test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/tsconfig.spec.json b/zeppelin-web-angular/projects/zeppelin-sdk/tsconfig.spec.json
new file mode 100644
index 00000000000..16da33db072
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/tsconfig.spec.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "src/test.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/tslint.json b/zeppelin-web-angular/projects/zeppelin-sdk/tslint.json
new file mode 100644
index 00000000000..124133f8499
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "lib",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "lib",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/README.md b/zeppelin-web-angular/projects/zeppelin-visualization/README.md
new file mode 100644
index 00000000000..aa8e560c926
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/README.md
@@ -0,0 +1,36 @@
+
+
+# ZeppelinVisualization
+
+This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.8.
+
+## Code scaffolding
+
+Run `ng generate component component-name --project zeppelin-visualization` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project zeppelin-visualization`.
+> Note: Don't forget to add `--project zeppelin-visualization` or else it will be added to the default project in your `angular.json` file.
+
+## Build
+
+Run `ng build zeppelin-visualization` to build the project. The build artifacts will be stored in the `dist/` directory.
+
+## Publishing
+
+After building your library with `ng build zeppelin-visualization`, go to the dist folder `cd dist/zeppelin-visualization` and run `npm publish`.
+
+## Running unit tests
+
+Run `ng test zeppelin-visualization` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/karma.conf.js b/zeppelin-web-angular/projects/zeppelin-visualization/karma.conf.js
new file mode 100644
index 00000000000..e04c06a8f52
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/karma.conf.js
@@ -0,0 +1,44 @@
+/*
+ * Licensed 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.
+ */
+
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client: {
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, '../../coverage/zeppelin-visualization'),
+ reports: ['html', 'lcovonly', 'text-summary'],
+ fixWebpackSourcePaths: true
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['Chrome'],
+ singleRun: false,
+ restartOnFileChange: true
+ });
+};
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/ng-package.json b/zeppelin-web-angular/projects/zeppelin-visualization/ng-package.json
new file mode 100644
index 00000000000..78b3ecf185e
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/ng-package.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
+ "dest": "../../dist/zeppelin-visualization",
+ "lib": {
+ "entryFile": "src/public-api.ts"
+ }
+}
\ No newline at end of file
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/package.json b/zeppelin-web-angular/projects/zeppelin-visualization/package.json
new file mode 100644
index 00000000000..1d4232be543
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@zeppelin/visualization",
+ "version": "0.0.1",
+ "peerDependencies": {
+ "@angular/common": "^8.2.8",
+ "@angular/core": "^8.2.8"
+ }
+}
\ No newline at end of file
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/data-set.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/data-set.ts
new file mode 100644
index 00000000000..98d4632fcab
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/data-set.ts
@@ -0,0 +1,17 @@
+/*
+ * Licensed 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 { ParagraphIResultsMsgItem } from '@zeppelin/sdk';
+
+export abstract class DataSet {
+ abstract loadParagraphResult(paragraphResult: ParagraphIResultsMsgItem): void;
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/g2-visualization-base.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/g2-visualization-base.ts
new file mode 100644
index 00000000000..d663788be0e
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/g2-visualization-base.ts
@@ -0,0 +1,57 @@
+/*
+ * Licensed 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 { GraphConfig } from '@zeppelin/sdk';
+
+import { G2VisualizationComponentBase } from './g2-visualization-component-base';
+import { PivotTransformation } from './pivot-transformation';
+import { Transformation } from './transformation';
+import { Visualization } from './visualization';
+import { VisualizationComponentPortal } from './visualization-component-portal';
+
+export abstract class G2VisualizationBase extends Visualization {
+ pivot = new PivotTransformation(this.getConfig());
+ abstract componentPortal: VisualizationComponentPortal;
+
+ constructor(config: GraphConfig) {
+ super(config);
+ }
+
+ destroy(): void {
+ if (this.componentRef) {
+ this.componentRef.destroy();
+ this.componentRef = null;
+ }
+ this.configChange$.complete();
+ this.configChange$ = null;
+ }
+
+ getTransformation(): Transformation {
+ return this.pivot;
+ }
+
+ refresh(): void {
+ if (this.componentRef) {
+ this.componentRef.instance.refresh();
+ }
+ }
+
+ render(data): void {
+ this.transformed = data;
+ if (this.componentRef) {
+ this.componentRef.instance.refreshSetting();
+ this.componentRef.instance.render();
+ } else {
+ this.componentRef = this.componentPortal.attachComponentPortal();
+ }
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/g2-visualization-component-base.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/g2-visualization-component-base.ts
new file mode 100644
index 00000000000..8e52dcdc9de
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/g2-visualization-component-base.ts
@@ -0,0 +1,93 @@
+/*
+ * Licensed 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 { ElementRef, OnDestroy } from '@angular/core';
+
+import * as G2 from '@antv/g2';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { Visualization } from './visualization';
+
+export abstract class G2VisualizationComponentBase implements OnDestroy {
+ abstract container: ElementRef;
+ chart: G2.Chart;
+ config: GraphConfig;
+
+ constructor(public visualization: Visualization) {}
+
+ abstract renderBefore(chart: G2.Chart): void;
+
+ abstract refreshSetting(): void;
+ abstract setScale(): void;
+
+ render() {
+ this.config = this.visualization.getConfig();
+ this.refreshSetting();
+ this.initChart();
+ this.chart.source(this.visualization.transformed);
+ this.renderBefore(this.chart);
+ this.chart.render();
+ this.renderAfter();
+ }
+
+ renderAfter(): void {}
+
+ getKey(): string {
+ let key = '';
+ if (this.config.keys && this.config.keys[0]) {
+ key = this.config.keys[0].name;
+ }
+ return key;
+ }
+
+ refresh(): void {
+ this.config = this.visualization.getConfig();
+ this.chart.changeHeight(this.config.height || 400);
+ setTimeout(() => {
+ this.setScale();
+ this.chart.forceFit();
+ });
+ }
+
+ initChart() {
+ if (this.chart) {
+ this.chart.clear();
+ } else {
+ if (this.container && this.container.nativeElement) {
+ this.chart = new G2.Chart({
+ forceFit: true,
+ container: this.container.nativeElement,
+ height: this.config.height || 400,
+ padding: {
+ top: 80,
+ left: 50,
+ right: 50,
+ bottom: 50
+ }
+ });
+ this.chart.legend({
+ position: 'top-right'
+ // tslint:disable-next-line
+ } as any);
+ } else {
+ throw new Error(`Can't find the container, Please make sure on correct assignment.`);
+ }
+ }
+ }
+
+ ngOnDestroy(): void {
+ if (this.chart) {
+ this.chart.destroy();
+ this.chart = null;
+ }
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/index.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/pivot-transformation.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/pivot-transformation.ts
new file mode 100644
index 00000000000..963cc48f9fa
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/pivot-transformation.ts
@@ -0,0 +1,230 @@
+/*
+ * Licensed 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 { DataSet } from '@antv/data-set';
+import { get } from 'lodash';
+
+import { TableData } from './table-data';
+import { Transformation } from './transformation';
+
+export class PivotTransformation extends Transformation {
+ constructor(config) {
+ super(config);
+ }
+
+ removeUnknown(array: Array<{ name: string }>, tableData: TableData): void {
+ for (let i = 0; i < array.length; i++) {
+ // remove non existing column
+ let found = false;
+ for (let j = 0; j < tableData.columns.length; j++) {
+ const a = array[i];
+ const b = tableData.columns[j];
+ if (a.name === b) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ array.splice(i, 1);
+ i--;
+ }
+ }
+ }
+
+ setDefaultConfig(tableData: TableData) {
+ const config = this.getConfig();
+ config.keys = config.keys || [];
+ config.groups = config.groups || [];
+ config.values = config.values || [];
+ this.removeUnknown(config.keys, tableData);
+ this.removeUnknown(config.values, tableData);
+ this.removeUnknown(config.groups, tableData);
+ if (config.keys.length === 0 && config.groups.length === 0 && config.values.length === 0) {
+ if (config.keys.length === 0 && tableData.columns[0]) {
+ config.keys = [
+ {
+ name: tableData.columns[0],
+ index: 0,
+ aggr: 'sum'
+ }
+ ];
+ }
+
+ if (config.values.length === 0 && tableData.columns[1]) {
+ config.values = [
+ {
+ name: tableData.columns[1],
+ index: 1,
+ aggr: 'sum'
+ }
+ ];
+ }
+ }
+ }
+
+ // tslint:disable-next-line:no-any
+ transform(tableData: TableData): any {
+ const config = this.getConfig();
+ this.setDefaultConfig(tableData);
+ const ds = new DataSet();
+ let dv = ds.createView().source(tableData.rows);
+
+ let firstKey = '';
+ if (config.keys && config.keys[0]) {
+ firstKey = config.keys[0].name;
+ }
+ let keys = [];
+ let groups = [];
+ let values = [];
+ let aggregates = [];
+
+ // set values from config
+ if (config.mode !== 'scatterChart') {
+ keys = config.keys.map(e => e.name);
+ groups = config.groups.map(e => e.name);
+ values = config.values.map(v => `${v.name}(${v.aggr})`);
+ aggregates = config.values.map(v => (v.aggr === 'avg' ? 'mean' : v.aggr));
+ } else {
+ const xAxis = get(config.setting, 'scatterChart.xAxis.name', tableData.columns[0]);
+ const yAxis = get(config.setting, 'scatterChart.yAxis.name', tableData.columns[1]);
+ const group = get(config.setting, 'scatterChart.group.name');
+ keys = xAxis ? [xAxis] : [];
+ values = yAxis ? [yAxis] : [];
+ groups = group ? [group] : [];
+ }
+
+ // try coercion to number type
+ dv.transform({
+ type: 'map',
+ callback: row => {
+ Object.keys(row).forEach(k => {
+ if (config.keys.map(e => e.name).indexOf(k) === -1) {
+ const numberValue = Number.parseFloat(row[k]);
+ row[k] = Number.isFinite(numberValue) ? numberValue : row[k];
+ }
+ });
+ return row;
+ }
+ });
+
+ // not applicable with type scatter chart
+ if (config.mode !== 'scatterChart') {
+
+ // aggregate values
+ dv.transform({
+ type: 'aggregate',
+ fields: config.values.map(v => v.name),
+ operations: aggregates,
+ as: values,
+ groupBy: [...keys, ...groups]
+ });
+
+ // fill the rows to keep the charts is continuity
+ dv.transform({
+ type: 'fill-rows',
+ groupBy: [...keys, ...groups],
+ fillBy: 'group'
+ });
+
+ /**
+ * fill the field to keep the charts is continuity
+ *
+ * before
+ * ```
+ * [
+ * { x: 0, y: 1 },
+ * { x: 0, y: 2 },
+ * { x: 0, y: 3 },
+ * { x: 0 }
+ * ]
+ * ```
+ * after
+ * ```
+ * [
+ * { x: 0, y: 1 },
+ * { x: 0, y: 2 },
+ * { x: 0, y: 3 },
+ * { x: 0, y: 0 }
+ * // ^^^^^ filled this
+ * ]
+ * ```
+ */
+ config.values
+ .map(v => `${v.name}(${v.aggr})`)
+ .forEach(field => {
+ dv.transform({
+ field,
+ type: 'impute',
+ groupBy: keys,
+ method: 'value',
+ value: config.mode === 'stackedAreaChart' ? 0 : null
+ });
+ });
+ }
+
+ dv.transform({
+ type: 'fold',
+ fields: values,
+ key: '__key__',
+ value: '__value__'
+ });
+
+ dv.transform({
+ type: 'partition',
+ groupBy: groups
+ });
+
+ const groupsData = [];
+ Object.keys(dv.rows).forEach(groupKey => {
+ const groupName = groupKey.replace(/^_/, '');
+ dv.rows[groupKey].forEach(row => {
+ const getKey = () => {
+ if (config.mode !== 'pieChart') {
+ return groupName ? `${row.__key__}.${groupName}` : row.__key__
+ } else {
+ const keyName = keys.map(k => row[k]).join('.');
+ return groupName ? `${keyName}.${groupName}` : keyName;
+ }
+ };
+ groupsData.push({
+ ...row,
+ __key__: getKey()
+ });
+ });
+ });
+
+ groupsData.sort(
+ (a, b) =>
+ dv.origin.findIndex(o => o[firstKey] === a[firstKey]) - dv.origin.findIndex(o => o[firstKey] === b[firstKey])
+ );
+
+ console.log(groupsData);
+ dv = ds
+ .createView({
+ state: {
+ filterData: null
+ }
+ })
+ .source(groupsData);
+
+ if (config.mode === 'stackedAreaChart' || config.mode === 'pieChart') {
+ dv.transform({
+ type: 'percent',
+ field: '__value__',
+ dimension: '__key__',
+ groupBy: keys,
+ as: '__percent__'
+ });
+ }
+ return dv;
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/public-api.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/public-api.ts
new file mode 100644
index 00000000000..c28b0e2a649
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/public-api.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed 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.
+ */
+
+/*
+ * Public API Surface of zeppelin-visualization
+ */
+
+export * from './data-set';
+export * from './transformation';
+export * from './visualization-component-portal';
+export * from './visualization';
+export * from './table-data';
+export * from './table-transformation';
+export * from './pivot-transformation';
+export * from './g2-visualization-base';
+export * from './g2-visualization-component-base';
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/table-data.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/table-data.ts
new file mode 100644
index 00000000000..f0bd5ad4227
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/table-data.ts
@@ -0,0 +1,35 @@
+/*
+ * Licensed 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 { DataSet as AntvDataSet } from '@antv/data-set';
+
+import { DatasetType, ParagraphIResultsMsgItem } from '@zeppelin/sdk';
+import { DataSet } from './data-set';
+
+export class TableData extends DataSet {
+ columns: string[] = [];
+ // tslint:disable-next-line
+ rows: any[] = [];
+
+ loadParagraphResult({ data, type }: ParagraphIResultsMsgItem): void {
+ if (type !== DatasetType.TABLE) {
+ console.error('Can not load paragraph result');
+ return;
+ }
+ const ds = new AntvDataSet();
+ const dv = ds.createView().source(data, {
+ type: 'tsv'
+ });
+ this.columns = dv.origin && dv.origin.columns ? dv.origin.columns : [];
+ this.rows = dv.rows || [];
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/table-transformation.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/table-transformation.ts
new file mode 100644
index 00000000000..19111ecad88
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/table-transformation.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed 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 { TableData } from './table-data';
+import { Transformation } from './transformation';
+
+// tslint:disable-next-line:no-any
+export class TableTransformation extends Transformation {
+ constructor(config) {
+ super(config);
+ }
+
+ // tslint:disable-next-line:no-any
+ transform(tableData: TableData): any {
+ return tableData;
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/transformation.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/transformation.ts
new file mode 100644
index 00000000000..522ac021326
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/transformation.ts
@@ -0,0 +1,46 @@
+/*
+ * Licensed 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 { GraphConfig } from '@zeppelin/sdk';
+
+import { DataSet } from './data-set';
+
+export interface Setting {
+ // tslint:disable-next-line:no-any
+ template: any;
+ // tslint:disable-next-line:no-any
+ scope: any;
+}
+
+export abstract class Transformation {
+ dataset: DataSet;
+ constructor(private config: GraphConfig) {}
+
+ // tslint:disable-next-line:no-any
+ abstract transform(tableData): any;
+
+ setConfig(config) {
+ this.config = config;
+ }
+
+ setTableData(dataset: DataSet) {
+ this.dataset = dataset;
+ }
+
+ getTableData(): DataSet {
+ return this.dataset;
+ }
+
+ getConfig() {
+ return this.config;
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/visualization-component-portal.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/visualization-component-portal.ts
new file mode 100644
index 00000000000..a9418d98307
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/visualization-component-portal.ts
@@ -0,0 +1,46 @@
+/*
+ * Licensed 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 { CdkPortalOutlet, ComponentPortal, ComponentType, PortalInjector } from '@angular/cdk/portal';
+import { ComponentFactoryResolver, InjectionToken, ViewContainerRef } from '@angular/core';
+
+import { Visualization } from './visualization';
+
+export const VISUALIZATION = new InjectionToken('Visualization');
+
+export class VisualizationComponentPortal {
+ constructor(
+ private visualization: T,
+ private component: ComponentType,
+ private portalOutlet: CdkPortalOutlet,
+ private viewContainerRef: ViewContainerRef,
+ private componentFactoryResolver?: ComponentFactoryResolver
+ ) {}
+
+ createInjector() {
+ const userInjector = this.viewContainerRef && this.viewContainerRef.injector;
+ // tslint:disable-next-line
+ const injectionTokens = new WeakMap([[VISUALIZATION, this.visualization]]);
+ return new PortalInjector(userInjector, injectionTokens);
+ }
+
+ getComponentPortal() {
+ const injector = this.createInjector();
+ return new ComponentPortal(this.component, null, injector, this.componentFactoryResolver);
+ }
+
+ attachComponentPortal() {
+ const componentRef = this.portalOutlet.attachComponentPortal(this.getComponentPortal());
+ componentRef.changeDetectorRef.markForCheck();
+ return componentRef;
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/src/visualization.ts b/zeppelin-web-angular/projects/zeppelin-visualization/src/visualization.ts
new file mode 100644
index 00000000000..bb483e58766
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/src/visualization.ts
@@ -0,0 +1,44 @@
+/*
+ * Licensed 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 { ComponentRef } from '@angular/core';
+import { Subject } from 'rxjs';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { Transformation } from './transformation';
+
+// tslint:disable-next-line
+export abstract class Visualization {
+ // tslint:disable-next-line
+ transformed: any;
+ componentRef: ComponentRef;
+ configChange$ = new Subject();
+ constructor(private config: GraphConfig) {}
+
+ abstract getTransformation(): Transformation;
+ abstract render(tableData): void;
+ abstract refresh(): void;
+ abstract destroy(): void;
+
+ configChanged() {
+ return this.configChange$.asObservable();
+ }
+
+ setConfig(config: GraphConfig) {
+ this.config = config;
+ this.refresh();
+ }
+
+ getConfig() {
+ return this.config;
+ }
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/tsconfig.lib.json b/zeppelin-web-angular/projects/zeppelin-visualization/tsconfig.lib.json
new file mode 100644
index 00000000000..15689086f6e
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/tsconfig.lib.json
@@ -0,0 +1,27 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/lib",
+ "target": "es2015",
+ "declaration": true,
+ "inlineSources": true,
+ "types": [],
+ "lib": [
+ "dom",
+ "es2018"
+ ]
+ },
+ "angularCompilerOptions": {
+ "annotateForClosureCompiler": true,
+ "skipTemplateCodegen": true,
+ "strictMetadataEmit": true,
+ "fullTemplateTypeCheck": true,
+ "strictInjectionParameters": true,
+ "enableResourceInlining": true,
+ "flatModuleId": "@zeppelin/visualization"
+ },
+ "exclude": [
+ "src/test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/tsconfig.spec.json b/zeppelin-web-angular/projects/zeppelin-visualization/tsconfig.spec.json
new file mode 100644
index 00000000000..16da33db072
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/tsconfig.spec.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../out-tsc/spec",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "src/test.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
diff --git a/zeppelin-web-angular/projects/zeppelin-visualization/tslint.json b/zeppelin-web-angular/projects/zeppelin-visualization/tslint.json
new file mode 100644
index 00000000000..124133f8499
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-visualization/tslint.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../../tslint.json",
+ "rules": {
+ "directive-selector": [
+ true,
+ "attribute",
+ "lib",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "lib",
+ "kebab-case"
+ ]
+ }
+}
diff --git a/zeppelin-web-angular/proxy.conf.js b/zeppelin-web-angular/proxy.conf.js
new file mode 100644
index 00000000000..c3d571ce3e5
--- /dev/null
+++ b/zeppelin-web-angular/proxy.conf.js
@@ -0,0 +1,59 @@
+/*
+ * Licensed 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.
+ */
+
+const dotenv = require('dotenv');
+const HttpsProxyAgent = require('https-proxy-agent');
+dotenv.config();
+
+const proxyConfig = [
+ {
+ context: ['/'],
+ target: 'http://localhost:8080',
+ secure: false,
+ changeOrigin: true
+ },
+ {
+ context: '/ws',
+ target: 'ws://localhost:8080',
+ secure: false,
+ ws:true,
+ changeOrigin: true
+ }
+];
+
+function httpUrlToWSUrl(url) {
+ return url.replace(/(http)(s)?\:\/\//, "ws$2://");
+}
+
+function setupForCorporateProxy(proxyConfig) {
+ const proxyServer = process.env.SERVER_PROXY;
+ const httpProxy = process.env.HTTP_PROXY;
+ if (proxyServer) {
+ let agent = null;
+ if (httpProxy) {
+ agent = new HttpsProxyAgent(httpProxy);
+ }
+ proxyConfig.forEach(function(entry) {
+ if (entry.context === '/ws') {
+ entry.target = httpUrlToWSUrl(proxyServer)
+ } else {
+ entry.target = proxyServer;
+ }
+ if (agent) {
+ entry.agent = agent;
+ }
+ });
+ }
+ return proxyConfig;
+}
+
+module.exports = setupForCorporateProxy(proxyConfig);
diff --git a/zeppelin-web-angular/screenshot.png b/zeppelin-web-angular/screenshot.png
new file mode 100644
index 00000000000..4c157c5fdef
Binary files /dev/null and b/zeppelin-web-angular/screenshot.png differ
diff --git a/zeppelin-web-angular/src/.editorconfig b/zeppelin-web-angular/src/.editorconfig
new file mode 100644
index 00000000000..8b14efe23aa
--- /dev/null
+++ b/zeppelin-web-angular/src/.editorconfig
@@ -0,0 +1,12 @@
+# Editor configuration, see https://editorconfig.org
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/zeppelin-web-angular/src/.gitignore b/zeppelin-web-angular/src/.gitignore
new file mode 100644
index 00000000000..85158b7a442
--- /dev/null
+++ b/zeppelin-web-angular/src/.gitignore
@@ -0,0 +1,43 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node_modules
+
+# profiling files
+chrome-profiler-events.json
+speed-measure-plugin.json
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db
diff --git a/zeppelin-web-angular/src/app/app-http.interceptor.ts b/zeppelin-web-angular/src/app/app-http.interceptor.ts
new file mode 100644
index 00000000000..520eea89418
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app-http.interceptor.ts
@@ -0,0 +1,53 @@
+/*
+ * Licensed 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 { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { throwError, Observable } from 'rxjs';
+import { catchError, map } from 'rxjs/operators';
+
+import { isNil } from 'lodash';
+
+import { environment } from '@zeppelin/environment';
+import { TicketService } from '@zeppelin/services';
+
+@Injectable()
+export class AppHttpInterceptor implements HttpInterceptor {
+ constructor(private ticketService: TicketService) {}
+
+ // tslint:disable-next-line:no-any
+ intercept(httpRequest: HttpRequest, next: HttpHandler): Observable> {
+ let httpRequestUpdated = httpRequest.clone({ withCredentials: true });
+ if (environment.production) {
+ httpRequestUpdated = httpRequest.clone({ setHeaders: { 'X-Requested-With': 'XMLHttpRequest' } });
+ }
+ return next.handle(httpRequestUpdated).pipe(
+ map(event => {
+ if (event instanceof HttpResponse) {
+ return event.clone({ body: event.body.body });
+ } else {
+ return event;
+ }
+ }),
+ catchError(event => {
+ const redirect = event.headers.get('Location');
+ if (event.status === 401 && !isNil(redirect)) {
+ // Handle page redirect
+ window.location.href = redirect;
+ } else if (event.status === 405 && !event.url.contains('logout')) {
+ this.ticketService.logout().subscribe();
+ }
+ return throwError(event);
+ })
+ );
+ }
+}
diff --git a/zeppelin-web-angular/src/app/app-message.interceptor.ts b/zeppelin-web-angular/src/app/app-message.interceptor.ts
new file mode 100644
index 00000000000..02fdf962c6c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app-message.interceptor.ts
@@ -0,0 +1,69 @@
+/*
+ * Licensed 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 { Injectable } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { NzModalService } from 'ng-zorro-antd/modal';
+import { NzNotificationService } from 'ng-zorro-antd/notification';
+
+import { MessageInterceptor } from '@zeppelin/interfaces';
+import { MessageReceiveDataTypeMap, OP, WebSocketMessage } from '@zeppelin/sdk';
+import { TicketService } from '@zeppelin/services';
+
+@Injectable()
+export class AppMessageInterceptor implements MessageInterceptor {
+ constructor(
+ private router: Router,
+ private nzNotificationService: NzNotificationService,
+ private ticketService: TicketService,
+ private nzModalService: NzModalService
+ ) {}
+
+ received(data: WebSocketMessage): WebSocketMessage {
+ if (data.op === OP.NEW_NOTE) {
+ const rData = data.data as MessageReceiveDataTypeMap[OP.NEW_NOTE];
+ this.router.navigate(['/notebook', rData.note.id]).then();
+ } else if (data.op === OP.AUTH_INFO) {
+ const rData = data.data as MessageReceiveDataTypeMap[OP.AUTH_INFO];
+ if (this.ticketService.ticket.roles === '[]') {
+ this.nzModalService.confirm({
+ nzClosable: false,
+ nzMaskClosable: false,
+ nzTitle: 'Insufficient privileges',
+ nzContent: rData.info
+ });
+ } else {
+ this.nzModalService.create({
+ nzClosable: false,
+ nzMaskClosable: false,
+ nzTitle: 'Insufficient privileges',
+ nzContent: rData.info,
+ nzOkText: 'Login',
+ nzOnOk: () => {
+ this.router.navigate(['/login']).then();
+ },
+ nzOnCancel: () => {
+ this.router.navigate(['/']).then();
+ }
+ });
+ }
+ } else if (data.op === OP.ERROR_INFO) {
+ // tslint:disable-next-line:no-any
+ const rData = (data.data as any) as MessageReceiveDataTypeMap[OP.ERROR_INFO];
+ if (rData.info) {
+ this.nzNotificationService.warning('ERROR', rData.info);
+ }
+ }
+ return data;
+ }
+}
diff --git a/zeppelin-web-angular/src/app/app-routing.module.ts b/zeppelin-web-angular/src/app/app-routing.module.ts
new file mode 100644
index 00000000000..4a580cf9c8f
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app-routing.module.ts
@@ -0,0 +1,36 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
+
+const routes: Routes = [
+ {
+ path: '',
+ loadChildren: () => import('./pages/workspace/workspace.module').then(m => m.WorkspaceModule)
+ },
+ {
+ path: 'login',
+ loadChildren: () => import('./pages/login/login.module').then(m => m.LoginModule)
+ }
+];
+
+@NgModule({
+ imports: [
+ RouterModule.forRoot(routes, {
+ useHash: true,
+ preloadingStrategy: PreloadAllModules
+ })
+ ],
+ exports: [RouterModule]
+})
+export class AppRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/app-runtime-compiler.providers.ts b/zeppelin-web-angular/src/app/app-runtime-compiler.providers.ts
new file mode 100644
index 00000000000..bde0d988f2f
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app-runtime-compiler.providers.ts
@@ -0,0 +1,41 @@
+/*
+ * Licensed 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 {
+ Compiler,
+ CompilerFactory,
+ CompilerOptions,
+ COMPILER_OPTIONS,
+ StaticProvider,
+ ViewEncapsulation
+} from '@angular/core';
+import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
+
+const compilerOptions: CompilerOptions = {
+ useJit: true,
+ defaultEncapsulation: ViewEncapsulation.None
+};
+
+export function createCompiler(compilerFactory: CompilerFactory) {
+ return compilerFactory.createCompiler([compilerOptions]);
+}
+
+export const RUNTIME_COMPILER_PROVIDERS: StaticProvider[] = [
+ { provide: COMPILER_OPTIONS, useValue: compilerOptions, multi: true },
+ { provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
+ { provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
+];
+
+// TODO(hsuanxyz)
+// buildOptimizer false
+// import 'core-js/es7/reflect';
+// https://github.com/angular/angular/issues/27584#issuecomment-446462051
diff --git a/zeppelin-web-angular/src/app/app.component.html b/zeppelin-web-angular/src/app/app.component.html
new file mode 100644
index 00000000000..d101c77150d
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app.component.html
@@ -0,0 +1,15 @@
+
+
+
+Getting Ticket Data ...
+Logging out ...
diff --git a/zeppelin-web-angular/src/app/app.component.less b/zeppelin-web-angular/src/app/app.component.less
new file mode 100644
index 00000000000..f9587c7330a
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app.component.less
@@ -0,0 +1,26 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ .content {
+ background: @layout-body-background;
+ min-height: 100vh;
+ display: block;
+ position: relative;
+
+ &.blur {
+ filter: blur(6px);
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/app.component.spec.ts b/zeppelin-web-angular/src/app/app.component.spec.ts
new file mode 100644
index 00000000000..3dfbc76cc22
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app.component.spec.ts
@@ -0,0 +1,30 @@
+/*
+ * Licensed 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 { async, TestBed } from '@angular/core/testing';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppComponent } from './app.component';
+
+describe('AppComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [RouterTestingModule],
+ declarations: [AppComponent]
+ }).compileComponents();
+ }));
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app).toBeTruthy();
+ });
+});
diff --git a/zeppelin-web-angular/src/app/app.component.ts b/zeppelin-web-angular/src/app/app.component.ts
new file mode 100644
index 00000000000..dc9fcb3afbb
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app.component.ts
@@ -0,0 +1,39 @@
+/*
+ * Licensed 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 { Component } from '@angular/core';
+import { NavigationEnd, NavigationStart, Router } from '@angular/router';
+import { filter, map } from 'rxjs/operators';
+
+import { TicketService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.less']
+})
+export class AppComponent {
+ logout$ = this.ticketService.logout$;
+ loading$ = this.router.events.pipe(
+ filter(data => data instanceof NavigationEnd || data instanceof NavigationStart),
+ map(data => {
+ if (data instanceof NavigationStart) {
+ // load ticket when redirect to workspace
+ return data.url === '/';
+ } else if (data instanceof NavigationEnd) {
+ return false;
+ }
+ })
+ );
+
+ constructor(private router: Router, private ticketService: TicketService) {}
+}
diff --git a/zeppelin-web-angular/src/app/app.module.ts b/zeppelin-web-angular/src/app/app.module.ts
new file mode 100644
index 00000000000..5f651027048
--- /dev/null
+++ b/zeppelin-web-angular/src/app/app.module.ts
@@ -0,0 +1,90 @@
+/*
+ * Licensed 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 { registerLocaleData } from '@angular/common';
+import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
+import en from '@angular/common/locales/en';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { BrowserModule } from '@angular/platform-browser';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { Router, RouterModule } from '@angular/router';
+
+import { ZeppelinHeliumModule } from '@zeppelin/helium';
+import { en_US, NZ_I18N } from 'ng-zorro-antd/i18n';
+import { NzModalService } from 'ng-zorro-antd/modal';
+import { NzNotificationService } from 'ng-zorro-antd/notification';
+
+import { MESSAGE_INTERCEPTOR, TRASH_FOLDER_ID_TOKEN } from '@zeppelin/interfaces';
+import { loadMonacoBefore } from '@zeppelin/languages';
+import { TicketService } from '@zeppelin/services';
+import { ShareModule } from '@zeppelin/share';
+
+import { NZ_CODE_EDITOR_CONFIG } from '@zeppelin/share/code-editor';
+import { AppHttpInterceptor } from './app-http.interceptor';
+import { AppMessageInterceptor } from './app-message.interceptor';
+import { AppRoutingModule } from './app-routing.module';
+import { RUNTIME_COMPILER_PROVIDERS } from './app-runtime-compiler.providers';
+import { AppComponent } from './app.component';
+
+export const loadMonaco = () => {
+ loadMonacoBefore();
+};
+
+registerLocaleData(en);
+
+@NgModule({
+ declarations: [AppComponent],
+ imports: [
+ BrowserModule,
+ FormsModule,
+ HttpClientModule,
+ BrowserAnimationsModule,
+ ShareModule,
+ AppRoutingModule,
+ RouterModule,
+ ZeppelinHeliumModule
+ ],
+ providers: [
+ ...RUNTIME_COMPILER_PROVIDERS,
+ {
+ provide: NZ_I18N,
+ useValue: en_US
+ },
+ {
+ provide: HTTP_INTERCEPTORS,
+ useClass: AppHttpInterceptor,
+ multi: true,
+ deps: [TicketService]
+ },
+ {
+ provide: NZ_CODE_EDITOR_CONFIG,
+ useValue: {
+ defaultEditorOption: {
+ scrollBeyondLastLine: false
+ },
+ onLoad: loadMonaco
+ }
+ },
+ {
+ provide: MESSAGE_INTERCEPTOR,
+ useClass: AppMessageInterceptor,
+ deps: [Router, NzNotificationService, TicketService, NzModalService]
+ },
+ {
+ provide: TRASH_FOLDER_ID_TOKEN,
+ useValue: '~Trash'
+ }
+ ],
+ bootstrap: [AppComponent]
+})
+export class AppModule {}
diff --git a/zeppelin-web-angular/src/app/core/copy-text/copy-text-to-clipboard.ts b/zeppelin-web-angular/src/app/core/copy-text/copy-text-to-clipboard.ts
new file mode 100644
index 00000000000..fb1e6208e78
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/copy-text/copy-text-to-clipboard.ts
@@ -0,0 +1,65 @@
+/*
+ * Licensed 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 function copyTextToClipboard(text: string): void {
+ const textArea: HTMLTextAreaElement = document.createElement('textarea');
+
+ //
+ // *** This styling is an extra step which is likely not required. ***
+ //
+ // Why is it here? To ensure:
+ // 1. the element is able to have focus and selection.
+ // 2. if element was to flash render it has minimal visual impact.
+ // 3. less flakyness with selection and copying which **might** occur if
+ // the textarea element is not visible.
+ //
+ // The likelihood is the element won't even render, not even a flash,
+ // so some of these are just precautions. However in IE the element
+ // is visible whilst the popup box asking the user for permission for
+ // the web page to copy to the clipboard.
+ //
+
+ // Place in top-left corner of screen regardless of scroll position.
+ textArea.style.position = 'fixed';
+ textArea.style.top = '0';
+ textArea.style.left = '0';
+
+ // Ensure it has a small width and height. Setting to 1px / 1em
+ // doesn't work as this gives a negative w/h on some browsers.
+ textArea.style.width = '2em';
+ textArea.style.height = '2em';
+
+ // We don't need padding, reducing the size if it does flash render.
+ textArea.style.padding = '0';
+
+ // Clean up any borders.
+ textArea.style.border = 'none';
+ textArea.style.outline = 'none';
+ textArea.style.boxShadow = 'none';
+
+ // Avoid flash of white box if rendered for any reason.
+ textArea.style.background = 'transparent';
+
+ textArea.value = text;
+
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+
+ try {
+ document.execCommand('copy');
+ } catch (err) {
+ window.prompt('Copy to clipboard: Ctrl+C, Enter', text);
+ }
+
+ document.body.removeChild(textArea);
+}
diff --git a/zeppelin-web-angular/src/app/core/copy-text/index.ts b/zeppelin-web-angular/src/app/core/copy-text/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/copy-text/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/core/copy-text/public-api.ts b/zeppelin-web-angular/src/app/core/copy-text/public-api.ts
new file mode 100644
index 00000000000..aa47aad6726
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/copy-text/public-api.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './copy-text-to-clipboard';
diff --git a/zeppelin-web-angular/src/app/core/destroy-hook/destroy-hook.component.ts b/zeppelin-web-angular/src/app/core/destroy-hook/destroy-hook.component.ts
new file mode 100644
index 00000000000..7f394c0c4c9
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/destroy-hook/destroy-hook.component.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed 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 { OnDestroy } from '@angular/core';
+import { Subject } from 'rxjs';
+
+export class DestroyHookComponent implements OnDestroy {
+ readonly destroy$ = new Subject();
+
+ ngOnDestroy() {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/core/destroy-hook/index.ts b/zeppelin-web-angular/src/app/core/destroy-hook/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/destroy-hook/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/core/destroy-hook/public-api.ts b/zeppelin-web-angular/src/app/core/destroy-hook/public-api.ts
new file mode 100644
index 00000000000..c4b93aa16d8
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/destroy-hook/public-api.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './destroy-hook.component';
diff --git a/zeppelin-web-angular/src/app/core/index.ts b/zeppelin-web-angular/src/app/core/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/core/message-listener/index.ts b/zeppelin-web-angular/src/app/core/message-listener/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/message-listener/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/core/message-listener/message-listener.ts b/zeppelin-web-angular/src/app/core/message-listener/message-listener.ts
new file mode 100644
index 00000000000..5c29be8aaa0
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/message-listener/message-listener.ts
@@ -0,0 +1,59 @@
+/*
+ * Licensed 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 { OnDestroy } from '@angular/core';
+import { Subscriber } from 'rxjs';
+
+import { MessageReceiveDataTypeMap, ReceiveArgumentsType } from '@zeppelin/sdk';
+import { MessageService } from '@zeppelin/services';
+
+export class MessageListenersManager implements OnDestroy {
+ __zeppelinMessageListeners__: Array<() => void>;
+ __zeppelinMessageListeners$__ = new Subscriber();
+ constructor(public messageService: MessageService) {
+ if (this.__zeppelinMessageListeners__) {
+ this.__zeppelinMessageListeners__.forEach(fn => fn.apply(this));
+ }
+ }
+
+ ngOnDestroy(): void {
+ this.__zeppelinMessageListeners$__.unsubscribe();
+ this.__zeppelinMessageListeners$__ = null;
+ }
+}
+
+export function MessageListener(op: K) {
+ return function(
+ target: MessageListenersManager,
+ propertyKey: string,
+ descriptor: TypedPropertyDescriptor>
+ ) {
+ const oldValue = descriptor.value as ReceiveArgumentsType;
+
+ const fn = function() {
+ // tslint:disable:no-invalid-this
+ this.__zeppelinMessageListeners$__.add(
+ this.messageService.receive(op).subscribe(data => {
+ oldValue.apply(this, [data]);
+ })
+ );
+ };
+
+ if (!target.__zeppelinMessageListeners__) {
+ target.__zeppelinMessageListeners__ = [fn];
+ } else {
+ target.__zeppelinMessageListeners__.push(fn);
+ }
+
+ return descriptor;
+ };
+}
diff --git a/zeppelin-web-angular/src/app/core/message-listener/public-api.ts b/zeppelin-web-angular/src/app/core/message-listener/public-api.ts
new file mode 100644
index 00000000000..61a92db9183
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/message-listener/public-api.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './message-listener';
diff --git a/zeppelin-web-angular/src/app/core/paragraph-base/index.ts b/zeppelin-web-angular/src/app/core/paragraph-base/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/paragraph-base/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts b/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts
new file mode 100644
index 00000000000..f8f9a1be7b2
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts
@@ -0,0 +1,315 @@
+/*
+ * Licensed 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 { ChangeDetectorRef, QueryList } from '@angular/core';
+
+import {
+ AngularObjectRemove,
+ AngularObjectUpdate,
+ GraphConfig,
+ MessageReceiveDataTypeMap,
+ OP,
+ ParagraphConfig,
+ ParagraphEditorSetting,
+ ParagraphItem,
+ ParagraphIResultsMsgItem
+} from '@zeppelin/sdk';
+
+import { MessageService } from '@zeppelin/services/message.service';
+import { NgZService } from '@zeppelin/services/ng-z.service';
+import { NoteStatusService, ParagraphStatus } from '@zeppelin/services/note-status.service';
+
+import DiffMatchPatch from 'diff-match-patch';
+import { isEmpty, isEqual } from 'lodash';
+
+import { NotebookParagraphResultComponent } from '@zeppelin/pages/workspace/share/result/result.component';
+import { MessageListener, MessageListenersManager } from '../message-listener/message-listener';
+
+export abstract class ParagraphBase extends MessageListenersManager {
+ paragraph: ParagraphItem;
+ dirtyText: string;
+ originalText: string;
+ isEntireNoteRunning = false;
+ revisionView = false;
+ diffMatchPatch = new DiffMatchPatch();
+ isParagraphRunning = false;
+ results = [];
+ configs = {};
+ progress = 0;
+ colWidthOption = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
+ editorSetting: ParagraphEditorSetting = {};
+
+ notebookParagraphResultComponents: QueryList;
+
+ constructor(
+ public messageService: MessageService,
+ protected noteStatusService: NoteStatusService,
+ protected ngZService: NgZService,
+ protected cdr: ChangeDetectorRef
+ ) {
+ super(messageService);
+ }
+
+ abstract changeColWidth(needCommit: boolean, updateResult?: boolean): void;
+
+ @MessageListener(OP.PROGRESS)
+ onProgress(data: MessageReceiveDataTypeMap[OP.PROGRESS]) {
+ if (data.id === this.paragraph.id) {
+ this.progress = data.progress;
+ this.cdr.markForCheck();
+ }
+ }
+
+ @MessageListener(OP.NOTE_RUNNING_STATUS)
+ noteRunningStatusChange(data: MessageReceiveDataTypeMap[OP.NOTE_RUNNING_STATUS]) {
+ this.isEntireNoteRunning = data.status;
+ this.cdr.markForCheck();
+ }
+
+ @MessageListener(OP.PARAS_INFO)
+ updateParaInfos(data: MessageReceiveDataTypeMap[OP.PARAS_INFO]) {
+ if (this.paragraph.id === data.id) {
+ this.paragraph.runtimeInfos = data.infos;
+ this.cdr.markForCheck();
+ }
+ }
+
+ @MessageListener(OP.EDITOR_SETTING)
+ getEditorSetting(data: MessageReceiveDataTypeMap[OP.EDITOR_SETTING]) {
+ if (this.paragraph.id === data.paragraphId) {
+ this.paragraph.config.editorSetting = { ...this.paragraph.config.editorSetting, ...data.editor };
+ this.cdr.markForCheck();
+ }
+ }
+
+ @MessageListener(OP.PARAGRAPH)
+ paragraphData(data: MessageReceiveDataTypeMap[OP.PARAGRAPH]) {
+ const oldPara = this.paragraph;
+ const newPara = data.paragraph;
+ if (this.isUpdateRequired(oldPara, newPara)) {
+ this.updateParagraph(oldPara, newPara, () => {
+ if (newPara.results && newPara.results.msg) {
+ // tslint:disable-next-line:no-for-in-array
+ for (const i in newPara.results.msg) {
+ if (newPara.results.msg[i]) {
+ const newResult = newPara.results.msg ? newPara.results.msg[i] : new ParagraphIResultsMsgItem();
+ const oldResult =
+ oldPara.results && oldPara.results.msg ? oldPara.results.msg[i] : new ParagraphIResultsMsgItem();
+ const newConfig = newPara.config.results ? newPara.config.results[i] : { graph: new GraphConfig() };
+ const oldConfig = oldPara.config.results ? oldPara.config.results[i] : { graph: new GraphConfig() };
+ if (!isEqual(newResult, oldResult) || !isEqual(newConfig, oldConfig)) {
+ const resultComponent = this.notebookParagraphResultComponents.toArray()[i];
+ if (resultComponent) {
+ resultComponent.updateResult(newConfig, newResult);
+ }
+ }
+ }
+ }
+ }
+ this.cdr.markForCheck();
+ });
+ this.cdr.markForCheck();
+ }
+ }
+
+ @MessageListener(OP.PATCH_PARAGRAPH)
+ patchParagraph(data: MessageReceiveDataTypeMap[OP.PATCH_PARAGRAPH]) {
+ if (data.paragraphId === this.paragraph.id) {
+ let patch = data.patch;
+ patch = this.diffMatchPatch.patch_fromText(patch);
+ if (!this.paragraph.text) {
+ this.paragraph.text = '';
+ }
+ this.paragraph.text = this.diffMatchPatch.patch_apply(patch, this.paragraph.text)[0];
+ this.originalText = this.paragraph.text;
+ this.cdr.markForCheck();
+ }
+ }
+
+ @MessageListener(OP.ANGULAR_OBJECT_UPDATE)
+ angularObjectUpdate(data: AngularObjectUpdate) {
+ if (data.paragraphId === this.paragraph.id) {
+ const { name, object } = data.angularObject;
+ this.ngZService.setContextValue(name, object, data.paragraphId, false);
+ }
+ }
+
+ @MessageListener(OP.ANGULAR_OBJECT_REMOVE)
+ angularObjectRemove(data: AngularObjectRemove) {
+ if (data.paragraphId === this.paragraph.id) {
+ this.ngZService.unsetContextValue(data.name, data.paragraphId, false);
+ }
+ }
+
+ updateParagraph(oldPara: ParagraphItem, newPara: ParagraphItem, updateCallback: () => void) {
+ // 1. can't update on revision view
+ if (!this.revisionView) {
+ // 2. get status, refreshed
+ const statusChanged = newPara.status !== oldPara.status;
+ const resultRefreshed =
+ newPara.dateFinished !== oldPara.dateFinished ||
+ isEmpty(newPara.results) !== isEmpty(oldPara.results) ||
+ newPara.status === ParagraphStatus.ERROR ||
+ (newPara.status === ParagraphStatus.FINISHED && statusChanged);
+
+ // 3. update texts managed by paragraph
+ this.updateAllScopeTexts(oldPara, newPara);
+ // 4. execute callback to update result
+ updateCallback();
+
+ // 5. update remaining paragraph objects
+ this.updateParagraphObjectWhenUpdated(newPara);
+
+ // 6. handle scroll down by key properly if new paragraph is added
+ if (statusChanged || resultRefreshed) {
+ // when last paragraph runs, zeppelin automatically appends new paragraph.
+ // this broadcast will focus to the newly inserted paragraph
+ // TODO(hsuanxyz)
+ }
+ this.cdr.markForCheck();
+ }
+ }
+
+ isUpdateRequired(oldPara: ParagraphItem, newPara: ParagraphItem): boolean {
+ return (
+ newPara.id === oldPara.id &&
+ (newPara.dateCreated !== oldPara.dateCreated ||
+ newPara.text !== oldPara.text ||
+ newPara.dateFinished !== oldPara.dateFinished ||
+ newPara.dateStarted !== oldPara.dateStarted ||
+ newPara.dateUpdated !== oldPara.dateUpdated ||
+ newPara.status !== oldPara.status ||
+ newPara.jobName !== oldPara.jobName ||
+ newPara.title !== oldPara.title ||
+ isEmpty(newPara.results) !== isEmpty(oldPara.results) ||
+ newPara.errorMessage !== oldPara.errorMessage ||
+ !isEqual(newPara.settings, oldPara.settings) ||
+ !isEqual(newPara.config, oldPara.config) ||
+ !isEqual(newPara.runtimeInfos, oldPara.runtimeInfos))
+ );
+ }
+
+ updateAllScopeTexts(oldPara: ParagraphItem, newPara: ParagraphItem) {
+ if (oldPara.text !== newPara.text) {
+ if (this.dirtyText) {
+ // check if editor has local update
+ if (this.dirtyText === newPara.text) {
+ // when local update is the same from remote, clear local update
+ this.paragraph.text = newPara.text;
+ this.dirtyText = undefined;
+ this.originalText = newPara.text;
+ } else {
+ // if there're local update, keep it.
+ this.paragraph.text = newPara.text;
+ }
+ } else {
+ this.paragraph.text = newPara.text;
+ this.originalText = newPara.text;
+ }
+ }
+ this.cdr.markForCheck();
+ }
+
+ updateParagraphObjectWhenUpdated(newPara: ParagraphItem) {
+ if (this.paragraph.config.colWidth !== newPara.config.colWidth) {
+ this.changeColWidth(false);
+ }
+ this.paragraph.aborted = newPara.aborted;
+ this.paragraph.user = newPara.user;
+ this.paragraph.dateUpdated = newPara.dateUpdated;
+ this.paragraph.dateCreated = newPara.dateCreated;
+ this.paragraph.dateFinished = newPara.dateFinished;
+ this.paragraph.dateStarted = newPara.dateStarted;
+ this.paragraph.errorMessage = newPara.errorMessage;
+ this.paragraph.jobName = newPara.jobName;
+ this.paragraph.title = newPara.title;
+ this.paragraph.lineNumbers = newPara.lineNumbers;
+ this.paragraph.status = newPara.status;
+ this.paragraph.fontSize = newPara.fontSize;
+ if (newPara.status !== ParagraphStatus.RUNNING) {
+ this.paragraph.results = newPara.results;
+ }
+ this.paragraph.settings = newPara.settings;
+ this.paragraph.runtimeInfos = newPara.runtimeInfos;
+ this.isParagraphRunning = this.noteStatusService.isParagraphRunning(newPara);
+ this.paragraph.config = newPara.config;
+ this.initializeDefault(this.paragraph.config);
+ this.setResults();
+ this.cdr.markForCheck();
+ }
+
+ setResults() {
+ if (this.paragraph.results) {
+ this.results = this.paragraph.results.msg;
+ this.configs = this.paragraph.config.results;
+ }
+ if (!this.paragraph.config) {
+ this.paragraph.config = {};
+ }
+ }
+
+ initializeDefault(config: ParagraphConfig) {
+ const forms = this.paragraph.settings.forms;
+
+ if (!config.colWidth) {
+ config.colWidth = 12;
+ }
+
+ if (!config.fontSize) {
+ config.fontSize = 9;
+ }
+
+ if (config.enabled === undefined) {
+ config.enabled = true;
+ }
+
+ for (const idx in forms) {
+ if (forms[idx]) {
+ if (forms[idx].options) {
+ if (config.runOnSelectionChange === undefined) {
+ config.runOnSelectionChange = true;
+ }
+ }
+ }
+ }
+
+ if (!config.results) {
+ config.results = {};
+ }
+
+ if (!config.editorSetting) {
+ config.editorSetting = {};
+ } else if (config.editorSetting.editOnDblClick) {
+ this.editorSetting.isOutputHidden = config.editorSetting.editOnDblClick;
+ }
+ }
+
+ runParagraphUsingSpell(paragraphText: string, magic: string, propagated: boolean) {
+ // TODO(hsuanxyz)
+ }
+
+ runParagraphUsingBackendInterpreter(paragraphText: string) {
+ this.messageService.runParagraph(
+ this.paragraph.id,
+ this.paragraph.title,
+ paragraphText,
+ this.paragraph.config,
+ this.paragraph.settings.params
+ );
+ }
+
+ cancelParagraph() {
+ if (!this.isEntireNoteRunning) {
+ this.messageService.cancelParagraph(this.paragraph.id);
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/core/paragraph-base/public-api.ts b/zeppelin-web-angular/src/app/core/paragraph-base/public-api.ts
new file mode 100644
index 00000000000..a6ba53216ed
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/paragraph-base/public-api.ts
@@ -0,0 +1,14 @@
+/*
+ * Licensed 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 * from './paragraph-base';
+export * from './published';
diff --git a/zeppelin-web-angular/src/app/core/paragraph-base/published.ts b/zeppelin-web-angular/src/app/core/paragraph-base/published.ts
new file mode 100644
index 00000000000..0f415779679
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/paragraph-base/published.ts
@@ -0,0 +1,17 @@
+/*
+ * Licensed 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 publishedSymbol = Symbol('published');
+
+export interface Published {
+ readonly [publishedSymbol]: true;
+}
diff --git a/zeppelin-web-angular/src/app/core/public-api.ts b/zeppelin-web-angular/src/app/core/public-api.ts
new file mode 100644
index 00000000000..3f334c803b8
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/public-api.ts
@@ -0,0 +1,17 @@
+/*
+ * Licensed 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 * from './message-listener';
+export * from './destroy-hook';
+export * from './copy-text';
+export * from './paragraph-base';
+export * from './runtime-dynamic-module';
diff --git a/zeppelin-web-angular/src/app/core/runtime-dynamic-module/index.ts b/zeppelin-web-angular/src/app/core/runtime-dynamic-module/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/runtime-dynamic-module/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/core/runtime-dynamic-module/ng-zorro-antd-module.ts b/zeppelin-web-angular/src/app/core/runtime-dynamic-module/ng-zorro-antd-module.ts
new file mode 100644
index 00000000000..42c2d11e210
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/runtime-dynamic-module/ng-zorro-antd-module.ts
@@ -0,0 +1,148 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+
+import { NzAffixModule } from 'ng-zorro-antd/affix';
+import { NzAlertModule } from 'ng-zorro-antd/alert';
+import { NzAnchorModule } from 'ng-zorro-antd/anchor';
+import { NzAutocompleteModule } from 'ng-zorro-antd/auto-complete';
+import { NzAvatarModule } from 'ng-zorro-antd/avatar';
+import { NzBackTopModule } from 'ng-zorro-antd/back-top';
+import { NzBadgeModule } from 'ng-zorro-antd/badge';
+import { NzBreadCrumbModule } from 'ng-zorro-antd/breadcrumb';
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzCalendarModule } from 'ng-zorro-antd/calendar';
+import { NzCardModule } from 'ng-zorro-antd/card';
+import { NzCarouselModule } from 'ng-zorro-antd/carousel';
+import { NzCascaderModule } from 'ng-zorro-antd/cascader';
+import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
+import { NzCollapseModule } from 'ng-zorro-antd/collapse';
+import { NzCommentModule } from 'ng-zorro-antd/comment';
+import { NzNoAnimationModule, NzTransButtonModule, NzWaveModule } from 'ng-zorro-antd/core';
+import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
+import { NzDescriptionsModule } from 'ng-zorro-antd/descriptions';
+import { NzDividerModule } from 'ng-zorro-antd/divider';
+import { NzDrawerModule } from 'ng-zorro-antd/drawer';
+import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
+import { NzEmptyModule } from 'ng-zorro-antd/empty';
+import { NzFormModule } from 'ng-zorro-antd/form';
+import { NzGridModule } from 'ng-zorro-antd/grid';
+import { NzI18nModule } from 'ng-zorro-antd/i18n';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzInputModule } from 'ng-zorro-antd/input';
+import { NzInputNumberModule } from 'ng-zorro-antd/input-number';
+import { NzLayoutModule } from 'ng-zorro-antd/layout';
+import { NzListModule } from 'ng-zorro-antd/list';
+import { NzMentionModule } from 'ng-zorro-antd/mention';
+import { NzMenuModule } from 'ng-zorro-antd/menu';
+import { NzMessageModule } from 'ng-zorro-antd/message';
+import { NzModalModule } from 'ng-zorro-antd/modal';
+import { NzNotificationModule } from 'ng-zorro-antd/notification';
+import { NzPageHeaderModule } from 'ng-zorro-antd/page-header';
+import { NzPaginationModule } from 'ng-zorro-antd/pagination';
+import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
+import { NzPopoverModule } from 'ng-zorro-antd/popover';
+import { NzProgressModule } from 'ng-zorro-antd/progress';
+import { NzRadioModule } from 'ng-zorro-antd/radio';
+import { NzRateModule } from 'ng-zorro-antd/rate';
+import { NzResultModule } from 'ng-zorro-antd/result';
+import { NzSelectModule } from 'ng-zorro-antd/select';
+import { NzSkeletonModule } from 'ng-zorro-antd/skeleton';
+import { NzSliderModule } from 'ng-zorro-antd/slider';
+import { NzSpinModule } from 'ng-zorro-antd/spin';
+import { NzStatisticModule } from 'ng-zorro-antd/statistic';
+import { NzStepsModule } from 'ng-zorro-antd/steps';
+import { NzSwitchModule } from 'ng-zorro-antd/switch';
+import { NzTableModule } from 'ng-zorro-antd/table';
+import { NzTabsModule } from 'ng-zorro-antd/tabs';
+import { NzTagModule } from 'ng-zorro-antd/tag';
+import { NzTimePickerModule } from 'ng-zorro-antd/time-picker';
+import { NzTimelineModule } from 'ng-zorro-antd/timeline';
+import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
+import { NzTransferModule } from 'ng-zorro-antd/transfer';
+import { NzTreeModule } from 'ng-zorro-antd/tree';
+import { NzTreeSelectModule } from 'ng-zorro-antd/tree-select';
+import { NzTypographyModule } from 'ng-zorro-antd/typography';
+import { NzUploadModule } from 'ng-zorro-antd/upload';
+
+@NgModule({
+ exports: [
+ NzAffixModule,
+ NzAlertModule,
+ NzAnchorModule,
+ NzAutocompleteModule,
+ NzAvatarModule,
+ NzBackTopModule,
+ NzBadgeModule,
+ NzButtonModule,
+ NzBreadCrumbModule,
+ NzCalendarModule,
+ NzCardModule,
+ NzCarouselModule,
+ NzCascaderModule,
+ NzCheckboxModule,
+ NzCollapseModule,
+ NzCommentModule,
+ NzDatePickerModule,
+ NzDescriptionsModule,
+ NzDividerModule,
+ NzDrawerModule,
+ NzDropDownModule,
+ NzEmptyModule,
+ NzFormModule,
+ NzGridModule,
+ NzI18nModule,
+ NzIconModule,
+ NzInputModule,
+ NzInputNumberModule,
+ NzLayoutModule,
+ NzListModule,
+ NzMentionModule,
+ NzMenuModule,
+ NzMessageModule,
+ NzModalModule,
+ NzNoAnimationModule,
+ NzNotificationModule,
+ NzPageHeaderModule,
+ NzPaginationModule,
+ NzPopconfirmModule,
+ NzPopoverModule,
+ NzProgressModule,
+ NzRadioModule,
+ NzRateModule,
+ NzResultModule,
+ NzSelectModule,
+ NzSkeletonModule,
+ NzSliderModule,
+ NzSpinModule,
+ NzStatisticModule,
+ NzStepsModule,
+ NzSwitchModule,
+ NzTableModule,
+ NzTabsModule,
+ NzTagModule,
+ NzTimePickerModule,
+ NzTimelineModule,
+ NzToolTipModule,
+ NzTransButtonModule,
+ NzTransferModule,
+ NzTreeModule,
+ NzTreeSelectModule,
+ NzTypographyModule,
+ NzUploadModule,
+ NzWaveModule
+ ]
+})
+export class NzModule {
+ constructor() {}
+}
diff --git a/zeppelin-web-angular/src/app/core/runtime-dynamic-module/public-api.ts b/zeppelin-web-angular/src/app/core/runtime-dynamic-module/public-api.ts
new file mode 100644
index 00000000000..a8a0702b5a0
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/runtime-dynamic-module/public-api.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './runtime-dynamic-module.module';
diff --git a/zeppelin-web-angular/src/app/core/runtime-dynamic-module/runtime-dynamic-module.module.ts b/zeppelin-web-angular/src/app/core/runtime-dynamic-module/runtime-dynamic-module.module.ts
new file mode 100644
index 00000000000..27d08f94a81
--- /dev/null
+++ b/zeppelin-web-angular/src/app/core/runtime-dynamic-module/runtime-dynamic-module.module.ts
@@ -0,0 +1,21 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { NzModule } from './ng-zorro-antd-module';
+
+@NgModule({
+ declarations: [],
+ exports: [CommonModule, FormsModule, NzModule]
+})
+export class RuntimeDynamicModuleModule {}
diff --git a/zeppelin-web-angular/src/app/helium-manager/helium-manager.module.ts b/zeppelin-web-angular/src/app/helium-manager/helium-manager.module.ts
new file mode 100644
index 00000000000..940a29249fc
--- /dev/null
+++ b/zeppelin-web-angular/src/app/helium-manager/helium-manager.module.ts
@@ -0,0 +1,27 @@
+/*
+ * Licensed 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 { Compiler, CompilerFactory, COMPILER_OPTIONS, NgModule } from '@angular/core';
+import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
+
+export function createCompiler(compilerFactory: CompilerFactory) {
+ return compilerFactory.createCompiler();
+}
+
+@NgModule({
+ providers: [
+ { provide: COMPILER_OPTIONS, useValue: {}, multi: true },
+ { provide: CompilerFactory, useClass: JitCompilerFactory, deps: [COMPILER_OPTIONS] },
+ { provide: Compiler, useFactory: createCompiler, deps: [CompilerFactory] }
+ ]
+})
+export class HeliumManagerModule {}
diff --git a/zeppelin-web-angular/src/app/helium-manager/helium-manager.service.ts b/zeppelin-web-angular/src/app/helium-manager/helium-manager.service.ts
new file mode 100644
index 00000000000..1a1a03c55c3
--- /dev/null
+++ b/zeppelin-web-angular/src/app/helium-manager/helium-manager.service.ts
@@ -0,0 +1,75 @@
+/*
+ * Licensed 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 { Compiler, Injectable, Injector, NgModuleFactory, OnDestroy, Type } from '@angular/core';
+import { ZeppelinHeliumPackage, ZeppelinHeliumService } from '@zeppelin/helium';
+import { of, BehaviorSubject } from 'rxjs';
+import { HeliumManagerModule } from './helium-manager.module';
+
+export interface CompiledPackage {
+ // tslint:disable-next-line:no-any
+ moduleFactory: NgModuleFactory;
+ // tslint:disable-next-line:no-any
+ component: Type;
+ injector?: Injector;
+ name: string;
+ _raw: ZeppelinHeliumPackage;
+}
+
+@Injectable({
+ providedIn: HeliumManagerModule
+})
+export class HeliumManagerService implements OnDestroy {
+ private packages$ = new BehaviorSubject([]);
+
+ constructor(private zeppelinHeliumService: ZeppelinHeliumService, private compiler: Compiler) {}
+
+ initPackages() {
+ this.getEnabledPackages().subscribe(packages => {
+ packages.forEach(name => {
+ this.zeppelinHeliumService.loadPackage(name).then(heliumPackage => {
+ const loaded = this.packages$.value;
+ if (!loaded.find(p => p.name === heliumPackage.name)) {
+ this.compilePackage(heliumPackage);
+ }
+ });
+ });
+ });
+ }
+
+ getEnabledPackages() {
+ // return of(['helium-vis-example']);
+ return of([]);
+ }
+
+ packagesLoadChange() {
+ return this.packages$.asObservable();
+ }
+
+ compilePackage(pack: ZeppelinHeliumPackage) {
+ this.compiler.compileModuleAsync(pack.module).then(moduleFactory => {
+ this.packages$.next([
+ ...this.packages$.value,
+ {
+ moduleFactory,
+ name: pack.name,
+ component: pack.component,
+ _raw: pack
+ }
+ ]);
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.packages$.complete();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/helium-manager/index.ts b/zeppelin-web-angular/src/app/helium-manager/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/helium-manager/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/helium-manager/public-api.ts b/zeppelin-web-angular/src/app/helium-manager/public-api.ts
new file mode 100644
index 00000000000..8ba1a21026a
--- /dev/null
+++ b/zeppelin-web-angular/src/app/helium-manager/public-api.ts
@@ -0,0 +1,14 @@
+/*
+ * Licensed 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 * from './helium-manager.service';
+export * from './helium-manager.module';
diff --git a/zeppelin-web-angular/src/app/interfaces/credential.ts b/zeppelin-web-angular/src/app/interfaces/credential.ts
new file mode 100644
index 00000000000..533b1206819
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/credential.ts
@@ -0,0 +1,28 @@
+/*
+ * Licensed 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 interface Credential {
+ userCredentials: {
+ [key: string]: CredentialItem;
+ };
+}
+
+export interface CredentialItem {
+ username: string;
+ password: string;
+}
+
+export interface CredentialForm {
+ entity: string;
+ password: string;
+ username: string;
+}
diff --git a/zeppelin-web-angular/src/app/interfaces/index.ts b/zeppelin-web-angular/src/app/interfaces/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/interfaces/interpreter.ts b/zeppelin-web-angular/src/app/interfaces/interpreter.ts
new file mode 100644
index 00000000000..2e1d661d678
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/interpreter.ts
@@ -0,0 +1,104 @@
+/*
+ * Licensed 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 type InterpreterPropertyTypes = 'textarea' | 'string' | 'number' | 'url' | 'password' | 'checkbox';
+
+export interface Interpreter {
+ id: string;
+ name: string;
+ group: string;
+ properties: Properties;
+ status: string;
+ errorReason?: string;
+ interpreterGroup: InterpreterGroupItem[];
+ dependencies: DependenciesItem[];
+ option: Option;
+}
+
+export interface InterpreterMap {
+ [key: string]: Interpreter;
+}
+
+export interface CreateInterpreterRepositoryForm {
+ id: string;
+ url: string;
+ snapshot: boolean;
+ username: string;
+ password: string;
+ proxyProtocol: string;
+ proxyHost: string;
+ proxyPort: string;
+ proxyLogin: string;
+ proxyPassword: string;
+}
+
+export interface InterpreterRepository {
+ id: string;
+ type: string;
+ url: string;
+ releasePolicy: ReleasePolicy;
+ snapshotPolicy: SnapshotPolicy;
+ // tslint:disable-next-line
+ mirroredRepositories: any[];
+ repositoryManager: boolean;
+}
+interface ReleasePolicy {
+ enabled: boolean;
+ updatePolicy: string;
+ checksumPolicy: string;
+}
+interface SnapshotPolicy {
+ enabled: boolean;
+ updatePolicy: string;
+ checksumPolicy: string;
+}
+
+interface Properties {
+ [key: string]: {
+ name: string;
+ value: boolean;
+ type: string;
+ defaultValue?: string;
+ description?: string;
+ };
+}
+
+interface InterpreterGroupItem {
+ name: string;
+ class: string;
+ defaultInterpreter: boolean;
+ editor: Editor;
+}
+interface Editor {
+ language: string;
+ editOnDblClick: boolean;
+ completionKey?: string;
+ completionSupport?: boolean;
+}
+
+interface DependenciesItem {
+ groupArtifactVersion: string;
+ local: boolean;
+ exclusions: string[];
+}
+
+interface Option {
+ remote: boolean;
+ port: number;
+ isExistingProcess: boolean;
+ setPermission: boolean;
+ // tslint:disable-next-line:no-any
+ owners: any[];
+ isUserImpersonate: boolean;
+ perNote?: string;
+ perUser?: string;
+}
diff --git a/zeppelin-web-angular/src/app/interfaces/message-interceptor.ts b/zeppelin-web-angular/src/app/interfaces/message-interceptor.ts
new file mode 100644
index 00000000000..85618dfa44a
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/message-interceptor.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed 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 { InjectionToken } from '@angular/core';
+import { MessageReceiveDataTypeMap, WebSocketMessage } from '@zeppelin/sdk';
+
+export interface MessageInterceptor {
+ received(data: WebSocketMessage): WebSocketMessage;
+}
+
+export const MESSAGE_INTERCEPTOR = new InjectionToken('MESSAGE_INTERCEPTOR');
diff --git a/zeppelin-web-angular/src/app/interfaces/node-list.ts b/zeppelin-web-angular/src/app/interfaces/node-list.ts
new file mode 100644
index 00000000000..55331a9e740
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/node-list.ts
@@ -0,0 +1,42 @@
+/*
+ * Licensed 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 interface NodeList {
+ root: RootNode;
+ flatList: FlatListNodeItem[];
+ flatFolderMap: FlatFolderNodeMap;
+}
+
+export interface RootNode {
+ children: NodeItem[];
+}
+
+export interface NodeItem {
+ id: string;
+ title: string;
+ isLeaf?: boolean;
+ expanded?: boolean;
+ children?: NodeItem[];
+ isTrash: boolean;
+ nodeType?: string;
+ path?: string;
+}
+
+interface FlatListNodeItem {
+ id: string;
+ path: string;
+ isTrash: boolean;
+}
+
+interface FlatFolderNodeMap {
+ [title: string]: NodeItem;
+}
diff --git a/zeppelin-web-angular/src/app/interfaces/notebook-repo.ts b/zeppelin-web-angular/src/app/interfaces/notebook-repo.ts
new file mode 100644
index 00000000000..de037783ca7
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/notebook-repo.ts
@@ -0,0 +1,32 @@
+/*
+ * Licensed 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 interface NotebookRepo {
+ name: string;
+ className: string;
+ settings: NotebookRepoSettingsItem[];
+}
+
+export interface NotebookRepoPutData {
+ name: string;
+ settings: {
+ [key: string]: string;
+ };
+}
+
+export interface NotebookRepoSettingsItem {
+ type: string;
+ // tslint:disable-next-line:no-any
+ value: any[];
+ selected: string;
+ name: string;
+}
diff --git a/zeppelin-web-angular/src/app/interfaces/notebook-search.ts b/zeppelin-web-angular/src/app/interfaces/notebook-search.ts
new file mode 100644
index 00000000000..267ee943a59
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/notebook-search.ts
@@ -0,0 +1,19 @@
+/*
+ * Licensed 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 interface NotebookSearchResultItem {
+ id: string;
+ name: string;
+ snippet: string;
+ text: string;
+ header: string;
+}
diff --git a/zeppelin-web-angular/src/app/interfaces/public-api.ts b/zeppelin-web-angular/src/app/interfaces/public-api.ts
new file mode 100644
index 00000000000..e762a5c3667
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/public-api.ts
@@ -0,0 +1,20 @@
+/*
+ * Licensed 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 * from './ticket';
+export * from './trash-folder-id';
+export * from './interpreter';
+export * from './message-interceptor';
+export * from './security';
+export * from './credential';
+export * from './notebook-repo';
+export * from './notebook-search';
diff --git a/zeppelin-web-angular/src/app/interfaces/security.ts b/zeppelin-web-angular/src/app/interfaces/security.ts
new file mode 100644
index 00000000000..ff7db2a97d2
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/security.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed 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 interface SecurityUserList {
+ roles: string[];
+ users: string[];
+}
+
+export interface Permissions {
+ readers: string[];
+ owners: string[];
+ writers: string[];
+ runners: string[];
+}
diff --git a/zeppelin-web-angular/src/app/interfaces/ticket.ts b/zeppelin-web-angular/src/app/interfaces/ticket.ts
new file mode 100644
index 00000000000..0f4883e0d2b
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/ticket.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 class ITicket {
+ principal = '';
+ ticket = '';
+ redirectURL = '';
+ roles = '';
+}
+
+export class IZeppelinVersion {
+ 'git-commit-id': string;
+ 'git-timestamp': string;
+ 'version': string;
+}
+
+export class ITicketWrapped extends ITicket {
+ init = false;
+ screenUsername = '';
+}
diff --git a/zeppelin-web-angular/src/app/interfaces/trash-folder-id.ts b/zeppelin-web-angular/src/app/interfaces/trash-folder-id.ts
new file mode 100644
index 00000000000..035b92a1789
--- /dev/null
+++ b/zeppelin-web-angular/src/app/interfaces/trash-folder-id.ts
@@ -0,0 +1,15 @@
+/*
+ * Licensed 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 { InjectionToken } from '@angular/core';
+
+export const TRASH_FOLDER_ID_TOKEN = new InjectionToken('TRASH_FOLDER_ID');
diff --git a/zeppelin-web-angular/src/app/languages/index.ts b/zeppelin-web-angular/src/app/languages/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/languages/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/languages/load.ts b/zeppelin-web-angular/src/app/languages/load.ts
new file mode 100644
index 00000000000..64c617acd6e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/languages/load.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 { editor, languages } from 'monaco-editor';
+import { conf as ScalaConf, language as ScalaLanguage } from './scala';
+
+export const loadMonacoBefore = () => {
+ editor.defineTheme('zeppelin-theme', {
+ base: 'vs',
+ inherit: true,
+ rules: [],
+ colors: {
+ 'editor.lineHighlightBackground': '#0000FF10'
+ }
+ });
+ editor.setTheme('zeppelin-theme');
+ languages.register({ id: 'scala' });
+ languages.setMonarchTokensProvider('scala', ScalaLanguage);
+ languages.setLanguageConfiguration('scala', ScalaConf);
+};
diff --git a/zeppelin-web-angular/src/app/languages/public-api.ts b/zeppelin-web-angular/src/app/languages/public-api.ts
new file mode 100644
index 00000000000..973c93158ba
--- /dev/null
+++ b/zeppelin-web-angular/src/app/languages/public-api.ts
@@ -0,0 +1,14 @@
+/*
+ * Licensed 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 * from './scala';
+export * from './load';
diff --git a/zeppelin-web-angular/src/app/languages/scala.ts b/zeppelin-web-angular/src/app/languages/scala.ts
new file mode 100644
index 00000000000..581049129e0
--- /dev/null
+++ b/zeppelin-web-angular/src/app/languages/scala.ts
@@ -0,0 +1,236 @@
+/*
+ * Licensed 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.
+ */
+
+'use strict';
+
+import IRichLanguageConfiguration = monaco.languages.LanguageConfiguration;
+import ILanguage = monaco.languages.IMonarchLanguage;
+
+export const conf: IRichLanguageConfiguration = {
+ // the default separators except `@$`
+ wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
+ comments: {
+ lineComment: '//',
+ blockComment: ['/*', '*/']
+ },
+ brackets: [['{', '}'], ['[', ']'], ['(', ')']],
+ autoClosingPairs: [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: '"', close: '"' },
+ { open: "'", close: "'" }
+ ],
+ surroundingPairs: [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: '"', close: '"' },
+ { open: "'", close: "'" },
+ { open: '<', close: '>' }
+ ]
+};
+
+export const language = {
+ defaultToken: '',
+ tokenPostfix: '.scala',
+
+ keywords: [
+ // From https://www.scala-lang.org/files/archive/spec/2.11/01-lexical-syntax.html
+ 'abstract',
+ 'case',
+ 'catch',
+ 'class',
+ 'def',
+ 'do',
+ 'else',
+ 'extends',
+ 'false',
+ 'final',
+ 'finally',
+ 'for',
+ 'forSome',
+ 'if',
+ 'implicit',
+ 'import',
+ 'lazy',
+ 'macro',
+ 'match',
+ 'new',
+ 'null',
+ 'object',
+ 'override',
+ 'package',
+ 'private',
+ 'protected',
+ 'return',
+ 'sealed',
+ 'super',
+ 'this',
+ 'throw',
+ 'trait',
+ 'try',
+ 'true',
+ 'type',
+ 'val',
+ 'var',
+ 'while',
+ 'with',
+ 'yield'
+ ],
+
+ operators: [
+ '_',
+ ':',
+ '=',
+ '=>',
+ '<-',
+ '<:',
+ '<%',
+ '>:',
+ '#',
+ '@',
+
+ // Copied from java.ts, to be validated
+ '=',
+ '>',
+ '<',
+ '!',
+ '~',
+ '?',
+ ':',
+ '==',
+ '<=',
+ '>=',
+ '!=',
+ '&&',
+ '||',
+ '++',
+ '--',
+ '+',
+ '-',
+ '*',
+ '/',
+ '&',
+ '|',
+ '^',
+ '%',
+ '<<',
+ '>>',
+ '>>>',
+ '+=',
+ '-=',
+ '*=',
+ '/=',
+ '&=',
+ '|=',
+ '^=',
+ '%=',
+ '<<=',
+ '>>=',
+ '>>>='
+ ],
+
+ // we include these common regular expressions
+ symbols: /[=>](?!@symbols)/, '@brackets'],
+ [
+ /@symbols/,
+ {
+ cases: {
+ '@operators': 'delimiter',
+ '@default': ''
+ }
+ }
+ ],
+
+ // @ annotations.
+ [/@\s*[a-zA-Z_\$][\w\$]*/, 'annotation'],
+
+ // numbers
+ [/(@digits)[eE]([\-+]?(@digits))?[fFdD]?/, 'number.float'],
+ [/(@digits)\.(@digits)([eE][\-+]?(@digits))?[fFdD]?/, 'number.float'],
+ [/0[xX](@hexdigits)[Ll]?/, 'number.hex'],
+ [/0(@octaldigits)[Ll]?/, 'number.octal'],
+ [/0[bB](@binarydigits)[Ll]?/, 'number.binary'],
+ [/(@digits)[fFdD]/, 'number.float'],
+ [/(@digits)[lL]?/, 'number'],
+
+ // delimiter: after number because of .\d floats
+ [/[;,.]/, 'delimiter'],
+
+ // strings
+ [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
+ [/"/, 'string', '@string'],
+
+ // characters
+ [/'[^\\']'/, 'string'],
+ [/(')(@escapes)(')/, ['string', 'string.escape', 'string']],
+ [/'/, 'string.invalid']
+ ],
+
+ whitespace: [
+ [/[ \t\r\n]+/, ''],
+ [/\/\*\*(?!\/)/, 'comment.doc', '@scaladoc'],
+ [/\/\*/, 'comment', '@comment'],
+ [/\/\/.*$/, 'comment']
+ ],
+
+ comment: [
+ [/[^\/*]+/, 'comment'],
+ // [/\/\*/, 'comment', '@push' ], // nested comment not allowed :-(
+ // [/\/\*/, 'comment.invalid' ], // this breaks block comments in the shape of /* //*/
+ [/\*\//, 'comment', '@pop'],
+ [/[\/*]/, 'comment']
+ ],
+ // Identical copy of comment above, except for the addition of .doc
+ scaladoc: [
+ [/[^\/*]+/, 'comment.doc'],
+ // [/\/\*/, 'comment.doc', '@push' ], // nested comment not allowed :-(
+ [/\/\*/, 'comment.doc.invalid'],
+ [/\*\//, 'comment.doc', '@pop'],
+ [/[\/*]/, 'comment.doc']
+ ],
+
+ string: [
+ [/[^\\"]+/, 'string'],
+ [/@escapes/, 'string.escape'],
+ [/\\./, 'string.escape.invalid'],
+ [/"/, 'string', '@pop']
+ ]
+ }
+} as ILanguage;
diff --git a/zeppelin-web-angular/src/app/pages/login/login-routing.module.ts b/zeppelin-web-angular/src/app/pages/login/login-routing.module.ts
new file mode 100644
index 00000000000..06dd0304db8
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/login/login-routing.module.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { LoginComponent } from './login.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: LoginComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class LoginRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/login/login.component.html b/zeppelin-web-angular/src/app/pages/login/login.component.html
new file mode 100644
index 00000000000..faa16b67189
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/login/login.component.html
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
Welcome to Zeppelin!
+ Zeppelin is web-based notebook that enables interactive data analytics.
+ You can make beautiful data-driven, interactive, collaborative document with SQL, code and even more!
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/login/login.component.less b/zeppelin-web-angular/src/app/pages/login/login.component.less
new file mode 100644
index 00000000000..7aac210300d
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/login/login.component.less
@@ -0,0 +1,69 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ .content {
+ height: 100vh;
+ width: 100vw;
+
+ &:after {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image: url("../../../assets/images/bg.jpg");
+ background-size: cover;
+ filter: blur(4px);
+ background-repeat: no-repeat;
+ background-position: center;
+ }
+
+ .inner {
+ width: 800px;
+ height: 300px;
+ position: absolute;
+ z-index: 1;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ box-shadow: @box-shadow-base;
+
+ .form {
+ width: 500px;
+ position: absolute;
+ left: 0;
+ height: 100%;
+ background: @component-background;
+ padding: 36px;
+ }
+
+ .sidebar {
+ width: 300px;
+ position: absolute;
+ right: 0;
+ height: 100%;
+ background: @primary-color;
+ padding: 24px 36px;
+ color: @text-color-dark;
+
+ h1 {
+ color: @heading-color-dark;
+ }
+ }
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/login/login.component.ts b/zeppelin-web-angular/src/app/pages/login/login.component.ts
new file mode 100644
index 00000000000..937724974b6
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/login/login.component.ts
@@ -0,0 +1,47 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { TicketService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-login',
+ templateUrl: './login.component.html',
+ styleUrls: ['./login.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class LoginComponent implements OnInit {
+ userName: string;
+ password: string;
+ loading = false;
+
+ login() {
+ this.loading = true;
+ this.ticketService.login(this.userName, this.password).subscribe(
+ () => {
+ this.loading = false;
+ this.cdr.markForCheck();
+ this.router.navigate(['/']).then();
+ },
+ () => {
+ this.loading = false;
+ this.cdr.markForCheck();
+ }
+ );
+ }
+
+ constructor(private ticketService: TicketService, private cdr: ChangeDetectorRef, private router: Router) {}
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/pages/login/login.module.ts b/zeppelin-web-angular/src/app/pages/login/login.module.ts
new file mode 100644
index 00000000000..7a040a69364
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/login/login.module.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzFormModule } from 'ng-zorro-antd/form';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzInputModule } from 'ng-zorro-antd/input';
+
+import { LoginRoutingModule } from './login-routing.module';
+import { LoginComponent } from './login.component';
+
+@NgModule({
+ declarations: [LoginComponent],
+ imports: [CommonModule, LoginRoutingModule, FormsModule, NzFormModule, NzInputModule, NzButtonModule, NzIconModule]
+})
+export class LoginModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration-routing.module.ts
new file mode 100644
index 00000000000..6d499a0c7bc
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration-routing.module.ts
@@ -0,0 +1,27 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { ConfigurationComponent } from './configuration.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: ConfigurationComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class ConfigurationRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.html b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.html
new file mode 100644
index 00000000000..6708f345203
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.html
@@ -0,0 +1,37 @@
+
+
+
+
+ Shows current configurations for Zeppelin Server.
+
+ Note: For security reasons, some key/value pairs including passwords would not be shown.
+
+
+
+
+
+
Name
+
Value
+
+
+
+
+
{{data[0]}}
+
{{data[1]}}
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.less b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.less
new file mode 100644
index 00000000000..b3c4ee24e55
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.less
@@ -0,0 +1,22 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ .content {
+ padding: @card-padding-base / 2;
+ nz-table {
+ background: @card-background;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.ts b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.ts
new file mode 100644
index 00000000000..fb3b1205743
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.component.ts
@@ -0,0 +1,36 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+import { ConfigurationService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-configuration',
+ templateUrl: './configuration.component.html',
+ styleUrls: ['./configuration.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ConfigurationComponent implements OnInit {
+ configEntries: Array<[string, string]> = [];
+
+ constructor(private configurationService: ConfigurationService, private cdr: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ this.getAllConfig();
+ }
+
+ getAllConfig(): void {
+ this.configurationService.getAll().subscribe(data => {
+ this.configEntries = [...Object.entries(data)];
+ this.cdr.markForCheck();
+ });
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.module.ts b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.module.ts
new file mode 100644
index 00000000000..954017082ec
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/configuration/configuration.module.ts
@@ -0,0 +1,24 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+
+import { ShareModule } from '@zeppelin/share';
+import { NzTableModule } from 'ng-zorro-antd/table';
+import { ConfigurationRoutingModule } from './configuration-routing.module';
+import { ConfigurationComponent } from './configuration.component';
+
+@NgModule({
+ declarations: [ConfigurationComponent],
+ imports: [CommonModule, ShareModule, NzTableModule, ConfigurationRoutingModule]
+})
+export class ConfigurationModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/credential/credential-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/credential/credential-routing.module.ts
new file mode 100644
index 00000000000..5624c7e28ba
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/credential/credential-routing.module.ts
@@ -0,0 +1,28 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { CredentialComponent } from './credential.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: CredentialComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class CredentialRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.html b/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.html
new file mode 100644
index 00000000000..51b0c84c208
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.html
@@ -0,0 +1,134 @@
+
+
+
+
+ Manage your credentials. You can add new credential information.
+
+
+
+
+
+
+
+
+
+
Add new credential
+
+
+
+
+
+
+
+
Entity
+
Username
+
Password
+
Actions
+
+
+
+
+
+
{{entity}}
+
+
+
+
+
+
+
+
+
+
+
+
{{control.get('username')?.value}}
+
**********
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.less b/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.less
new file mode 100644
index 00000000000..87649caf0a5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.less
@@ -0,0 +1,39 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ ::ng-deep {
+ .ant-form-inline .ant-form-item-with-help {
+ margin-bottom: 0;
+ }
+
+ .credential-actions, .new-actions {
+ text-align: right;
+ button+button {
+ margin-left: 8px;
+ }
+ }
+
+ .actions-head {
+ text-align: right;
+ }
+ }
+
+ .content {
+ padding: @card-padding-base / 2;
+ nz-table {
+ background: @card-background;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.ts b/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.ts
new file mode 100644
index 00000000000..b28a6a63dfa
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/credential/credential.component.ts
@@ -0,0 +1,202 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { collapseMotion } from 'ng-zorro-antd/core';
+import { NzMessageService } from 'ng-zorro-antd/message';
+
+import { finalize } from 'rxjs/operators';
+
+import { CredentialForm } from '@zeppelin/interfaces';
+import { CredentialService, InterpreterService, TicketService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-credential',
+ templateUrl: './credential.component.html',
+ styleUrls: ['./credential.component.less'],
+ animations: [collapseMotion],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class CredentialComponent implements OnInit {
+ addForm: FormGroup;
+ showAdd = false;
+ adding = false;
+ interpreterNames: string[] = [];
+ interpreterFilteredNames: string[] = [];
+ editFlags: Map = new Map();
+ credentialFormArray: FormArray = this.fb.array([]);
+ docsLink: string;
+
+ get credentialControls(): FormGroup[] {
+ return this.credentialFormArray.controls as FormGroup[];
+ }
+
+ constructor(
+ private cdr: ChangeDetectorRef,
+ private fb: FormBuilder,
+ private nzMessageService: NzMessageService,
+ private interpreterService: InterpreterService,
+ private credentialService: CredentialService,
+ private ticketService: TicketService
+ ) {
+ this.setDocsLink();
+ }
+
+ setDocsLink() {
+ const version = this.ticketService.version;
+ this.docsLink = `https://zeppelin.apache.org/docs/${version}/setup/security/datasource_authorization.html`;
+ }
+
+ onEntityInput(event: Event) {
+ const input = event.target as HTMLInputElement;
+ if (input && input.value) {
+ this.interpreterFilteredNames = this.interpreterNames
+ .filter(e => e.indexOf(input.value.trim()) !== -1)
+ .slice(0, 10);
+ } else {
+ this.interpreterFilteredNames = this.interpreterNames.slice(0, 10);
+ }
+ }
+
+ getEntityFromForm(form: FormGroup): string {
+ return form.get('entity') && form.get('entity').value;
+ }
+
+ isEditing(form: FormGroup): boolean {
+ const entity = this.getEntityFromForm(form);
+ return entity && this.editFlags.has(entity);
+ }
+
+ setEditable(form: FormGroup) {
+ const entity = this.getEntityFromForm(form);
+ if (entity) {
+ this.editFlags.set(entity, form.getRawValue());
+ }
+ this.cdr.markForCheck();
+ }
+
+ unsetEditable(form: FormGroup, reset = true) {
+ const entity = this.getEntityFromForm(form);
+ if (reset && entity && this.editFlags.has(entity)) {
+ form.reset(this.editFlags.get(entity));
+ }
+ this.editFlags.delete(entity);
+ this.cdr.markForCheck();
+ }
+
+ submitForm(): void {
+ for (const i in this.addForm.controls) {
+ this.addForm.controls[i].markAsDirty();
+ this.addForm.controls[i].updateValueAndValidity();
+ }
+ if (this.addForm.valid) {
+ const data = this.addForm.getRawValue() as CredentialForm;
+ this.addCredential(data);
+ }
+ }
+
+ saveCredential(form: FormGroup) {
+ for (const i in form.controls) {
+ form.controls[i].markAsDirty();
+ form.controls[i].updateValueAndValidity();
+ }
+ if (form.valid) {
+ this.credentialService.updateCredential(form.getRawValue()).subscribe(() => {
+ this.nzMessageService.success('Successfully saved credentials.');
+ this.unsetEditable(form, false);
+ });
+ }
+ }
+
+ removeCredential(form: FormGroup) {
+ const entity = this.getEntityFromForm(form);
+ if (entity) {
+ this.credentialService.removeCredential(entity).subscribe(() => {
+ this.getCredentials();
+ });
+ }
+ }
+
+ triggerAdd(): void {
+ this.showAdd = !this.showAdd;
+ this.cdr.markForCheck();
+ }
+
+ cancelAdd() {
+ this.showAdd = false;
+ this.resetAddForm();
+ this.cdr.markForCheck();
+ }
+
+ getCredentials() {
+ this.credentialService.getCredentials().subscribe(data => {
+ const controls = [...Object.entries(data.userCredentials)].map(e => {
+ const entity = e[0];
+ const { username, password } = e[1];
+ return this.fb.group({
+ entity: [entity, [Validators.required]],
+ username: [username, [Validators.required]],
+ password: [password, [Validators.required]]
+ });
+ });
+ this.credentialFormArray = this.fb.array(controls);
+ this.cdr.markForCheck();
+ });
+ }
+
+ getInterpreterNames() {
+ this.interpreterService.getInterpretersSetting().subscribe(data => {
+ this.interpreterNames = data.map(e => `${e.group}.${e.name}`);
+ this.interpreterFilteredNames = this.interpreterNames.slice(0, 10);
+ this.cdr.markForCheck();
+ });
+ }
+
+ addCredential(data: CredentialForm) {
+ this.adding = true;
+ this.cdr.markForCheck();
+ this.credentialService
+ .addCredential(data)
+ .pipe(
+ finalize(() => {
+ this.adding = false;
+ this.cdr.markForCheck();
+ })
+ )
+ .subscribe(() => {
+ this.nzMessageService.success('Successfully saved credentials.');
+ this.getCredentials();
+ this.resetAddForm();
+ this.cdr.markForCheck();
+ });
+ }
+
+ resetAddForm() {
+ this.addForm.reset({
+ entity: null,
+ username: null,
+ password: null
+ });
+ this.cdr.markForCheck();
+ }
+
+ ngOnInit(): void {
+ this.getCredentials();
+ this.getInterpreterNames();
+ this.addForm = this.fb.group({
+ entity: [null, [Validators.required]],
+ username: [null, [Validators.required]],
+ password: [null, [Validators.required]]
+ });
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/credential/credential.module.ts b/zeppelin-web-angular/src/app/pages/workspace/credential/credential.module.ts
new file mode 100644
index 00000000000..39756edb9f4
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/credential/credential.module.ts
@@ -0,0 +1,54 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ShareModule } from '@zeppelin/share';
+import { NzAutocompleteModule } from 'ng-zorro-antd/auto-complete';
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzCardModule } from 'ng-zorro-antd/card';
+import { NzDividerModule } from 'ng-zorro-antd/divider';
+import { NzFormModule } from 'ng-zorro-antd/form';
+import { NzGridModule } from 'ng-zorro-antd/grid';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzInputModule } from 'ng-zorro-antd/input';
+import { NzMessageModule } from 'ng-zorro-antd/message';
+import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
+import { NzTableModule } from 'ng-zorro-antd/table';
+import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
+import { CredentialRoutingModule } from './credential-routing.module';
+import { CredentialComponent } from './credential.component';
+
+@NgModule({
+ declarations: [CredentialComponent],
+ imports: [
+ CommonModule,
+ CredentialRoutingModule,
+ FormsModule,
+ ShareModule,
+ ReactiveFormsModule,
+ NzFormModule,
+ NzAutocompleteModule,
+ NzButtonModule,
+ NzCardModule,
+ NzIconModule,
+ NzDividerModule,
+ NzInputModule,
+ NzMessageModule,
+ NzTableModule,
+ NzPopconfirmModule,
+ NzGridModule,
+ NzToolTipModule
+ ]
+})
+export class CredentialModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/home/home-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/home/home-routing.module.ts
new file mode 100644
index 00000000000..23a345f025a
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/home/home-routing.module.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { HomeComponent } from './home.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: HomeComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class HomeRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/home/home.component.html b/zeppelin-web-angular/src/app/pages/workspace/home/home.component.html
new file mode 100644
index 00000000000..5a26044c970
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/home/home.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ Welcome to Zeppelin!
+
+ Zeppelin is web-based notebook that enables interactive data analytics.
+ You can make beautiful data-driven, interactive, collaborative document with SQL, code and even more!
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/home/home.component.less b/zeppelin-web-angular/src/app/pages/workspace/home/home.component.less
new file mode 100644
index 00000000000..3f79862ddc6
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/home/home.component.less
@@ -0,0 +1,37 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ .content {
+ margin: 12px;
+ border: 1px solid @border-color-base;
+ padding: 24px;
+ background-color: @component-background;
+ position: relative;
+ background-image: url(../../../../assets/images/zeppelin_svg_logo_bg.svg);
+ background-repeat: no-repeat;
+ background-position: right bottom;
+ }
+ .welcome {
+ margin-bottom: 12px;
+ }
+ .more-info {
+ h3 {
+ margin-top: 0.5em;
+ }
+ }
+ .refresh-note {
+ margin-left: 6px
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/home/home.component.ts b/zeppelin-web-angular/src/app/pages/workspace/home/home.component.ts
new file mode 100644
index 00000000000..ecc40995d68
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/home/home.component.ts
@@ -0,0 +1,48 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+
+import { MessageListener, MessageListenersManager } from '@zeppelin/core';
+import { OP } from '@zeppelin/sdk';
+import { MessageService, TicketService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-home',
+ templateUrl: './home.component.html',
+ styleUrls: ['./home.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class HomeComponent extends MessageListenersManager implements OnInit {
+ loading = false;
+
+ reloadNoteList() {
+ this.messageService.reloadAllNotesFromRepo();
+ this.loading = true;
+ }
+
+ @MessageListener(OP.NOTES_INFO)
+ getNotes() {
+ this.loading = false;
+ this.cdr.markForCheck();
+ }
+
+ constructor(
+ public ticketService: TicketService,
+ public messageService: MessageService,
+ private cdr: ChangeDetectorRef
+ ) {
+ super(messageService);
+ }
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/home/home.module.ts b/zeppelin-web-angular/src/app/pages/workspace/home/home.module.ts
new file mode 100644
index 00000000000..49a29734486
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/home/home.module.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+
+import { NzGridModule } from 'ng-zorro-antd/grid';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
+
+import { ShareModule } from '@zeppelin/share';
+
+import { HomeRoutingModule } from './home-routing.module';
+import { HomeComponent } from './home.component';
+
+@NgModule({
+ declarations: [HomeComponent],
+ imports: [CommonModule, HomeRoutingModule, NzGridModule, NzIconModule, NzToolTipModule, ShareModule]
+})
+export class HomeModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.html b/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.html
new file mode 100644
index 00000000000..57a0b6339d7
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.html
@@ -0,0 +1,158 @@
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.less b/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.less
new file mode 100644
index 00000000000..f348ef32466
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.less
@@ -0,0 +1,19 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ nz-form-item {
+ margin-bottom: 5px;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.ts b/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.ts
new file mode 100644
index 00000000000..90e02a7f3ed
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/create-repository-modal/create-repository-modal.component.ts
@@ -0,0 +1,78 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
+import { takeUntil } from 'rxjs/operators';
+
+import { NzModalRef } from 'ng-zorro-antd/modal';
+
+import { DestroyHookComponent } from '@zeppelin/core';
+import { CreateInterpreterRepositoryForm } from '@zeppelin/interfaces';
+import { InterpreterService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-interpreter-create-repository-modal',
+ templateUrl: './create-repository-modal.component.html',
+ styleUrls: ['./create-repository-modal.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class InterpreterCreateRepositoryModalComponent extends DestroyHookComponent implements OnInit {
+ validateForm: FormGroup;
+ submitting = false;
+ urlProtocol = 'http://';
+
+ handleCancel() {
+ this.nzModalRef.close();
+ }
+
+ handleSubmit() {
+ const data = this.validateForm.getRawValue() as CreateInterpreterRepositoryForm;
+ // set url protocol
+ data.url = `${this.urlProtocol}${data.url}`;
+ // reset proxy port
+ const proxyPort = Number.parseInt(data.proxyPort, 10);
+ data.proxyPort = Number.isNaN(proxyPort) ? null : `${proxyPort}`;
+ this.interpreterService
+ .addRepository(data)
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(() => {
+ this.nzModalRef.close('Done');
+ });
+ }
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private nzModalRef: NzModalRef,
+ private interpreterService: InterpreterService
+ ) {
+ super();
+ }
+
+ ngOnInit() {
+ this.validateForm = this.formBuilder.group({
+ id: ['', [Validators.required]],
+ url: ['', [Validators.required]],
+ snapshot: [false, [Validators.required]],
+ username: '',
+ password: '',
+ proxyProtocol: 'HTTP',
+ proxyHost: '',
+ proxyPort: [
+ null,
+ [Validators.pattern('^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$')]
+ ],
+ proxyLogin: '',
+ proxyPassword: ''
+ });
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter-routing.module.ts
new file mode 100644
index 00000000000..c9c995825cb
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter-routing.module.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { InterpreterComponent } from './interpreter.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: InterpreterComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class InterpreterRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.html b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.html
new file mode 100644
index 00000000000..144a57d2fca
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.html
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
Repositories
+
Available repository lists. These repositories are used to resolve external dependencies of interpreter.
+
+ {{repo.id}}
+
+
+
+
+
+
+
+
+
+ Create
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.less b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.less
new file mode 100644
index 00000000000..8d5f88d6044
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.less
@@ -0,0 +1,51 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ .search-input {
+ width: 256px;
+ }
+
+ @media (max-width:959px){
+ .search-input {
+ width: 100%;
+ }
+ }
+
+ nz-tag.repo-item {
+ border-color: transparent;
+ background: @primary-6;
+ color: @text-color-inverse;
+ ::ng-deep i.anticon-close {
+ color: @text-color-inverse;
+ &:hover {
+ color: @icon-color-hover;
+ }
+ }
+ }
+
+ .editable-tag {
+ background: @white;
+ border-style: dashed;
+ }
+
+ .content {
+ padding: @card-padding-base / 2;
+
+ .create-interpreter {
+ text-align: center;
+ margin-bottom: 10px;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.ts b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.ts
new file mode 100644
index 00000000000..0507cd8313c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.component.ts
@@ -0,0 +1,188 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
+import { Subject } from 'rxjs';
+import { debounceTime } from 'rxjs/operators';
+
+import { collapseMotion } from 'ng-zorro-antd/core';
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalService } from 'ng-zorro-antd/modal';
+
+import { Interpreter, InterpreterPropertyTypes, InterpreterRepository } from '@zeppelin/interfaces';
+import { InterpreterService } from '@zeppelin/services';
+
+import { InterpreterCreateRepositoryModalComponent } from './create-repository-modal/create-repository-modal.component';
+
+@Component({
+ selector: 'zeppelin-interpreter',
+ templateUrl: './interpreter.component.html',
+ styleUrls: ['./interpreter.component.less'],
+ animations: [collapseMotion],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class InterpreterComponent implements OnInit, OnDestroy {
+ searchInterpreter = '';
+ search$ = new Subject();
+ showRepository = false;
+ showCreateSetting = false;
+ propertyTypes: InterpreterPropertyTypes[] = [];
+ interpreterSettings: Interpreter[] = [];
+ repositories: InterpreterRepository[] = [];
+ availableInterpreters: Interpreter[] = [];
+ filteredInterpreterSettings: Interpreter[] = [];
+
+ onSearchChange(value: string) {
+ this.search$.next(value);
+ }
+
+ filterInterpreters(value: string) {
+ this.filteredInterpreterSettings = this.interpreterSettings.filter(e => e.name.search(value) !== -1);
+ this.cdr.markForCheck();
+ }
+
+ triggerRepository(): void {
+ this.showRepository = !this.showRepository;
+ this.cdr.markForCheck();
+ }
+
+ removeRepository(repo: InterpreterRepository): void {
+ this.nzModalService.confirm({
+ nzTitle: repo.id,
+ nzContent: 'Do you want to delete this repository?',
+ nzOnOk: () => {
+ this.interpreterService.removeRepository(repo.id).subscribe(() => {
+ this.repositories = this.repositories.filter(e => e.id !== repo.id);
+ this.cdr.markForCheck();
+ });
+ }
+ });
+ }
+
+ addInterpreterSetting(data: Interpreter): void {
+ this.interpreterService.addInterpreterSetting(data).subscribe(res => {
+ this.interpreterSettings.push(res);
+ this.showCreateSetting = false;
+ this.filterInterpreters(this.searchInterpreter);
+ this.cdr.markForCheck();
+ });
+ }
+
+ updateInterpreter(data: Interpreter): void {
+ this.interpreterService.updateInterpreter(data).subscribe(res => {
+ const current = this.interpreterSettings.find(e => e.name === res.name);
+ if (current) {
+ current.status = res.status;
+ current.errorReason = res.errorReason;
+ current.option = res.option;
+ current.properties = res.properties;
+ current.dependencies = res.dependencies;
+ }
+ this.filterInterpreters(this.searchInterpreter);
+ this.cdr.markForCheck();
+ });
+ }
+
+ removeInterpreterSetting(settingId: string): void {
+ this.nzModalService.confirm({
+ nzTitle: 'Remove Interpreter',
+ nzContent: 'Do you want to delete this interpreter setting?',
+ nzOnOk: () => {
+ this.interpreterService.removeInterpreterSetting(settingId).subscribe(() => {
+ const index = this.interpreterSettings.findIndex(e => e.name === settingId);
+ this.interpreterSettings.splice(index, 1);
+ this.filterInterpreters(this.searchInterpreter);
+ this.cdr.markForCheck();
+ });
+ }
+ });
+ }
+
+ restartInterpreterSetting(settingId: string): void {
+ this.nzModalService.confirm({
+ nzTitle: 'Restart Interpreter',
+ nzContent: 'Do you want to restart this interpreter?',
+ nzOnOk: () => {
+ this.interpreterService.restartInterpreterSetting(settingId).subscribe(() => {
+ this.nzMessageService.info('Interpreter stopped. Will be lazily started on next run.');
+ });
+ }
+ });
+ }
+
+ createRepository(): void {
+ const modalRef = this.nzModalService.create({
+ nzTitle: 'Add New Repository',
+ nzContent: InterpreterCreateRepositoryModalComponent,
+ nzFooter: null,
+ nzWidth: '600px'
+ });
+ modalRef.afterClose.subscribe(data => {
+ if (data === 'Done') {
+ this.getRepositories();
+ }
+ });
+ }
+
+ getPropertyTypes(): void {
+ this.interpreterService.getAvailableInterpreterPropertyTypes().subscribe(data => {
+ this.propertyTypes = data;
+ this.cdr.markForCheck();
+ });
+ }
+
+ getInterpreterSettings(): void {
+ this.interpreterService.getInterpretersSetting().subscribe(data => {
+ this.interpreterSettings = data;
+ this.filteredInterpreterSettings = data;
+ this.cdr.markForCheck();
+ });
+ }
+
+ getAvailableInterpreters(): void {
+ this.interpreterService.getAvailableInterpreters().subscribe(data => {
+ this.availableInterpreters = Object.keys(data)
+ .sort()
+ .map(key => data[key]);
+ this.cdr.markForCheck();
+ });
+ }
+
+ getRepositories(): void {
+ this.interpreterService.getRepositories().subscribe(data => {
+ this.repositories = data;
+ this.cdr.markForCheck();
+ });
+ }
+
+ constructor(
+ private interpreterService: InterpreterService,
+ private cdr: ChangeDetectorRef,
+ private nzModalService: NzModalService,
+ private nzMessageService: NzMessageService
+ ) {}
+
+ ngOnInit() {
+ this.getPropertyTypes();
+ this.getInterpreterSettings();
+ this.getAvailableInterpreters();
+ this.getRepositories();
+
+ this.search$.pipe(debounceTime(150)).subscribe(value => this.filterInterpreters(value));
+ }
+
+ ngOnDestroy(): void {
+ this.search$.next();
+ this.search$.complete();
+ this.search$ = null;
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.module.ts b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.module.ts
new file mode 100644
index 00000000000..6ed202f1774
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/interpreter.module.ts
@@ -0,0 +1,71 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { NzAlertModule } from 'ng-zorro-antd/alert';
+import { NzBadgeModule } from 'ng-zorro-antd/badge';
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzCardModule } from 'ng-zorro-antd/card';
+import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
+import { NzDividerModule } from 'ng-zorro-antd/divider';
+import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
+import { NzFormModule } from 'ng-zorro-antd/form';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzInputModule } from 'ng-zorro-antd/input';
+import { NzMessageModule } from 'ng-zorro-antd/message';
+import { NzModalModule } from 'ng-zorro-antd/modal';
+import { NzRadioModule } from 'ng-zorro-antd/radio';
+import { NzSelectModule } from 'ng-zorro-antd/select';
+import { NzSwitchModule } from 'ng-zorro-antd/switch';
+import { NzTableModule } from 'ng-zorro-antd/table';
+import { NzTagModule } from 'ng-zorro-antd/tag';
+import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
+
+import { ShareModule } from '@zeppelin/share';
+
+import { InterpreterCreateRepositoryModalComponent } from './create-repository-modal/create-repository-modal.component';
+import { InterpreterRoutingModule } from './interpreter-routing.module';
+import { InterpreterComponent } from './interpreter.component';
+import { InterpreterItemComponent } from './item/item.component';
+
+@NgModule({
+ declarations: [InterpreterComponent, InterpreterCreateRepositoryModalComponent, InterpreterItemComponent],
+ entryComponents: [InterpreterCreateRepositoryModalComponent],
+ imports: [
+ CommonModule,
+ FormsModule,
+ ReactiveFormsModule,
+ InterpreterRoutingModule,
+ ShareModule,
+ NzFormModule,
+ NzSelectModule,
+ NzSwitchModule,
+ NzToolTipModule,
+ NzCheckboxModule,
+ NzRadioModule,
+ NzBadgeModule,
+ NzButtonModule,
+ NzModalModule,
+ NzInputModule,
+ NzDividerModule,
+ NzTagModule,
+ NzCardModule,
+ NzDropDownModule,
+ NzIconModule,
+ NzTableModule,
+ NzMessageModule,
+ NzAlertModule
+ ]
+})
+export class InterpreterModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/interpreter/item/item.component.html b/zeppelin-web-angular/src/app/pages/workspace/interpreter/item/item.component.html
new file mode 100644
index 00000000000..2e4e374520b
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/interpreter/item/item.component.html
@@ -0,0 +1,447 @@
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/job-manager/job/job.component.less b/zeppelin-web-angular/src/app/pages/workspace/job-manager/job/job.component.less
new file mode 100644
index 00000000000..bf958132e1c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/job-manager/job/job.component.less
@@ -0,0 +1,70 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ display: block;
+ margin-bottom: 10px;
+ position: relative;
+
+ ::ng-deep .job-item .ant-card-body {
+ padding: @card-padding-base / 2;
+ }
+
+ .job-title {
+ margin-bottom: 10px;
+ }
+
+ .note-icon {
+ margin-right: 5px;
+ }
+
+ .right-tools {
+ display: inline-block;
+ float: right;
+ color: @text-color-secondary;
+ & > * {
+ margin-left: 5px;
+ }
+ .job-control-btn {
+ cursor: pointer;
+ color: @processing-color;
+ &.running {
+ color: @error-color;
+ }
+ }
+ }
+
+ .interpreter {
+ color: @text-color;
+ &.unset {
+ color: @text-color-secondary;
+ }
+ }
+
+ zeppelin-job-manager-job-status {
+ ::ng-deep .ant-badge-status-text {
+ margin-left: 0;
+ }
+ margin: 0 4px;
+ }
+
+ .footer-progress {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ margin-bottom: -5px;
+ padding: 0 1px;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/job-manager/job/job.component.ts b/zeppelin-web-angular/src/app/pages/workspace/job-manager/job/job.component.ts
new file mode 100644
index 00000000000..e2990c24922
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/job-manager/job/job.component.ts
@@ -0,0 +1,83 @@
+/*
+ * Licensed 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 {
+ ChangeDetectionStrategy,
+ Component,
+ EventEmitter,
+ Input,
+ OnChanges,
+ OnInit,
+ Output,
+ SimpleChanges
+} from '@angular/core';
+
+import * as distanceInWords from 'date-fns/distance_in_words';
+
+import { JobsItem, JobStatus } from '@zeppelin/sdk';
+
+@Component({
+ selector: 'zeppelin-job-manager-job',
+ templateUrl: './job.component.html',
+ styleUrls: ['./job.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class JobManagerJobComponent implements OnInit, OnChanges {
+ @Input() note: JobsItem;
+ @Input() highlight: string | null = null;
+ @Output() readonly start = new EventEmitter();
+ @Output() readonly stop = new EventEmitter();
+
+ icon = 'file';
+ relativeTime = '';
+ progress = 0;
+
+ setIcon(): void {
+ const noteType = this.note.noteType;
+ if (noteType === 'normal') {
+ this.icon = 'file';
+ } else if (noteType === 'cron') {
+ this.icon = 'close-circle';
+ } else {
+ this.icon = 'file-unknown';
+ }
+ }
+
+ setRelativeTime(): void {
+ this.relativeTime = distanceInWords(new Date(), new Date(this.note.unixTimeLastRun));
+ }
+
+ setProgress(): void {
+ const runningCount = this.note.paragraphs.filter(
+ paragraph => [JobStatus.FINISHED, JobStatus.RUNNING].indexOf(paragraph.status) !== -1
+ ).length;
+ this.progress = runningCount / this.note.paragraphs.length;
+ }
+
+ onStartClick(): void {
+ this.start.emit(this.note.noteId);
+ }
+
+ onStopClick(): void {
+ this.stop.emit(this.note.noteId);
+ }
+
+ constructor() {}
+
+ ngOnInit() {}
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.setIcon();
+ this.setRelativeTime();
+ this.setProgress();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.html
new file mode 100644
index 00000000000..3f4875a5bca
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
Setting
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.less
new file mode 100644
index 00000000000..7e242390dcf
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.less
@@ -0,0 +1,37 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+
+ display: block;
+ margin-bottom: @card-padding-base;
+ position: relative;
+
+ ::ng-deep .repo-item {
+ &.edit {
+ //background: @orange-1;
+ }
+ }
+
+ .extra-wrap {
+ button {
+ transition: none;
+ }
+ button + button {
+ margin-bottom: 0;
+ margin-left: 8px;
+ }
+ }
+
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.ts
new file mode 100644
index 00000000000..f66247c9599
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/item/item.component.ts
@@ -0,0 +1,78 @@
+/*
+ * Licensed 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 {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ OnChanges,
+ Output,
+ SimpleChanges
+} from '@angular/core';
+import { FormArray, FormBuilder, Validators } from '@angular/forms';
+import { NotebookRepo } from '@zeppelin/interfaces';
+
+@Component({
+ selector: 'zeppelin-notebook-repo-item',
+ templateUrl: './item.component.html',
+ styleUrls: ['./item.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookRepoItemComponent implements OnChanges {
+ @Input() repo: NotebookRepo;
+ @Output() readonly repoChange = new EventEmitter();
+
+ settingFormArray: FormArray;
+ editMode = false;
+
+ constructor(private cdr: ChangeDetectorRef, private fb: FormBuilder) {}
+
+ triggerEditMode() {
+ this.editMode = !this.editMode;
+ this.cdr.markForCheck();
+ }
+
+ save() {
+ this.settingFormArray.controls.forEach(control => {
+ control.markAsDirty();
+ control.updateValueAndValidity();
+ });
+
+ if (this.settingFormArray.valid) {
+ const values = this.settingFormArray.getRawValue() as string[];
+ values.forEach((value, i) => (this.repo.settings[i].selected = value));
+ this.repoChange.emit(this.repo);
+ this.editMode = false;
+ this.cdr.markForCheck();
+ }
+ }
+
+ cancel() {
+ this.buildForm();
+ this.editMode = false;
+ this.cdr.markForCheck();
+ }
+
+ buildForm() {
+ const controls = this.repo.settings.map(setting => {
+ return this.fb.control(setting.selected, [Validators.required]);
+ });
+ this.settingFormArray = this.fb.array(controls);
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes.repo) {
+ this.buildForm();
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos-routing.module.ts
new file mode 100644
index 00000000000..e7e7ca19ad2
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos-routing.module.ts
@@ -0,0 +1,28 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { NotebookReposComponent } from './notebook-repos.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: NotebookReposComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class NotebookReposRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.html
new file mode 100644
index 00000000000..d47ad877782
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.less
new file mode 100644
index 00000000000..5a3f8f0b1ff
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.less
@@ -0,0 +1,20 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+
+ .content {
+ padding: @card-padding-base / 2;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.ts
new file mode 100644
index 00000000000..1faffeeeab0
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.component.ts
@@ -0,0 +1,51 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+import { NotebookRepo } from '@zeppelin/interfaces';
+import { NotebookRepoService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-notebook-repos',
+ templateUrl: './notebook-repos.component.html',
+ styleUrls: ['./notebook-repos.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookReposComponent implements OnInit {
+ repositories: NotebookRepo[] = [];
+
+ constructor(private notebookRepoService: NotebookRepoService, private cdr: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ this.getRepos();
+ }
+
+ getRepos() {
+ this.notebookRepoService.getRepos().subscribe(data => {
+ this.repositories = data.sort((a, b) => a.name.charCodeAt(0) - b.name.charCodeAt(0));
+ this.cdr.markForCheck();
+ });
+ }
+
+ updateRepoSetting(repo: NotebookRepo) {
+ const data = {
+ name: repo.className,
+ settings: {}
+ };
+ repo.settings.forEach(({ name, selected }) => {
+ data.settings[name] = selected;
+ });
+
+ this.notebookRepoService.updateRepo(data).subscribe(() => {
+ this.getRepos();
+ });
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.module.ts
new file mode 100644
index 00000000000..9f30074443e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-repos/notebook-repos.module.ts
@@ -0,0 +1,47 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+
+import { ShareModule } from '@zeppelin/share';
+
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzCardModule } from 'ng-zorro-antd/card';
+import { NzFormModule } from 'ng-zorro-antd/form';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzInputModule } from 'ng-zorro-antd/input';
+import { NzSelectModule } from 'ng-zorro-antd/select';
+import { NzTableModule } from 'ng-zorro-antd/table';
+
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { NotebookRepoItemComponent } from './item/item.component';
+import { NotebookReposRoutingModule } from './notebook-repos-routing.module';
+import { NotebookReposComponent } from './notebook-repos.component';
+
+@NgModule({
+ declarations: [NotebookReposComponent, NotebookRepoItemComponent],
+ imports: [
+ CommonModule,
+ ShareModule,
+ FormsModule,
+ ReactiveFormsModule,
+ NotebookReposRoutingModule,
+ NzCardModule,
+ NzButtonModule,
+ NzInputModule,
+ NzTableModule,
+ NzIconModule,
+ NzFormModule,
+ NzSelectModule
+ ]
+})
+export class NotebookReposModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search-routing.module.ts
new file mode 100644
index 00000000000..91277928ca4
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search-routing.module.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { NotebookSearchComponent } from './notebook-search.component';
+
+const routes: Routes = [
+ {
+ path: '',
+ component: NotebookSearchComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class NotebookSearchRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.html
new file mode 100644
index 00000000000..e1db6d90f26
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.less
new file mode 100644
index 00000000000..108b1a4ac92
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.less
@@ -0,0 +1,24 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ .main {
+ padding: @card-padding-base / 2;
+ }
+
+ zeppelin-notebook-search-result-item {
+ margin-bottom: 16px;
+ display: block;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.ts
new file mode 100644
index 00000000000..2036d23b828
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.ts
@@ -0,0 +1,58 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { NotebookSearchResultItem } from '@zeppelin/interfaces';
+import { NotebookSearchService } from '@zeppelin/services/notebook-search.service';
+import { Subject } from 'rxjs';
+import { filter, map, switchMap, takeUntil, tap } from 'rxjs/operators';
+
+@Component({
+ selector: 'zeppelin-notebook-search',
+ templateUrl: './notebook-search.component.html',
+ styleUrls: ['./notebook-search.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookSearchComponent implements OnInit, OnDestroy {
+ private destroy$ = new Subject();
+ private searchAction$ = this.router.params.pipe(
+ takeUntil(this.destroy$),
+ map(params => params.queryStr),
+ filter(queryStr => typeof queryStr === 'string' && !!queryStr.trim()),
+ tap(() => (this.searching = true)),
+ switchMap(queryStr => this.notebookSearchService.search(queryStr))
+ );
+
+ results: NotebookSearchResultItem[] = [];
+ searching = false;
+
+ constructor(
+ private cdr: ChangeDetectorRef,
+ private router: ActivatedRoute,
+ private notebookSearchService: NotebookSearchService
+ ) {}
+
+ ngOnInit() {
+ this.searchAction$.subscribe(results => {
+ this.results = results;
+ this.searching = false;
+ this.cdr.markForCheck();
+ });
+ }
+
+ ngOnDestroy(): void {
+ this.notebookSearchService.clear();
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.module.ts
new file mode 100644
index 00000000000..69dafa8688f
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.module.ts
@@ -0,0 +1,29 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+
+import { NzCardModule } from 'ng-zorro-antd/card';
+
+import { ShareModule } from '@zeppelin/share';
+
+import { NotebookSearchRoutingModule } from './notebook-search-routing.module';
+import { NotebookSearchComponent } from './notebook-search.component';
+import { NotebookSearchResultItemComponent } from './result-item/result-item.component';
+
+@NgModule({
+ declarations: [NotebookSearchComponent, NotebookSearchResultItemComponent],
+ imports: [CommonModule, NotebookSearchRoutingModule, ShareModule, NzCardModule, FormsModule]
+})
+export class NotebookSearchModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.html
new file mode 100644
index 00000000000..14d6ee6bacc
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.html
@@ -0,0 +1,22 @@
+
+
+
+
+ {{displayName}}
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.less
new file mode 100644
index 00000000000..cb24d4e47b3
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.less
@@ -0,0 +1,19 @@
+/*
+ * Licensed 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.
+ */
+
+::ng-deep {
+ .monaco-editor {
+ .mark {
+ background: #fdf733;
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.ts
new file mode 100644
index 00000000000..67eabe6406e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.ts
@@ -0,0 +1,173 @@
+/*
+ * Licensed 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 {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ Input,
+ NgZone,
+ OnChanges,
+ OnDestroy,
+ SimpleChanges
+} from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { NotebookSearchResultItem } from '@zeppelin/interfaces';
+import { getKeywordPositions, KeywordPosition } from '@zeppelin/utility/get-keyword-positions';
+import { editor, Range } from 'monaco-editor';
+import IEditor = editor.IEditor;
+import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
+
+@Component({
+ selector: 'zeppelin-notebook-search-result-item',
+ templateUrl: './result-item.component.html',
+ styleUrls: ['./result-item.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookSearchResultItemComponent implements OnChanges, OnDestroy {
+ @Input() result: NotebookSearchResultItem;
+ queryParams = {};
+ displayName = '';
+ routerLink = [];
+ mergedStr: string;
+ keywords: string[] = [];
+ highlightPositions: KeywordPosition[] = [];
+ editor: IStandaloneCodeEditor;
+ height = 0;
+ decorations: string[] = [];
+ editorOption = {
+ readOnly: true,
+ fontSize: 12,
+ renderLineHighlight: 'none',
+ minimap: { enabled: false },
+ lineNumbers: 'off',
+ glyphMargin: false,
+ scrollBeyondLastLine: false,
+ contextmenu: false
+ };
+
+ constructor(private ngZone: NgZone, private cdr: ChangeDetectorRef, private router: ActivatedRoute) {}
+
+ setDisplayNameAndRouterLink(): void {
+ const term = this.router.snapshot.params.queryStr;
+ const listOfId = this.result.id.split('/');
+ const [noteId, hasParagraph, paragraph] = listOfId;
+ if (!hasParagraph) {
+ this.routerLink = ['/', 'notebook', this.result.id];
+ this.queryParams = {};
+ } else {
+ this.routerLink = ['/', 'notebook', noteId];
+ this.queryParams = {
+ paragraph,
+ term
+ };
+ }
+ this.displayName = this.result.name ? this.result.name : `Note ${noteId}`;
+ }
+
+ setHighlightKeyword(): void {
+ let mergedStr = this.result.header ? `${this.result.header}\n\n${this.result.snippet}` : this.result.snippet;
+
+ const regexp = /(.+?)<\/B>/g;
+ const matches = [];
+ let match = regexp.exec(mergedStr);
+
+ while (match !== null) {
+ if (match[1]) {
+ matches.push(match[1].toLocaleLowerCase());
+ }
+ match = regexp.exec(mergedStr);
+ }
+
+ mergedStr = mergedStr.replace(regexp, '$1');
+ this.mergedStr = mergedStr;
+ const keywords = [...new Set(matches)];
+ this.highlightPositions = getKeywordPositions(keywords, mergedStr);
+ }
+
+ applyHighlight() {
+ if (this.editor) {
+ this.decorations = this.editor.deltaDecorations(
+ this.decorations,
+ this.highlightPositions.map(highlight => {
+ const line = highlight.line + 1;
+ const character = highlight.character + 1;
+ return {
+ range: new Range(line, character, line, character + highlight.length),
+ options: {
+ className: 'mark',
+ stickiness: 1
+ }
+ };
+ })
+ );
+ this.cdr.markForCheck();
+ }
+ }
+
+ setLanguage() {
+ const editorModes = {
+ scala: /^%(\w*\.)?(spark|flink)/,
+ python: /^%(\w*\.)?(pyspark|python)/,
+ html: /^%(\w*\.)?(angular|ng)/,
+ r: /^%(\w*\.)?(r|sparkr|knitr)/,
+ sql: /^%(\w*\.)?\wql/,
+ yaml: /^%(\w*\.)?\wconf/,
+ markdown: /^%md/,
+ shell: /^%sh/
+ };
+ let mode = 'text';
+ const model = this.editor.getModel();
+ const keys = Object.keys(editorModes);
+ for (let i = 0; i < keys.length; i++) {
+ if (editorModes[keys[i]].test(this.result.snippet)) {
+ mode = keys[i];
+ break;
+ }
+ }
+ editor.setModelLanguage(model, mode);
+ }
+
+ autoAdjustEditorHeight() {
+ this.ngZone.run(() => {
+ setTimeout(() => {
+ if (this.editor) {
+ this.height =
+ this.editor.getTopForLineNumber(Number.MAX_SAFE_INTEGER) + this.editor.getConfiguration().lineHeight * 2;
+ this.editor.layout();
+ this.cdr.markForCheck();
+ }
+ });
+ });
+ }
+
+ initializedEditor(editorInstance: IEditor) {
+ this.editor = editorInstance as IStandaloneCodeEditor;
+ this.editor.setValue(this.mergedStr);
+ this.setLanguage();
+ this.autoAdjustEditorHeight();
+ this.applyHighlight();
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes.result) {
+ this.setDisplayNameAndRouterLink();
+ this.setHighlightKeyword();
+ this.autoAdjustEditorHeight();
+ this.applyHighlight();
+ }
+ }
+
+ ngOnDestroy(): void {
+ this.editor.dispose();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.html
new file mode 100644
index 00000000000..1da21e63a63
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/action-bar/action-bar.component.html
@@ -0,0 +1,239 @@
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.less
new file mode 100644
index 00000000000..c51b70e61a6
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.less
@@ -0,0 +1,50 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ .add-paragraph {
+ height: 32px;
+ text-align: center;
+ margin: -12px 0;
+ color: @primary-color;
+ font-weight: 500;
+ position: relative;;
+
+ .inner {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 10;
+ display: none;
+ line-height: 30px;
+ background: @blue-1;
+ border: 1px solid @border-color-split;
+ box-shadow: @btn-shadow;
+ padding: 0 12px;
+
+ &.disabled {
+ cursor: default;
+ color: @disabled-color;
+ }
+ }
+
+ &:hover {
+ .inner {
+ display: block;
+ }
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.ts
new file mode 100644
index 00000000000..bb77a832979
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/add-paragraph/add-paragraph.component.ts
@@ -0,0 +1,34 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+
+@Component({
+ selector: 'zeppelin-notebook-add-paragraph',
+ templateUrl: './add-paragraph.component.html',
+ styleUrls: ['./add-paragraph.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookAddParagraphComponent implements OnInit {
+ @Output() readonly addParagraph = new EventEmitter();
+ @Input() disabled = false;
+
+ clickAdd() {
+ if (!this.disabled) {
+ this.addParagraph.emit();
+ }
+ }
+
+ constructor() {}
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.html
new file mode 100644
index 00000000000..263390cbce6
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.html
@@ -0,0 +1,55 @@
+
+
+
+
+
Settings
+
+
+
+
Interpreter binding
+
+ Bind interpreter for this note.
+ Click to Bind/Unbind interpreter.
+ Drag and drop to reorder interpreters.
+ The first interpreter on the list becomes default. To create/remove interpreters, go to
+ Interpreter
+ menu.
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.less
new file mode 100644
index 00000000000..fcaea0a913f
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.less
@@ -0,0 +1,30 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ .interpreter-binding {
+ .interpreter-list {
+ background: @layout-body-background;
+ padding: 12px;
+ }
+
+ .submit-interpreter {
+ margin-top: 12px;
+
+ button {
+ margin-right: 12px;
+ }
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.ts
new file mode 100644
index 00000000000..34a9990e829
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/interpreter-binding/interpreter-binding.component.ts
@@ -0,0 +1,81 @@
+/*
+ * Licensed 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 { moveItemInArray, CdkDragDrop } from '@angular/cdk/drag-drop';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core';
+
+import { NzModalService } from 'ng-zorro-antd/modal';
+
+import { InterpreterBindingItem } from '@zeppelin/sdk';
+import { InterpreterService, MessageService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-notebook-interpreter-binding',
+ templateUrl: './interpreter-binding.component.html',
+ styleUrls: ['./interpreter-binding.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookInterpreterBindingComponent {
+ private restarting = false;
+ @Input() noteId: string;
+ @Input() interpreterBindings: InterpreterBindingItem[] = [];
+ @Input() activatedExtension: 'interpreter' | 'permissions' | 'revisions' | 'hide' = 'hide';
+ @Output() readonly activatedExtensionChange = new EventEmitter<
+ 'interpreter' | 'permissions' | 'revisions' | 'hide'
+ >();
+
+ restartInterpreter(interpreter: InterpreterBindingItem) {
+ this.nzModalService.create({
+ nzTitle: 'Restart interpreter',
+ nzContent: `Do you want to restart ${interpreter.name} interpreter?`,
+ nzOkLoading: this.restarting,
+ nzOnOk: () =>
+ new Promise(resolve => {
+ this.restarting = true;
+ this.interpreterService.restartInterpreter(interpreter.id, this.noteId).subscribe(
+ data => {
+ this.restarting = false;
+ this.cdr.markForCheck();
+ resolve();
+ },
+ () => {
+ this.restarting = false;
+ resolve();
+ }
+ );
+ })
+ });
+ }
+
+ drop(event: CdkDragDrop) {
+ moveItemInArray(this.interpreterBindings, event.previousIndex, event.currentIndex);
+ }
+
+ saveSetting() {
+ const selectedSettingIds = this.interpreterBindings.filter(item => item.selected).map(item => item.id);
+ this.messageService.saveInterpreterBindings(this.noteId, selectedSettingIds);
+ this.messageService.getInterpreterBindings(this.noteId);
+ this.closeSetting();
+ }
+
+ closeSetting() {
+ this.activatedExtension = 'hide';
+ this.activatedExtensionChange.emit('hide');
+ }
+
+ constructor(
+ private nzModalService: NzModalService,
+ private interpreterService: InterpreterService,
+ private cdr: ChangeDetectorRef,
+ private messageService: MessageService
+ ) {}
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.html
new file mode 100644
index 00000000000..2ca58518f71
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.less
new file mode 100644
index 00000000000..896b6f8dabf
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.less
@@ -0,0 +1,27 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+:host {
+ display: block;
+ padding: 0 4px;
+}
+
+.themeMixin({
+ .forms-wrap {
+ background: @component-background;
+ border: 1px solid @border-color-split;
+ box-shadow: @card-shadow;
+ padding: 12px;
+ position: relative;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.ts
new file mode 100644
index 00000000000..9eb0b721403
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/note-form-block/note-form-block.component.ts
@@ -0,0 +1,43 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { DynamicForms, DynamicFormParams } from '@zeppelin/sdk';
+
+@Component({
+ selector: 'zeppelin-note-form-block',
+ templateUrl: './note-form-block.component.html',
+ styleUrls: ['./note-form-block.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NoteFormBlockComponent implements OnInit {
+ @Input() noteTitle: string;
+ @Input() formDefs: DynamicForms;
+ @Input() paramDefs: DynamicFormParams;
+ @Output() readonly noteTitleChange = new EventEmitter();
+ @Output() readonly noteFormChange = new EventEmitter();
+ @Output() readonly noteFormNameRemove = new EventEmitter();
+ constructor() {}
+
+ ngOnInit() {}
+
+ onFormRemove({ name }) {
+ this.noteFormNameRemove.emit(name);
+ }
+
+ onFormChange() {
+ this.noteFormChange.emit(this.paramDefs);
+ }
+
+ setTitle(title: string) {
+ this.noteTitleChange.emit(title);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts
new file mode 100644
index 00000000000..321b788e74f
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook-routing.module.ts
@@ -0,0 +1,33 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+import { NotebookComponent } from './notebook.component';
+
+const routes: Routes = [
+ {
+ path: ':noteId',
+ component: NotebookComponent
+ },
+ {
+ path: ':noteId/revision/:revisionId',
+ component: NotebookComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class NotebookRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html
new file mode 100644
index 00000000000..b204c867388
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.less
new file mode 100644
index 00000000000..582bbb74910
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.less
@@ -0,0 +1,36 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+
+.themeMixin({
+ .notebook-container {
+ background: @layout-body-background;
+ display: block;
+ position: relative;
+ padding-top: 50px;
+ min-height: calc(~"100vh - 50px");
+
+ &.simple {
+ background: @component-background;
+ }
+ }
+ .extension-area {
+ padding: 24px;
+ background: @component-background;
+ box-shadow: -2px 4px 2px 0 rgba(0, 0, 0, 0.06);
+ }
+ .paragraph-area {
+ margin: 6px 0 0 0;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
new file mode 100644
index 00000000000..9a0ecc9a26e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.ts
@@ -0,0 +1,401 @@
+/*
+ * Licensed 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 {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ OnDestroy,
+ OnInit,
+ QueryList,
+ ViewChildren
+} from '@angular/core';
+import { ActivatedRoute, Router } from '@angular/router';
+import { isNil } from 'lodash';
+import { Subject } from 'rxjs';
+import { distinctUntilKeyChanged, map, startWith, takeUntil } from 'rxjs/operators';
+
+import { MessageListener, MessageListenersManager } from '@zeppelin/core';
+import { Permissions } from '@zeppelin/interfaces';
+import {
+ DynamicFormParams,
+ InterpreterBindingItem,
+ MessageReceiveDataTypeMap,
+ Note,
+ OP,
+ RevisionListItem
+} from '@zeppelin/sdk';
+import {
+ MessageService,
+ NgZService,
+ NoteStatusService,
+ NoteVarShareService,
+ SecurityService,
+ TicketService
+} from '@zeppelin/services';
+
+import { scrollIntoViewIfNeeded } from '@zeppelin/utility/element';
+import { NotebookParagraphComponent } from './paragraph/paragraph.component';
+
+@Component({
+ selector: 'zeppelin-notebook',
+ templateUrl: './notebook.component.html',
+ styleUrls: ['./notebook.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookComponent extends MessageListenersManager implements OnInit, OnDestroy {
+ @ViewChildren(NotebookParagraphComponent) listOfNotebookParagraphComponent: QueryList;
+ private destroy$ = new Subject();
+ note: Note['note'];
+ permissions: Permissions;
+ selectId: string | null = null;
+ scrolledId: string | null = null;
+ isOwner = true;
+ noteRevisions: RevisionListItem[] = [];
+ currentRevision: string;
+ collaborativeMode = false;
+ revisionView = false;
+ collaborativeModeUsers = [];
+ isNoteDirty = false;
+ isShowNoteForms = false;
+ saveTimer = null;
+ interpreterBindings: InterpreterBindingItem[] = [];
+ activatedExtension: 'interpreter' | 'permissions' | 'revisions' | 'hide' = 'hide';
+
+ @MessageListener(OP.NOTE)
+ getNote(data: MessageReceiveDataTypeMap[OP.NOTE]) {
+ const note = data.note;
+ if (isNil(note)) {
+ this.router.navigate(['/']).then();
+ } else {
+ this.removeParagraphFromNgZ();
+ this.note = note;
+ const { paragraphId } = this.activatedRoute.snapshot.params;
+ if (paragraphId) {
+ this.note = this.cleanParagraphExcept(paragraphId);
+ this.initializeLookAndFeel();
+ } else {
+ this.initializeLookAndFeel();
+ this.getInterpreterBindings();
+ this.getPermissions();
+ this.note.config.personalizedMode =
+ this.note.config.personalizedMode === undefined ? 'false' : this.note.config.personalizedMode;
+ }
+ if (this.note.noteForms && this.note.noteParams) {
+ this.saveNoteForms({
+ formsData: {
+ forms: this.note.noteForms,
+ params: this.note.noteParams
+ }
+ });
+ }
+ this.cdr.markForCheck();
+ }
+ }
+
+ @MessageListener(OP.INTERPRETER_BINDINGS)
+ loadInterpreterBindings(data: MessageReceiveDataTypeMap[OP.INTERPRETER_BINDINGS]) {
+ this.interpreterBindings = data.interpreterBindings;
+ if (!this.interpreterBindings.some(item => item.selected)) {
+ this.activatedExtension = 'interpreter';
+ }
+ this.cdr.markForCheck();
+ }
+
+ @MessageListener(OP.PARAGRAPH_REMOVED)
+ removeParagraph(data: MessageReceiveDataTypeMap[OP.PARAGRAPH_REMOVED]) {
+ const { paragraphId } = this.activatedRoute.snapshot.params;
+ if (paragraphId || this.revisionView) {
+ return;
+ }
+ this.note.paragraphs = this.note.paragraphs.filter(p => p.id !== data.id);
+ this.cdr.markForCheck();
+ }
+
+ @MessageListener(OP.PARAGRAPH_ADDED)
+ addParagraph(data: MessageReceiveDataTypeMap[OP.PARAGRAPH_ADDED]) {
+ const { paragraphId } = this.activatedRoute.snapshot.params;
+ if (paragraphId || this.revisionView) {
+ return;
+ }
+ this.note.paragraphs.splice(data.index, 0, data.paragraph).map(p => {
+ return {
+ ...p,
+ focus: p.id === data.paragraph.id
+ };
+ });
+ this.note.paragraphs = [...this.note.paragraphs];
+ this.cdr.markForCheck();
+ // TODO(hsuanxyz) focus on paragraph
+ }
+
+ @MessageListener(OP.SAVE_NOTE_FORMS)
+ saveNoteForms(data: MessageReceiveDataTypeMap[OP.SAVE_NOTE_FORMS]) {
+ this.note.noteForms = data.formsData.forms;
+ this.note.noteParams = data.formsData.params;
+ this.setNoteFormsStatus();
+ }
+
+ @MessageListener(OP.NOTE_REVISION)
+ getNoteRevision(data: MessageReceiveDataTypeMap[OP.NOTE_REVISION]) {
+ const note = data.note;
+ if (isNil(note)) {
+ this.router.navigate(['/']).then();
+ } else {
+ this.note = data.note;
+ this.initializeLookAndFeel();
+ this.cdr.markForCheck();
+ }
+ }
+
+ @MessageListener(OP.SET_NOTE_REVISION)
+ setNoteRevision() {
+ const { noteId } = this.activatedRoute.snapshot.params;
+ this.router.navigate(['/notebook', noteId]).then();
+ }
+
+ @MessageListener(OP.PARAGRAPH_MOVED)
+ moveParagraph(data: MessageReceiveDataTypeMap[OP.PARAGRAPH_MOVED]) {
+ if (!this.revisionView) {
+ const movedPara = this.note.paragraphs.find(p => p.id === data.id);
+ if (movedPara) {
+ const listOfRestPara = this.note.paragraphs.filter(p => p.id !== data.id);
+ this.note.paragraphs = [...listOfRestPara.slice(0, data.index), movedPara, ...listOfRestPara.slice(data.index)];
+ const paragraphComponent = this.listOfNotebookParagraphComponent.find(e => e.paragraph.id === data.id);
+ this.cdr.markForCheck();
+ if (paragraphComponent) {
+ // Call when next tick
+ setTimeout(() => {
+ scrollIntoViewIfNeeded(paragraphComponent.getElement());
+ });
+ }
+ }
+ }
+ }
+
+ @MessageListener(OP.COLLABORATIVE_MODE_STATUS)
+ getCollaborativeModeStatus(data: MessageReceiveDataTypeMap[OP.COLLABORATIVE_MODE_STATUS]) {
+ this.collaborativeMode = Boolean(data.status);
+ this.collaborativeModeUsers = data.users;
+ this.cdr.markForCheck();
+ }
+
+ @MessageListener(OP.PATCH_PARAGRAPH)
+ patchParagraph() {
+ this.collaborativeMode = true;
+ this.cdr.markForCheck();
+ }
+
+ @MessageListener(OP.NOTE_UPDATED)
+ noteUpdated(data: MessageReceiveDataTypeMap[OP.NOTE_UPDATED]) {
+ if (data.name !== this.note.name) {
+ this.note.name = data.name;
+ }
+ this.note.config = data.config;
+ this.note.info = data.info;
+ this.initializeLookAndFeel();
+ this.cdr.markForCheck();
+ }
+
+ @MessageListener(OP.LIST_REVISION_HISTORY)
+ listRevisionHistory(data: MessageReceiveDataTypeMap[OP.LIST_REVISION_HISTORY]) {
+ this.noteRevisions = data.revisionList;
+ if (this.noteRevisions) {
+ if (this.noteRevisions.length === 0 || this.noteRevisions[0].id !== 'Head') {
+ this.noteRevisions.splice(0, 0, { id: 'Head', message: 'Head' });
+ }
+ const { revisionId } = this.activatedRoute.snapshot.params;
+ if (revisionId) {
+ this.currentRevision = this.noteRevisions.find(r => r.id === revisionId).message;
+ } else {
+ this.currentRevision = 'Head';
+ }
+ }
+ this.cdr.markForCheck();
+ }
+
+ saveParagraph(id: string) {
+ this.listOfNotebookParagraphComponent
+ .toArray()
+ .find(p => p.paragraph.id === id)
+ .saveParagraph();
+ }
+
+ killSaveTimer() {
+ if (this.saveTimer) {
+ clearTimeout(this.saveTimer);
+ this.saveTimer = null;
+ }
+ }
+
+ startSaveTimer() {
+ this.killSaveTimer();
+ this.isNoteDirty = true;
+ this.saveTimer = setTimeout(() => {
+ this.saveNote();
+ }, 10000);
+ }
+
+ onParagraphSelect(id: string) {
+ this.selectId = id;
+ }
+
+ onParagraphScrolled(id: string) {
+ this.scrolledId = id;
+ }
+
+ onSelectAtIndex(index: number) {
+ const scopeIndex = Math.min(this.note.paragraphs.length, Math.max(0, index));
+ if (this.note.paragraphs[scopeIndex]) {
+ this.selectId = this.note.paragraphs[scopeIndex].id;
+ }
+ }
+
+ saveNote() {
+ if (this.note && this.note.paragraphs && this.listOfNotebookParagraphComponent) {
+ this.listOfNotebookParagraphComponent.toArray().forEach(p => {
+ p.saveParagraph();
+ });
+ this.isNoteDirty = null;
+ this.cdr.markForCheck();
+ }
+ }
+
+ getInterpreterBindings() {
+ this.messageService.getInterpreterBindings(this.note.id);
+ }
+
+ getPermissions() {
+ this.securityService.getPermissions(this.note.id).subscribe(data => {
+ this.permissions = data;
+ this.isOwner = !(
+ this.permissions.owners.length && this.permissions.owners.indexOf(this.ticketService.ticket.principal) < 0
+ );
+ this.cdr.markForCheck();
+ });
+ }
+
+ get viewOnly(): boolean {
+ return this.noteStatusService.viewOnly(this.note);
+ }
+
+ initializeLookAndFeel() {
+ this.note.config.looknfeel = this.note.config.looknfeel || 'default';
+ if (this.note.paragraphs && this.note.paragraphs[0]) {
+ this.note.paragraphs[0].focus = true;
+ }
+ }
+
+ cleanParagraphExcept(paragraphId) {
+ const targetParagraph = this.note.paragraphs.find(p => p.id === paragraphId);
+ const config = targetParagraph.config || {};
+ config.editorHide = true;
+ config.tableHide = false;
+ const paragraphs = [{ ...targetParagraph, config }];
+ return { ...this.note, paragraphs };
+ }
+
+ setAllParagraphTableHide(tableHide: boolean) {
+ this.listOfNotebookParagraphComponent.forEach(p => p.setTableHide(tableHide));
+ }
+
+ setAllParagraphEditorHide(editorHide: boolean) {
+ this.listOfNotebookParagraphComponent.forEach(p => p.setEditorHide(editorHide));
+ }
+
+ onNoteFormChange(noteParams: DynamicFormParams) {
+ this.messageService.saveNoteForms({
+ noteParams,
+ id: this.note.id
+ });
+ }
+
+ onFormNameRemove(formName: string) {
+ this.messageService.removeNoteForms(this.note, formName);
+ }
+
+ onNoteTitleChange(noteFormTitle: string) {
+ this.messageService.updateNote(this.note.id, this.note.name, {
+ ...this.note.config,
+ noteFormTitle
+ });
+ }
+
+ setNoteFormsStatus() {
+ this.isShowNoteForms = this.note && this.note.noteForms && Object.keys(this.note.noteForms).length !== 0;
+ this.cdr.markForCheck();
+ }
+
+ constructor(
+ private activatedRoute: ActivatedRoute,
+ public messageService: MessageService,
+ private cdr: ChangeDetectorRef,
+ private noteStatusService: NoteStatusService,
+ private noteVarShareService: NoteVarShareService,
+ private ticketService: TicketService,
+ private securityService: SecurityService,
+ private router: Router,
+ protected ngZService: NgZService
+ ) {
+ super(messageService);
+ }
+
+ ngOnInit() {
+ this.activatedRoute.queryParamMap
+ .pipe(
+ startWith(this.activatedRoute.snapshot.queryParamMap),
+ takeUntil(this.destroy$),
+ map(data => data.get('paragraph'))
+ )
+ .subscribe(id => {
+ this.onParagraphSelect(id);
+ this.onParagraphScrolled(id);
+ });
+ this.activatedRoute.params
+ .pipe(
+ takeUntil(this.destroy$),
+ distinctUntilKeyChanged('noteId')
+ )
+ .subscribe(() => {
+ this.noteVarShareService.clear();
+ });
+ this.activatedRoute.params.pipe(takeUntil(this.destroy$)).subscribe(param => {
+ const { noteId, revisionId } = param;
+ if (revisionId) {
+ this.messageService.noteRevision(noteId, revisionId);
+ } else {
+ this.messageService.getNote(noteId);
+ }
+ this.revisionView = !!revisionId;
+ this.cdr.markForCheck();
+ this.messageService.listRevisionHistory(noteId);
+ // TODO(hsuanxyz) scroll to current paragraph
+ });
+ this.revisionView = !!this.activatedRoute.snapshot.params.revisionId;
+ }
+
+ removeParagraphFromNgZ(): void {
+ if (this.note && Array.isArray(this.note.paragraphs)) {
+ this.note.paragraphs.forEach(p => {
+ this.ngZService.removeParagraph(p.id);
+ });
+ }
+ }
+
+ ngOnDestroy(): void {
+ super.ngOnDestroy();
+ this.killSaveTimer();
+ this.saveNote();
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts
new file mode 100644
index 00000000000..49a6230e22c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts
@@ -0,0 +1,100 @@
+/*
+ * Licensed 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 { DragDropModule } from '@angular/cdk/drag-drop';
+import { PortalModule } from '@angular/cdk/portal';
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzCodeEditorModule } from 'ng-zorro-antd/code-editor';
+import { NzNoAnimationModule } from 'ng-zorro-antd/core';
+import { NzDividerModule } from 'ng-zorro-antd/divider';
+import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
+import { NzFormModule } from 'ng-zorro-antd/form';
+import { NzGridModule } from 'ng-zorro-antd/grid';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzInputModule } from 'ng-zorro-antd/input';
+import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
+import { NzPopoverModule } from 'ng-zorro-antd/popover';
+import { NzProgressModule } from 'ng-zorro-antd/progress';
+import { NzRadioModule } from 'ng-zorro-antd/radio';
+import { NzSelectModule } from 'ng-zorro-antd/select';
+import { NzSwitchModule } from 'ng-zorro-antd/switch';
+import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
+
+import { ShareModule } from '@zeppelin/share';
+
+import { NotebookAddParagraphComponent } from './add-paragraph/add-paragraph.component';
+import { NotebookInterpreterBindingComponent } from './interpreter-binding/interpreter-binding.component';
+import { NotebookParagraphCodeEditorComponent } from './paragraph/code-editor/code-editor.component';
+import { NotebookParagraphControlComponent } from './paragraph/control/control.component';
+import { NotebookParagraphFooterComponent } from './paragraph/footer/footer.component';
+import { NotebookParagraphComponent } from './paragraph/paragraph.component';
+import { NotebookParagraphProgressComponent } from './paragraph/progress/progress.component';
+import { NotebookPermissionsComponent } from './permissions/permissions.component';
+import { NotebookRevisionsComparatorComponent } from './revisions-comparator/revisions-comparator.component';
+
+import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
+import { WorkspaceShareModule } from '../../workspace/share/share.module';
+import { NotebookActionBarComponent } from './action-bar/action-bar.component';
+import { NoteFormBlockComponent } from './note-form-block/note-form-block.component';
+import { NotebookRoutingModule } from './notebook-routing.module';
+import { NotebookComponent } from './notebook.component';
+import { NotebookShareModule } from './share/share.module';
+
+@NgModule({
+ declarations: [
+ NotebookComponent,
+ NotebookActionBarComponent,
+ NotebookInterpreterBindingComponent,
+ NotebookPermissionsComponent,
+ NotebookRevisionsComparatorComponent,
+ NotebookParagraphComponent,
+ NotebookAddParagraphComponent,
+ NotebookParagraphCodeEditorComponent,
+ NotebookParagraphProgressComponent,
+ NotebookParagraphFooterComponent,
+ NotebookParagraphControlComponent,
+ NoteFormBlockComponent
+ ],
+ imports: [
+ CommonModule,
+ PortalModule,
+ WorkspaceShareModule,
+ NotebookRoutingModule,
+ ShareModule,
+ NotebookShareModule,
+ NzButtonModule,
+ NzIconModule,
+ NzDropDownModule,
+ NzNoAnimationModule,
+ NzToolTipModule,
+ NzPopconfirmModule,
+ NzFormModule,
+ NzPopoverModule,
+ NzInputModule,
+ FormsModule,
+ ReactiveFormsModule,
+ NzDividerModule,
+ NzProgressModule,
+ NzSwitchModule,
+ NzSelectModule,
+ NzGridModule,
+ NzRadioModule,
+ DragDropModule,
+ NzCodeEditorModule,
+ NzCheckboxModule
+ ]
+})
+export class NotebookModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.html
new file mode 100644
index 00000000000..d543e670d49
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.html
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.less
new file mode 100644
index 00000000000..8f61bd5b93f
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.less
@@ -0,0 +1,35 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+:host {
+ display: block;
+}
+
+.themeMixin({
+
+ zeppelin-code-editor {
+ display: block;
+ border-left: 4px solid @border-color-split;
+ overflow: hidden;
+
+ &.dirty {
+ border-left-color: @warning-color;
+ }
+
+ &.focused:not(.dirty) {
+ border-left-color: @primary-color;
+ }
+ }
+
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts
new file mode 100644
index 00000000000..99cc97d14b6
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts
@@ -0,0 +1,232 @@
+/*
+ * Licensed 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 {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ NgZone,
+ OnChanges,
+ OnDestroy,
+ Output,
+ SimpleChanges
+} from '@angular/core';
+
+import { editor as MonacoEditor, IDisposable } from 'monaco-editor';
+import IStandaloneCodeEditor = MonacoEditor.IStandaloneCodeEditor;
+import IEditor = monaco.editor.IEditor;
+
+import { InterpreterBindingItem } from '@zeppelin/sdk';
+import { CompletionService, MessageService } from '@zeppelin/services';
+
+import { pt2px } from '@zeppelin/utility/css-unit-conversion';
+import { NotebookParagraphControlComponent } from '../control/control.component';
+
+@Component({
+ selector: 'zeppelin-notebook-paragraph-code-editor',
+ templateUrl: './code-editor.component.html',
+ styleUrls: ['./code-editor.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestroy, AfterViewInit {
+ // TODO(hsuanxyz):
+ // 1. cursor position
+ @Input() readOnly = false;
+ @Input() language = 'text';
+ @Input() paragraphControl: NotebookParagraphControlComponent;
+ @Input() lineNumbers = false;
+ @Input() focus = false;
+ @Input() collaborativeMode = false;
+ @Input() text: string;
+ @Input() fontSize: number;
+ @Input() dirty = false;
+ @Input() interpreterBindings: InterpreterBindingItem[] = [];
+ @Input() pid: string;
+ @Output() readonly textChanged = new EventEmitter();
+ @Output() readonly editorBlur = new EventEmitter();
+ @Output() readonly editorFocus = new EventEmitter();
+ private editor: IStandaloneCodeEditor;
+ private monacoDisposables: IDisposable[] = [];
+ height = 0;
+ interpreterName: string;
+
+ autoAdjustEditorHeight() {
+ if (this.editor) {
+ this.ngZone.run(() => {
+ this.height =
+ this.editor.getTopForLineNumber(Number.MAX_SAFE_INTEGER) + this.editor.getConfiguration().lineHeight * 2;
+ this.editor.layout();
+ this.cdr.markForCheck();
+ });
+ }
+ }
+
+ initEditorListener() {
+ const editor = this.editor;
+ this.monacoDisposables.push(
+ editor.onDidFocusEditorText(() => {
+ this.editorFocus.emit();
+ }),
+ editor.onDidBlurEditorText(() => {
+ this.editorBlur.emit();
+ }),
+
+ editor.onDidChangeModelContent(() => {
+ this.ngZone.run(() => {
+ this.text = editor.getModel().getValue();
+ this.textChanged.emit(this.text);
+ this.setParagraphMode(true);
+ this.autoAdjustEditorHeight();
+ setTimeout(() => {
+ this.autoAdjustEditorHeight();
+ });
+ });
+ })
+ );
+ }
+
+ setEditorValue() {
+ if (this.editor && this.editor.getModel() && this.editor.getModel().getValue() !== this.text) {
+ this.editor.getModel().setValue(this.text || '');
+ }
+ }
+
+ initializedEditor(editor: IEditor) {
+ this.editor = editor as IStandaloneCodeEditor;
+ this.editor.addCommand(
+ monaco.KeyCode.Escape,
+ () => {
+ if (document.activeElement instanceof HTMLElement) {
+ document.activeElement.blur();
+ }
+ },
+ '!suggestWidgetVisible'
+ );
+
+ this.updateEditorOptions();
+ this.setParagraphMode();
+ this.initEditorListener();
+ this.initEditorFocus();
+ this.initCompletionService();
+ this.setEditorValue();
+ setTimeout(() => {
+ this.autoAdjustEditorHeight();
+ });
+ }
+
+ initCompletionService(): void {
+ this.completionService.registerAsCompletionReceiver(this.editor.getModel(), this.paragraphControl.pid);
+ }
+
+ initEditorFocus() {
+ if (this.focus && this.editor) {
+ this.editor.focus();
+ }
+ }
+
+ updateEditorOptions() {
+ if (this.editor) {
+ this.editor.updateOptions({
+ readOnly: this.readOnly,
+ fontSize: pt2px(this.fontSize),
+ renderLineHighlight: this.focus ? 'all' : 'none',
+ minimap: { enabled: false },
+ lineNumbers: this.lineNumbers ? 'on' : 'off',
+ glyphMargin: false,
+ folding: false,
+ scrollBeyondLastLine: false,
+ contextmenu: false,
+ matchBrackets: false
+ });
+ }
+ }
+
+ getInterpreterName(paragraphText: string) {
+ const match = /^\s*%(.+?)(\s|\()/g.exec(paragraphText);
+ if (match) {
+ return match[1].trim();
+ // get default interpreter name if paragraph text doesn't start with '%'
+ // TODO(hsuanxyz): dig into the cause what makes interpreterBindings to have no element
+ } else if (this.interpreterBindings && this.interpreterBindings.length !== 0) {
+ return this.interpreterBindings[0].name;
+ }
+ return '';
+ }
+
+ setParagraphMode(changed = false) {
+ if (this.editor && !changed) {
+ const model = this.editor.getModel();
+ if (this.language) {
+ // TODO(hsuanxyz): config convertMap
+ const convertMap = {
+ sh: 'shell'
+ };
+ monaco.editor.setModelLanguage(model, convertMap[this.language] || this.language);
+ }
+ } else {
+ const interpreterName = this.getInterpreterName(this.text);
+ if (this.interpreterName !== interpreterName) {
+ this.interpreterName = interpreterName;
+ this.getEditorSetting(interpreterName);
+ }
+ }
+ }
+
+ getEditorSetting(interpreterName: string) {
+ this.messageService.editorSetting(this.pid, interpreterName);
+ }
+
+ layout() {
+ if (this.editor) {
+ setTimeout(() => {
+ this.editor.layout();
+ });
+ }
+ }
+
+ constructor(
+ private cdr: ChangeDetectorRef,
+ private ngZone: NgZone,
+ private messageService: MessageService,
+ private completionService: CompletionService
+ ) {}
+
+ ngOnChanges(changes: SimpleChanges): void {
+ const { text, interpreterBindings, language, readOnly, focus, lineNumbers, fontSize } = changes;
+ if (readOnly || focus || lineNumbers || fontSize) {
+ this.updateEditorOptions();
+ }
+ if (focus) {
+ this.initEditorFocus();
+ }
+ if (text) {
+ this.setEditorValue();
+ }
+
+ if (interpreterBindings || language) {
+ this.setParagraphMode();
+ }
+ if (text || fontSize) {
+ this.autoAdjustEditorHeight();
+ }
+ }
+
+ ngOnDestroy(): void {
+ this.completionService.unregister(this.editor.getModel());
+ this.monacoDisposables.forEach(d => d.dispose());
+ }
+
+ ngAfterViewInit(): void {}
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.html
new file mode 100644
index 00000000000..5ec5f52fccf
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.html
@@ -0,0 +1,85 @@
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.less
new file mode 100644
index 00000000000..700a6b666e8
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.less
@@ -0,0 +1,73 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+:host {
+ display: flex;
+}
+
+.list-item {
+ display: flex;
+ justify-content: space-between;
+}
+
+.setting-menu {
+ width: 270px;
+
+ li {
+ font-size: 12px;
+
+ i {
+ margin-right: 6px;
+ }
+ }
+}
+
+.short-cut {
+ opacity: 0.7;
+}
+
+.paragraph-id {
+ text-align: center;
+}
+
+.themeMixin({
+ .job-link {
+ margin-right: 12px;
+
+ a {
+ color: @link-color;
+ }
+ }
+ .status {
+ color: @text-color-secondary;
+ margin-right: 12px;
+ }
+ .progress {
+ color: @text-color-secondary;
+ margin-right: 12px;
+ }
+
+ a {
+ margin-left: 8px;
+ color: @text-color-secondary;
+
+ .run-para {
+ color: @primary-color;
+ }
+
+ .cancel-para {
+ color: @warning-color;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.ts
new file mode 100644
index 00000000000..846a46de1b9
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/control/control.component.ts
@@ -0,0 +1,279 @@
+/*
+ * Licensed 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 {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ OnChanges,
+ OnInit,
+ Output
+} from '@angular/core';
+import { copyTextToClipboard } from '@zeppelin/core';
+
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalService } from 'ng-zorro-antd/modal';
+
+import { ActivatedRoute } from '@angular/router';
+import { RuntimeInfos } from '@zeppelin/sdk';
+import { MessageService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-notebook-paragraph-control',
+ exportAs: 'paragraphControl',
+ templateUrl: './control.component.html',
+ styleUrls: ['./control.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookParagraphControlComponent implements OnInit, OnChanges {
+ @Input() status: string;
+ @Input() progress = 0;
+ @Input() revisionView = false;
+ @Input() enabled = true;
+ @Input() pid: string;
+ @Input() tableHide = false;
+ @Input() editorHide = false;
+ @Input() colWidth: number;
+ @Input() fontSize: number;
+ @Input() runOnSelectionChange = true;
+ @Input() isEntireNoteRunning = true;
+ @Input() runtimeInfos: RuntimeInfos;
+ @Input() colWidthOption = [];
+ @Input() first = false;
+ @Input() last = false;
+ @Input() title = false;
+ @Input() lineNumbers = false;
+ @Input() paragraphLength: number;
+ @Output() readonly colWidthChange = new EventEmitter();
+ @Output() readonly titleChange = new EventEmitter();
+ @Output() readonly enabledChange = new EventEmitter();
+ @Output() readonly fontSizeChange = new EventEmitter();
+ @Output() readonly tableHideChange = new EventEmitter();
+ @Output() readonly runParagraph = new EventEmitter();
+ @Output() readonly lineNumbersChange = new EventEmitter();
+ @Output() readonly cancelParagraph = new EventEmitter();
+ @Output() readonly editorHideChange = new EventEmitter();
+ @Output() readonly runOnSelectionChangeChange = new EventEmitter();
+ @Output() readonly moveUp = new EventEmitter();
+ @Output() readonly moveDown = new EventEmitter();
+ @Output() readonly insertNew = new EventEmitter();
+ @Output() readonly runAllAbove = new EventEmitter();
+ @Output() readonly runAllBelowAndCurrent = new EventEmitter();
+ @Output() readonly cloneParagraph = new EventEmitter();
+ @Output() readonly removeParagraph = new EventEmitter();
+ @Output() readonly openSingleParagraph = new EventEmitter();
+ fontSizeOption = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
+ dropdownVisible = false;
+ isMac = navigator.appVersion.indexOf('Mac') !== -1;
+ listOfMenu: Array<{
+ label: string;
+ show: boolean;
+ disabled: boolean;
+ icon: string;
+ shortCut: string;
+ trigger(): void;
+ }> = [];
+
+ updateListOfMenu() {
+ this.listOfMenu = [
+ {
+ label: 'Run',
+ show: !this.first,
+ disabled: this.isEntireNoteRunning,
+ icon: 'play-circle',
+ trigger: () => this.trigger(this.runParagraph),
+ shortCut: this.isMac ? '⇧+⌘+Enter' : 'Shift+Ctrl+Enter'
+ },
+ {
+ label: 'Run all above',
+ show: !this.first,
+ disabled: this.isEntireNoteRunning,
+ icon: 'up-square',
+ trigger: () => this.trigger(this.runAllAbove),
+ shortCut: this.isMac ? '⇧+⌘+Enter' : 'Shift+Ctrl+Enter'
+ },
+ {
+ label: 'Run all below',
+ show: !this.last,
+ disabled: this.isEntireNoteRunning,
+ icon: 'down-square',
+ trigger: () => this.trigger(this.runAllBelowAndCurrent),
+ shortCut: this.isMac ? '⇧+⌘+Enter' : 'Shift+Ctrl+Enter'
+ },
+ {
+ label: 'Link this paragraph',
+ show: true,
+ disabled: false,
+ icon: 'export',
+ trigger: () => {
+ this.openSingleParagraph.emit(this.pid);
+ },
+ shortCut: this.isMac ? '⌥+⌘+T' : 'Alt+Ctrl+T'
+ },
+ {
+ label: 'Clear output',
+ show: true,
+ disabled: this.isEntireNoteRunning,
+ icon: 'fire',
+ trigger: () => this.clearParagraphOutput(),
+ shortCut: this.isMac ? '⌥+⌘+L' : 'Alt+Ctrl+L'
+ },
+ {
+ label: 'Remove',
+ show: this.paragraphLength > 1,
+ disabled: this.isEntireNoteRunning,
+ icon: 'delete',
+ trigger: () => this.onRemoveParagraph(),
+ shortCut: this.isMac ? '⇧+Del (Command)' : 'Shift+Del (Command)'
+ },
+ {
+ label: 'Move up',
+ show: !this.first,
+ disabled: this.isEntireNoteRunning,
+ icon: 'up',
+ trigger: () => this.trigger(this.moveUp),
+ shortCut: `${this.isMac ? '⌘' : 'Ctrl'}+K (Command)`
+ },
+ {
+ label: 'Move down',
+ show: !this.last,
+ disabled: this.isEntireNoteRunning,
+ icon: 'down',
+ trigger: () => this.trigger(this.moveDown),
+ shortCut: `${this.isMac ? '⌘' : 'Ctrl'}+J (Command)`
+ },
+ {
+ label: 'Insert new',
+ show: true,
+ disabled: this.isEntireNoteRunning,
+ icon: 'plus',
+ trigger: () => this.trigger(this.insertNew),
+ shortCut: `B (Command)`
+ },
+ {
+ label: 'Clone paragraph',
+ show: true,
+ disabled: this.isEntireNoteRunning,
+ icon: 'copy',
+ trigger: () => this.trigger(this.cloneParagraph),
+ shortCut: `C (Command)`
+ },
+ {
+ label: this.title ? 'Hide Title' : 'Show Title',
+ show: true,
+ disabled: false,
+ icon: 'font-colors',
+ trigger: () => this.toggleTitle(),
+ shortCut: `T (Command)`
+ },
+ {
+ label: this.lineNumbers ? 'Hide line numbers' : 'Show line numbers',
+ show: true,
+ disabled: false,
+ icon: 'ordered-list',
+ trigger: () => this.toggleLineNumbers(),
+ shortCut: `L (Command)`
+ },
+ {
+ label: this.enabled ? 'Disable run' : 'Enable run',
+ show: true,
+ disabled: this.isEntireNoteRunning,
+ icon: 'api',
+ trigger: () => this.toggleEnabled(),
+ shortCut: `R (Command)`
+ }
+ ];
+ }
+
+ toggleEditor() {
+ this.editorHide = !this.editorHide;
+ this.editorHideChange.emit(this.editorHide);
+ }
+
+ toggleOutput() {
+ this.tableHide = !this.tableHide;
+ this.tableHideChange.emit(this.tableHide);
+ }
+
+ toggleRunOnSelectionChange() {
+ this.runOnSelectionChange = !this.runOnSelectionChange;
+ this.runOnSelectionChangeChange.emit(this.runOnSelectionChange);
+ }
+
+ toggleTitle() {
+ this.title = !this.title;
+ this.titleChange.emit(this.title);
+ }
+
+ toggleLineNumbers() {
+ this.lineNumbers = !this.lineNumbers;
+ this.lineNumbersChange.emit(this.lineNumbers);
+ }
+
+ toggleEnabled() {
+ if (!this.isEntireNoteRunning) {
+ this.enabled = !this.enabled;
+ this.enabledChange.emit(this.enabled);
+ }
+ }
+
+ changeColWidth(colWidth: number) {
+ this.colWidth = +colWidth;
+ this.colWidthChange.emit(this.colWidth);
+ this.dropdownVisible = false;
+ }
+
+ changeFontSize(fontSize: number) {
+ this.fontSize = +fontSize;
+ this.fontSizeChange.emit(this.fontSize);
+ }
+
+ copyClipboard(id: string) {
+ copyTextToClipboard(id);
+ this.nzMessageService.info('Paragraph id copied');
+ }
+
+ clearParagraphOutput() {
+ if (!this.isEntireNoteRunning) {
+ this.messageService.paragraphClearOutput(this.pid);
+ }
+ }
+
+ onRemoveParagraph() {
+ this.removeParagraph.emit();
+ }
+
+ trigger(event: EventEmitter) {
+ if (!this.isEntireNoteRunning) {
+ this.dropdownVisible = false;
+ event.emit();
+ }
+ }
+
+ constructor(
+ private cdr: ChangeDetectorRef,
+ private nzMessageService: NzMessageService,
+ private activatedRoute: ActivatedRoute,
+ private messageService: MessageService
+ ) {}
+
+ ngOnInit() {
+ this.updateListOfMenu();
+ }
+
+ ngOnChanges(): void {
+ this.updateListOfMenu();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.html
new file mode 100644
index 00000000000..677356b1d5d
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.html
@@ -0,0 +1,16 @@
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.less
new file mode 100644
index 00000000000..767a5491765
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.less
@@ -0,0 +1,21 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ .footer {
+ color: @text-color-secondary;
+ font-size: 12px;
+ margin-top: 12px;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.ts
new file mode 100644
index 00000000000..1be51ca9c31
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/footer/footer.component.ts
@@ -0,0 +1,74 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
+
+import * as distanceInWordsStrict from 'date-fns/distance_in_words_strict';
+import * as distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
+import * as format from 'date-fns/format';
+
+@Component({
+ selector: 'zeppelin-notebook-paragraph-footer',
+ templateUrl: './footer.component.html',
+ styleUrls: ['./footer.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookParagraphFooterComponent implements OnChanges {
+ @Input() dateStarted: string;
+ @Input() dateFinished: string;
+ @Input() dateUpdated: string;
+ @Input() showExecutionTime = false;
+ @Input() showElapsedTime = false;
+ @Input() user: string;
+ executionTime: string;
+ elapsedTime: string;
+
+ isResultOutdated() {
+ return this.dateUpdated !== undefined && Date.parse(this.dateUpdated) > Date.parse(this.dateStarted);
+ }
+
+ getExecutionTime() {
+ const end = this.dateFinished;
+ const start = this.dateStarted;
+ const timeMs = Date.parse(end) - Date.parse(start);
+ if (isNaN(timeMs) || timeMs < 0) {
+ if (this.isResultOutdated()) {
+ return 'outdated';
+ }
+ return '';
+ }
+
+ const durationFormat = distanceInWordsStrict(start, end);
+ const endFormat = format(this.dateFinished, 'MMMM DD YYYY, h:mm:ss A');
+
+ const user = this.user === undefined || this.user === null ? 'anonymous' : this.user;
+ let desc = `Took ${durationFormat}. Last updated by ${user} at ${endFormat}.`;
+
+ if (this.isResultOutdated()) {
+ desc += ' (outdated)';
+ }
+
+ return desc;
+ }
+
+ getElapsedTime() {
+ // TODO(hsuanxyz) dateStarted undefined after start
+ return `Started ${distanceInWordsToNow(this.dateStarted || new Date())} ago.`;
+ }
+
+ constructor() {}
+
+ ngOnChanges() {
+ this.executionTime = this.getExecutionTime();
+ this.elapsedTime = this.getElapsedTime();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.html
new file mode 100644
index 00000000000..e2866272e97
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.html
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.less
new file mode 100644
index 00000000000..60cc5ac3b19
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.less
@@ -0,0 +1,77 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+:host {
+ display: block;
+ padding: 0 4px;
+}
+
+.themeMixin({
+
+ .paragraph {
+ background: @component-background;
+ border: 1px solid @border-color-split;
+ box-shadow: @card-shadow;
+ padding: 32px 12px 12px 12px;
+ position: relative;
+
+ &.focused {
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.46);
+ }
+
+ zeppelin-notebook-paragraph-code-editor + zeppelin-notebook-paragraph-dynamic-forms {
+ margin-top: 24px;
+ }
+
+ zeppelin-notebook-paragraph-progress + zeppelin-notebook-paragraph-dynamic-forms {
+ margin-top: 24px;
+ }
+
+ &.simple {
+ box-shadow: none;
+ border-color: transparent;
+
+ zeppelin-notebook-paragraph-control, zeppelin-notebook-paragraph-footer {
+ visibility: hidden;
+ }
+
+ &:hover {
+ border: 1px solid @border-color-split;
+ box-shadow: @card-shadow;
+
+ zeppelin-notebook-paragraph-control, zeppelin-notebook-paragraph-footer {
+ visibility: visible;
+ }
+ }
+ }
+
+ &.report {
+ &:hover {
+ box-shadow: none;
+ border-color: transparent;
+
+ zeppelin-notebook-paragraph-control, zeppelin-notebook-paragraph-footer {
+ visibility: hidden;
+ }
+ }
+ }
+
+ zeppelin-notebook-paragraph-control {
+ position: absolute;
+ right: 12px;
+ top: 8px;
+ z-index: 10;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.ts
new file mode 100644
index 00000000000..3afdea13f6d
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/paragraph.component.ts
@@ -0,0 +1,647 @@
+/*
+ * Licensed 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 {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ Output,
+ QueryList,
+ SimpleChanges,
+ ViewChild,
+ ViewChildren
+} from '@angular/core';
+import { merge, Observable, Subject } from 'rxjs';
+import { map, takeUntil } from 'rxjs/operators';
+
+import { NzModalService } from 'ng-zorro-antd/modal';
+
+import { ParagraphBase } from '@zeppelin/core';
+import { InterpreterBindingItem, Note, ParagraphConfigResult, ParagraphItem } from '@zeppelin/sdk';
+import {
+ HeliumService,
+ MessageService,
+ NgZService,
+ NoteStatusService,
+ NoteVarShareService,
+ ParagraphActions,
+ ShortcutsMap,
+ ShortcutService
+} from '@zeppelin/services';
+import { SpellResult } from '@zeppelin/spell/spell-result';
+
+import { NgTemplateAdapterService } from '@zeppelin/services/ng-template-adapter.service';
+import { NzResizeEvent } from 'ng-zorro-antd/resizable';
+import { NotebookParagraphResultComponent } from '../../share/result/result.component';
+import { NotebookParagraphCodeEditorComponent } from './code-editor/code-editor.component';
+
+type Mode = 'edit' | 'command';
+
+@Component({
+ selector: 'zeppelin-notebook-paragraph',
+ templateUrl: './paragraph.component.html',
+ styleUrls: ['./paragraph.component.less'],
+ host: {
+ tabindex: '-1',
+ '(focusin)': 'onFocus()'
+ },
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookParagraphComponent extends ParagraphBase implements OnInit, OnChanges, OnDestroy, AfterViewInit {
+ @ViewChild(NotebookParagraphCodeEditorComponent, { static: false })
+ notebookParagraphCodeEditorComponent: NotebookParagraphCodeEditorComponent;
+ @ViewChildren(NotebookParagraphResultComponent) notebookParagraphResultComponents: QueryList<
+ NotebookParagraphResultComponent
+ >;
+ @Input() paragraph: ParagraphItem;
+ @Input() note: Note['note'];
+ @Input() looknfeel: string;
+ @Input() revisionView: boolean;
+ @Input() select: boolean = false;
+ @Input() scrolled: boolean = false;
+ @Input() index: number = -1;
+ @Input() viewOnly: boolean;
+ @Input() last: boolean;
+ @Input() collaborativeMode = false;
+ @Input() first: boolean;
+ @Input() interpreterBindings: InterpreterBindingItem[] = [];
+ @Output() readonly saveNoteTimer = new EventEmitter();
+ @Output() readonly triggerSaveParagraph = new EventEmitter();
+ @Output() readonly selected = new EventEmitter();
+ @Output() readonly selectAtIndex = new EventEmitter();
+
+ private destroy$ = new Subject();
+ private mode: Mode = 'command';
+ waitConfirmFromEdit = false;
+
+ switchMode(mode: Mode): void {
+ if (mode === this.mode) {
+ return;
+ }
+ this.mode = mode;
+ if (mode === 'edit') {
+ this.focusEditor();
+ } else {
+ this.blurEditor();
+ }
+ }
+
+ textChanged(text: string) {
+ this.dirtyText = text;
+ this.paragraph.text = text;
+ if (this.dirtyText !== this.originalText) {
+ if (this.collaborativeMode) {
+ this.sendPatch();
+ } else {
+ this.startSaveTimer();
+ }
+ }
+ }
+
+ sendPatch() {
+ this.originalText = this.originalText ? this.originalText : '';
+ const patch = this.diffMatchPatch.patch_make(this.originalText, this.dirtyText).toString();
+ this.originalText = this.dirtyText;
+ this.messageService.patchParagraph(this.paragraph.id, this.note.id, patch);
+ }
+
+ startSaveTimer() {
+ this.saveNoteTimer.emit();
+ }
+
+ onFocus() {
+ this.selected.emit(this.paragraph.id);
+ }
+
+ focusEditor() {
+ this.paragraph.focus = true;
+ this.saveParagraph();
+ this.cdr.markForCheck();
+ }
+
+ blurEditor() {
+ this.paragraph.focus = false;
+ (this.host.nativeElement as HTMLElement).focus();
+ this.saveParagraph();
+ this.cdr.markForCheck();
+ }
+
+ onEditorFocus() {
+ this.switchMode('edit');
+ }
+
+ onEditorBlur() {
+ // Ignore events triggered by open the confirm box in edit mode
+ if (!this.waitConfirmFromEdit) {
+ this.switchMode('command');
+ }
+ }
+
+ saveParagraph() {
+ const dirtyText = this.paragraph.text;
+ if (dirtyText === undefined || dirtyText === this.originalText) {
+ return;
+ }
+ this.commitParagraph();
+ this.originalText = dirtyText;
+ this.dirtyText = undefined;
+ this.cdr.markForCheck();
+ }
+
+ removeParagraph() {
+ if (!this.isEntireNoteRunning) {
+ if (this.note.paragraphs.length === 1) {
+ this.nzModalService.warning({
+ nzTitle: `Warning`,
+ nzContent: `All the paragraphs can't be deleted`
+ });
+ } else {
+ this.nzModalService.confirm({
+ nzTitle: 'Delete Paragraph',
+ nzContent: 'Do you want to delete this paragraph?',
+ nzOnOk: () => {
+ this.messageService.paragraphRemove(this.paragraph.id);
+ this.cdr.markForCheck();
+ // TODO(hsuanxyz) moveFocusToNextParagraph
+ }
+ });
+ }
+ }
+ }
+
+ runAllAbove() {
+ const index = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id);
+ const toRunParagraphs = this.note.paragraphs.filter((p, i) => i < index);
+
+ const paragraphs = toRunParagraphs.map(p => {
+ return {
+ id: p.id,
+ title: p.title,
+ paragraph: p.text,
+ config: p.config,
+ params: p.settings.params
+ };
+ });
+ this.nzModalService.confirm({
+ nzTitle: 'Run all above?',
+ nzContent: 'Are you sure to run all above paragraphs?',
+ nzOnOk: () => {
+ this.messageService.runAllParagraphs(this.note.id, paragraphs);
+ }
+ });
+ // TODO(hsuanxyz): save cursor
+ }
+
+ doubleClickParagraph() {
+ if (this.paragraph.config.editorSetting.editOnDblClick && this.revisionView !== true) {
+ this.paragraph.config.editorHide = false;
+ this.paragraph.config.tableHide = true;
+ // TODO(hsuanxyz): focus editor
+ }
+ }
+
+ runAllBelowAndCurrent() {
+ const index = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id);
+ const toRunParagraphs = this.note.paragraphs.filter((p, i) => i >= index);
+
+ const paragraphs = toRunParagraphs.map(p => {
+ return {
+ id: p.id,
+ title: p.title,
+ paragraph: p.text,
+ config: p.config,
+ params: p.settings.params
+ };
+ });
+ this.nzModalService
+ .confirm({
+ nzTitle: 'Run current and all below?',
+ nzContent: 'Are you sure to run current and all below?',
+ nzOnOk: () => {
+ this.messageService.runAllParagraphs(this.note.id, paragraphs);
+ }
+ })
+ .afterClose.pipe(takeUntil(this.destroy$))
+ .subscribe(() => {
+ this.waitConfirmFromEdit = false;
+ });
+ // TODO(hsuanxyz): save cursor
+ }
+
+ cloneParagraph(position: string = 'below', newText?: string) {
+ let newIndex = -1;
+ for (let i = 0; i < this.note.paragraphs.length; i++) {
+ if (this.note.paragraphs[i].id === this.paragraph.id) {
+ // determine position of where to add new paragraph; default is below
+ if (position === 'above') {
+ newIndex = i;
+ } else {
+ newIndex = i + 1;
+ }
+ break;
+ }
+ }
+
+ if (newIndex < 0 || newIndex > this.note.paragraphs.length) {
+ return;
+ }
+
+ const config = this.paragraph.config;
+ config.editorHide = false;
+
+ this.messageService.copyParagraph(
+ newIndex,
+ this.paragraph.title,
+ newText || this.paragraph.text,
+ config,
+ this.paragraph.settings.params
+ );
+ }
+
+ runParagraphAfter(text: string) {
+ this.originalText = text;
+ this.dirtyText = undefined;
+
+ if (this.paragraph.config.editorSetting.editOnDblClick) {
+ this.paragraph.config.editorHide = true;
+ this.paragraph.config.tableHide = false;
+ this.commitParagraph();
+ } else if (this.editorSetting.isOutputHidden && !this.paragraph.config.editorSetting.editOnDblClick) {
+ // %md/%angular repl make output to be hidden by default after running
+ // so should open output if repl changed from %md/%angular to another
+ this.paragraph.config.editorHide = false;
+ this.paragraph.config.tableHide = false;
+ this.commitParagraph();
+ }
+ this.editorSetting.isOutputHidden = this.paragraph.config.editorSetting.editOnDblClick;
+ }
+
+ runParagraph(paragraphText?: string, propagated: boolean = false) {
+ const text = paragraphText || this.paragraph.text;
+ if (text && !this.isParagraphRunning) {
+ const magic = SpellResult.extractMagic(text);
+
+ if (this.heliumService.getSpellByMagic(magic)) {
+ this.runParagraphUsingSpell(text, magic, propagated);
+ this.runParagraphAfter(text);
+ } else {
+ const check = this.ngTemplateAdapterService.preCheck(text);
+ if (!check) {
+ this.runParagraphUsingBackendInterpreter(text);
+ this.runParagraphAfter(text);
+ } else {
+ this.waitConfirmFromEdit = true;
+ this.nzModalService
+ .confirm({
+ nzTitle: 'Do you want to migrate the Angular.js template?',
+ nzContent:
+ 'The Angular.js template has been deprecated, please upgrade to Angular template.' +
+ ' (more info)',
+ nzOnOk: () => {
+ this.switchMode('command');
+ this.ngTemplateAdapterService
+ .openMigrationDialog(check)
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(newText => {
+ this.cloneParagraph('below', newText);
+ });
+ }
+ })
+ .afterClose.pipe(takeUntil(this.destroy$))
+ .subscribe(() => (this.waitConfirmFromEdit = false));
+ }
+ }
+ }
+ }
+
+ insertParagraph(position: string) {
+ if (this.revisionView === true) {
+ return;
+ }
+ let newIndex = -1;
+ for (let i = 0; i < this.note.paragraphs.length; i++) {
+ if (this.note.paragraphs[i].id === this.paragraph.id) {
+ // determine position of where to add new paragraph; default is below
+ if (position === 'above') {
+ newIndex = i;
+ } else {
+ newIndex = i + 1;
+ }
+ break;
+ }
+ }
+
+ if (newIndex < 0 || newIndex > this.note.paragraphs.length) {
+ return;
+ }
+ this.messageService.insertParagraph(newIndex);
+ this.cdr.markForCheck();
+ }
+
+ setTitle(title: string) {
+ this.paragraph.title = title;
+ this.commitParagraph();
+ }
+
+ commitParagraph() {
+ const {
+ id,
+ title,
+ text,
+ config,
+ settings: { params }
+ } = this.paragraph;
+ this.messageService.commitParagraph(id, title, text, config, params, this.note.id);
+ this.cdr.markForCheck();
+ }
+
+ moveUpParagraph() {
+ const newIndex = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id) - 1;
+ if (newIndex < 0 || newIndex >= this.note.paragraphs.length) {
+ return;
+ }
+ // save dirtyText of moving paragraphs.
+ const prevParagraph = this.note.paragraphs[newIndex];
+ // TODO(hsuanxyz): save pre paragraph?
+ this.saveParagraph();
+ this.triggerSaveParagraph.emit(prevParagraph.id);
+ this.messageService.moveParagraph(this.paragraph.id, newIndex);
+ }
+
+ moveDownParagraph() {
+ const newIndex = this.note.paragraphs.findIndex(p => p.id === this.paragraph.id) + 1;
+ if (newIndex < 0 || newIndex >= this.note.paragraphs.length) {
+ return;
+ }
+ // save dirtyText of moving paragraphs.
+ const nextParagraph = this.note.paragraphs[newIndex];
+ // TODO(hsuanxyz): save pre paragraph?
+ this.saveParagraph();
+ this.triggerSaveParagraph.emit(nextParagraph.id);
+ this.messageService.moveParagraph(this.paragraph.id, newIndex);
+ }
+
+ changeColWidth(needCommit: boolean, updateResult = true) {
+ if (needCommit) {
+ this.commitParagraph();
+ }
+ if (this.notebookParagraphCodeEditorComponent) {
+ this.notebookParagraphCodeEditorComponent.layout();
+ }
+
+ if (updateResult) {
+ this.notebookParagraphResultComponents.forEach(comp => {
+ comp.setGraphConfig();
+ });
+ }
+ }
+
+ onSizeChange(resize: NzResizeEvent) {
+ this.paragraph.config.colWidth = resize.col;
+ this.changeColWidth(true, false);
+ this.cdr.markForCheck();
+ }
+
+ onConfigChange(configResult: ParagraphConfigResult, index: number) {
+ this.paragraph.config.results[index] = configResult;
+ this.commitParagraph();
+ }
+
+ setEditorHide(editorHide: boolean) {
+ this.paragraph.config.editorHide = editorHide;
+ this.cdr.markForCheck();
+ }
+
+ setTableHide(tableHide: boolean) {
+ this.paragraph.config.tableHide = tableHide;
+ this.cdr.markForCheck();
+ }
+
+ openSingleParagraph(paragraphId: string): void {
+ const noteId = this.note.id;
+ const redirectToUrl = `${location.protocol}//${location.host}${location.pathname}#/notebook/${noteId}/paragraph/${paragraphId}`;
+ window.open(redirectToUrl);
+ }
+
+ trackByIndexFn(index: number) {
+ return index;
+ }
+
+ constructor(
+ noteStatusService: NoteStatusService,
+ cdr: ChangeDetectorRef,
+ ngZService: NgZService,
+ private heliumService: HeliumService,
+ public messageService: MessageService,
+ private nzModalService: NzModalService,
+ private noteVarShareService: NoteVarShareService,
+ private shortcutService: ShortcutService,
+ private host: ElementRef,
+ private ngTemplateAdapterService: NgTemplateAdapterService
+ ) {
+ super(messageService, noteStatusService, ngZService, cdr);
+ }
+
+ ngOnInit() {
+ const shortcutService = this.shortcutService.forkByElement(this.host.nativeElement);
+ const observables: Array<
+ Observable<{
+ action: ParagraphActions;
+ event: KeyboardEvent;
+ }>
+ > = [];
+ Object.entries(ShortcutsMap).forEach(([action, keys]) => {
+ const keysArr: string[] = Array.isArray(keys) ? keys : [keys];
+ keysArr.forEach(key => {
+ observables.push(
+ shortcutService
+ .bindShortcut({
+ keybindings: key
+ })
+ .pipe(
+ takeUntil(this.destroy$),
+ map(({ event }) => {
+ return {
+ event,
+ action: action as ParagraphActions
+ };
+ })
+ )
+ );
+ });
+ });
+
+ merge<{
+ action: ParagraphActions;
+ event: KeyboardEvent;
+ }>(...observables)
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(({ action, event }) => {
+ if (this.mode === 'command') {
+ switch (action) {
+ case ParagraphActions.InsertAbove:
+ this.insertParagraph('above');
+ break;
+ case ParagraphActions.InsertBelow:
+ this.insertParagraph('below');
+ break;
+ case ParagraphActions.SwitchEditorShow:
+ this.setEditorHide(!this.paragraph.config.editorHide);
+ this.commitParagraph();
+ break;
+ case ParagraphActions.SwitchOutputShow:
+ this.setTableHide(!this.paragraph.config.tableHide);
+ this.commitParagraph();
+ break;
+ case ParagraphActions.SwitchTitleShow:
+ this.paragraph.config.title = !this.paragraph.config.title;
+ this.commitParagraph();
+ break;
+ case ParagraphActions.SwitchLineNumber:
+ this.paragraph.config.lineNumbers = !this.paragraph.config.lineNumbers;
+ this.commitParagraph();
+ break;
+ case ParagraphActions.MoveToUp:
+ event.preventDefault();
+ this.moveUpParagraph();
+ break;
+ case ParagraphActions.MoveToDown:
+ event.preventDefault();
+ this.moveDownParagraph();
+ break;
+ case ParagraphActions.SwitchEnable:
+ this.paragraph.config.enabled = !this.paragraph.config.enabled;
+ this.commitParagraph();
+ break;
+ case ParagraphActions.ReduceWidth:
+ this.paragraph.config.colWidth = Math.max(1, this.paragraph.config.colWidth - 1);
+ this.cdr.markForCheck();
+ this.changeColWidth(true);
+ break;
+ case ParagraphActions.IncreaseWidth:
+ this.paragraph.config.colWidth = Math.min(12, this.paragraph.config.colWidth + 1);
+ this.cdr.markForCheck();
+ this.changeColWidth(true);
+ break;
+ case ParagraphActions.Delete:
+ this.removeParagraph();
+ break;
+ case ParagraphActions.SelectAbove:
+ event.preventDefault();
+ this.selectAtIndex.emit(this.index - 1);
+ break;
+ case ParagraphActions.SelectBelow:
+ event.preventDefault();
+ this.selectAtIndex.emit(this.index + 1);
+ break;
+ default:
+ break;
+ }
+ }
+ switch (action) {
+ case ParagraphActions.Link:
+ this.openSingleParagraph(this.paragraph.id);
+ break;
+ case ParagraphActions.EditMode:
+ if (this.mode === 'command') {
+ event.preventDefault();
+ }
+ if (!this.paragraph.config.editorHide) {
+ this.switchMode('edit');
+ }
+ break;
+ case ParagraphActions.Run:
+ event.preventDefault();
+ this.runParagraph();
+ break;
+ case ParagraphActions.RunBelow:
+ this.waitConfirmFromEdit = true;
+ this.runAllBelowAndCurrent();
+ break;
+ case ParagraphActions.Cancel:
+ event.preventDefault();
+ this.cancelParagraph();
+ break;
+ default:
+ break;
+ }
+ });
+ this.setResults();
+ this.originalText = this.paragraph.text;
+ this.isEntireNoteRunning = this.noteStatusService.isEntireNoteRunning(this.note);
+ this.isParagraphRunning = this.noteStatusService.isParagraphRunning(this.paragraph);
+ this.noteVarShareService.set(this.paragraph.id + '_paragraphScope', this);
+ this.initializeDefault(this.paragraph.config);
+ this.ngZService
+ .runParagraphAction()
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(id => {
+ if (id === this.paragraph.id) {
+ this.runParagraph();
+ }
+ });
+ this.ngZService
+ .contextChanged()
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(change => {
+ if (change.paragraphId === this.paragraph.id && change.emit) {
+ if (change.set) {
+ this.messageService.angularObjectClientBind(this.note.id, change.key, change.value, change.paragraphId);
+ } else {
+ this.messageService.angularObjectClientUnbind(this.note.id, change.key, change.paragraphId);
+ }
+ }
+ });
+ }
+
+ scrollIfNeeded(): void {
+ if (this.scrolled && this.host.nativeElement) {
+ setTimeout(() => {
+ this.host.nativeElement.scrollIntoView();
+ });
+ }
+ }
+ ngOnChanges(changes: SimpleChanges): void {
+ const { index, select, scrolled } = changes;
+ if (
+ (index && index.currentValue !== index.previousValue && this.select) ||
+ (select && select.currentValue === true && select.previousValue !== true)
+ ) {
+ setTimeout(() => {
+ if (this.mode === 'command' && this.host.nativeElement) {
+ (this.host.nativeElement as HTMLElement).focus();
+ }
+ });
+ }
+ if (scrolled) {
+ this.scrollIfNeeded();
+ }
+ }
+
+ getElement(): HTMLElement {
+ return this.host && this.host.nativeElement;
+ }
+ ngAfterViewInit(): void {
+ this.scrollIfNeeded();
+ }
+
+ ngOnDestroy(): void {
+ super.ngOnDestroy();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.html
new file mode 100644
index 00000000000..07c886a22d0
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.html
@@ -0,0 +1,16 @@
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.less
new file mode 100644
index 00000000000..019b5ca53b5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.less
@@ -0,0 +1,12 @@
+/*
+ * Licensed 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.
+ */
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.ts
new file mode 100644
index 00000000000..ce205a0ad59
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/progress/progress.component.ts
@@ -0,0 +1,32 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
+
+@Component({
+ selector: 'zeppelin-notebook-paragraph-progress',
+ templateUrl: './progress.component.html',
+ styleUrls: ['./progress.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookParagraphProgressComponent implements OnChanges {
+ @Input() progress = 0;
+ displayProgress = 0;
+
+ ngOnChanges(): void {
+ if (this.progress > 0 && this.progress < 100) {
+ this.displayProgress = this.progress;
+ } else {
+ this.displayProgress = 100;
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.html
new file mode 100644
index 00000000000..c3e699d4aef
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.html
@@ -0,0 +1,88 @@
+
+
+
+
+
Note Permissions (Only note owners can change)
+
+
+
+
+ Enter comma separated users and groups in the fields.
+ Empty field (*) implies anyone can do the operation.
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.less
new file mode 100644
index 00000000000..146cd37f4af
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.less
@@ -0,0 +1,33 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ .permissions-form {
+ background: @layout-body-background;
+ padding: 12px;
+ margin-bottom: 12px;
+ }
+ nz-form-item {
+ margin-bottom: 12px;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ .submit-permissions {
+ button {
+ margin-right: 12px;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.ts
new file mode 100644
index 00000000000..6ceaaeb5951
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/permissions/permissions.component.ts
@@ -0,0 +1,136 @@
+/*
+ * Licensed 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 {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ OnChanges,
+ OnInit,
+ Output
+} from '@angular/core';
+
+import { NzMessageService } from 'ng-zorro-antd/message';
+import { NzModalService } from 'ng-zorro-antd/modal';
+
+import { Permissions } from '@zeppelin/interfaces';
+import { SecurityService, TicketService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-notebook-permissions',
+ templateUrl: './permissions.component.html',
+ styleUrls: ['./permissions.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookPermissionsComponent implements OnInit, OnChanges {
+ @Input() permissions: Permissions;
+ @Input() noteId: string;
+ @Input() activatedExtension: 'interpreter' | 'permissions' | 'revisions' | 'hide' = 'hide';
+ @Output() readonly activatedExtensionChange = new EventEmitter<
+ 'interpreter' | 'permissions' | 'revisions' | 'hide'
+ >();
+ permissionsBack: Permissions;
+ listOfUserAndRole = [];
+
+ savePermissions() {
+ const principal = this.ticketService.ticket.principal;
+ const isAnonymous = principal === 'anonymous';
+ if (isAnonymous || this.ticketService.ticket.principal.trim().length === 0) {
+ this.blockAnonUsers();
+ }
+ if (this.isOwnerEmpty()) {
+ this.nzModalService.create({
+ nzTitle: 'Setting Owners Permissions',
+ nzContent: `Please fill the [Owners] field. If not, it will set as current user. Current user : [ ${this.ticketService.ticket.principal.trim()} ]`,
+ nzOnOk: () => {
+ this.permissions.owners = [this.ticketService.ticket.principal];
+ this.setPermissions();
+ },
+ nzOnCancel: () => {
+ this.resetPermissions();
+ }
+ });
+ } else {
+ this.setPermissions();
+ }
+ }
+
+ closePermissions() {
+ this.activatedExtension = 'hide';
+ this.activatedExtensionChange.emit('hide');
+ }
+
+ blockAnonUsers() {
+ this.nzModalService.create({
+ nzTitle: 'No permission',
+ nzContent: 'Only authenticated user can set the permission.',
+ nzOkText: 'Read Doc',
+ nzOnOk: () => {
+ const url = `https://zeppelin.apache.org/docs/${this.ticketService.version}/security/notebook_authorization.html`;
+ window.open(url);
+ }
+ });
+ }
+
+ setPermissions() {
+ this.securityService.setPermissions(this.noteId, this.permissions).subscribe(() => {
+ this.nzMessageService.success('Permissions Saved Successfully');
+ this.closePermissions();
+ });
+ }
+
+ resetPermissions() {
+ this.permissions = { ...this.permissionsBack };
+ }
+
+ isOwnerEmpty() {
+ return !this.permissions.owners.some(o => o.trim().length > 0);
+ }
+
+ searchUser(search: string) {
+ this.securityService.searchUsers(search).subscribe(data => {
+ const results = [];
+ if (data.users.length) {
+ results.push({
+ text: 'Users :',
+ children: data.users
+ });
+ }
+ if (data.roles.length) {
+ results.push({
+ text: 'Roles :',
+ children: data.roles
+ });
+ }
+ this.listOfUserAndRole = results;
+ this.cdr.markForCheck();
+ });
+ }
+
+ constructor(
+ private securityService: SecurityService,
+ private cdr: ChangeDetectorRef,
+ private nzMessageService: NzMessageService,
+ private ticketService: TicketService,
+ private nzModalService: NzModalService
+ ) {}
+
+ ngOnInit() {
+ this.permissionsBack = { ...this.permissions };
+ }
+
+ ngOnChanges(): void {
+ this.permissionsBack = { ...this.permissions };
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.html
new file mode 100644
index 00000000000..6f0e1be83f1
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.html
@@ -0,0 +1,20 @@
+
+
+
+
+
Revisions comparator
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.less
new file mode 100644
index 00000000000..019b5ca53b5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.less
@@ -0,0 +1,12 @@
+/*
+ * Licensed 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.
+ */
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.ts
new file mode 100644
index 00000000000..1876b3cbbdb
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.ts
@@ -0,0 +1,25 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'zeppelin-notebook-revisions-comparator',
+ templateUrl: './revisions-comparator.component.html',
+ styleUrls: ['./revisions-comparator.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookRevisionsComparatorComponent implements OnInit {
+ constructor() {}
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.html b/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.html
new file mode 100644
index 00000000000..aa7b5e777b5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.html
@@ -0,0 +1,23 @@
+
+
+
+
+
{{value || defaultTitle}}
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.less b/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.less
new file mode 100644
index 00000000000..6efdd89a7aa
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.less
@@ -0,0 +1,56 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ .elastic {
+ margin-right: 24px;
+ overflow: hidden;
+ max-width: 100%;
+
+ &.min {
+ margin-bottom: 6px;
+ margin-right: 0;
+ p, input {
+ font-size: 16px;
+ padding: 0 1px;
+ }
+
+ input {
+ height: 24px;
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ p, input {
+ font-size: 28px;
+ width: 100%;
+ font-weight: 700;
+ }
+
+ p {
+ margin: 0 1px;
+ padding: 0 11px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ input {
+ height: 36px;
+ margin: 7px 0;
+ padding: 0 10px;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.ts
new file mode 100644
index 00000000000..2057a875cf1
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/share/elastic-input/elastic-input.component.ts
@@ -0,0 +1,89 @@
+/*
+ * Licensed 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 {
+ ChangeDetectionStrategy,
+ Component,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnChanges,
+ Output,
+ Renderer2,
+ SimpleChanges,
+ ViewChild
+} from '@angular/core';
+
+@Component({
+ selector: 'zeppelin-elastic-input',
+ templateUrl: './elastic-input.component.html',
+ styleUrls: ['./elastic-input.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ElasticInputComponent implements OnChanges {
+ @Input() value: string;
+ @Input() readonly = false;
+ @Input() min = false;
+ @Input() defaultTitle = 'Untitled';
+ @Output() readonly valueUpdate = new EventEmitter();
+ @ViewChild('inputElement', { read: ElementRef, static: false }) inputElement: ElementRef;
+ @ViewChild('pElement', { read: ElementRef, static: false }) pElement: ElementRef;
+ @ViewChild('elasticElement', { read: ElementRef, static: true }) elasticElement: ElementRef;
+ showEditor = false;
+ editValue: string;
+
+ cancelEdit() {
+ this.editValue = this.value;
+ this.showEditor = false;
+ }
+
+ updateValue(value: string) {
+ const trimmedNewName = value.trim();
+ if (typeof value === 'string') {
+ this.editValue = trimmedNewName;
+ }
+ }
+
+ setEditorState(showEditor: boolean) {
+ if (!this.readonly) {
+ this.showEditor = showEditor;
+ if (!this.showEditor) {
+ this.valueUpdate.emit(this.editValue);
+ } else {
+ const width = this.pElement.nativeElement.getBoundingClientRect().width;
+ this.renderer.setStyle(this.elasticElement.nativeElement, 'width', `${width}px`);
+ setTimeout(() => {
+ this.inputElement.nativeElement.focus();
+ this.renderer.setStyle(this.inputElement.nativeElement, 'width', `${width}px`);
+ });
+ }
+ }
+ }
+
+ updateInputWidth() {
+ const width = this.inputElement.nativeElement.scrollWidth;
+ if (width > this.inputElement.nativeElement.getBoundingClientRect().width) {
+ this.renderer.removeStyle(this.elasticElement.nativeElement, 'width');
+ this.renderer.setStyle(this.inputElement.nativeElement, 'width', `${width}px`);
+ }
+ }
+
+ constructor(private renderer: Renderer2) {}
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes.value) {
+ this.showEditor = false;
+ this.editValue = this.value;
+ this.renderer.removeStyle(this.elasticElement.nativeElement, 'width');
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/share/share.module.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/share/share.module.ts
new file mode 100644
index 00000000000..b8ae9a7d06b
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/share/share.module.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+
+import { NzInputModule } from 'ng-zorro-antd/input';
+
+import { ElasticInputComponent } from './elastic-input/elastic-input.component';
+
+@NgModule({
+ declarations: [ElasticInputComponent],
+ exports: [ElasticInputComponent],
+ imports: [CommonModule, NzInputModule, FormsModule]
+})
+export class NotebookShareModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html
new file mode 100644
index 00000000000..4423ddafd7d
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.html
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.less b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.less
new file mode 100644
index 00000000000..2fe3799b5f3
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.less
@@ -0,0 +1,11 @@
+/*
+* Licensed 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.
+*/
diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts
new file mode 100644
index 00000000000..2b4e17c1599
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/published/paragraph/paragraph.component.ts
@@ -0,0 +1,99 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, QueryList, ViewChildren } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { MessageListener, ParagraphBase } from '@zeppelin/core';
+import { publishedSymbol, Published } from '@zeppelin/core/paragraph-base/published';
+import { NotebookParagraphResultComponent } from '@zeppelin/pages/workspace/share/result/result.component';
+import { MessageReceiveDataTypeMap, Note, OP } from '@zeppelin/sdk';
+import { HeliumService, MessageService, NgZService, NoteStatusService } from '@zeppelin/services';
+import { SpellResult } from '@zeppelin/spell/spell-result';
+import { isNil } from 'lodash';
+
+@Component({
+ selector: 'zeppelin-publish-paragraph',
+ templateUrl: './paragraph.component.html',
+ styleUrls: ['./paragraph.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class PublishedParagraphComponent extends ParagraphBase implements Published, OnInit {
+ readonly [publishedSymbol] = true;
+
+ noteId: string;
+ paragraphId: string;
+
+ @ViewChildren(NotebookParagraphResultComponent) notebookParagraphResultComponents: QueryList<
+ NotebookParagraphResultComponent
+ >;
+
+ constructor(
+ public messageService: MessageService,
+ noteStatusService: NoteStatusService,
+ ngZService: NgZService,
+ cdr: ChangeDetectorRef,
+ private activatedRoute: ActivatedRoute,
+ private heliumService: HeliumService
+ ) {
+ super(messageService, noteStatusService, ngZService, cdr);
+ this.activatedRoute.params.subscribe(params => {
+ this.noteId = params.noteId;
+ this.paragraphId = params.paragraphId;
+ this.messageService.getNote(this.noteId);
+ });
+ }
+
+ ngOnInit() {}
+
+ @MessageListener(OP.NOTE)
+ getNote(data: MessageReceiveDataTypeMap[OP.NOTE]) {
+ const note = data.note;
+ if (!isNil(note)) {
+ this.paragraph = (note as Note['note']).paragraphs.find(p => p.id === this.paragraphId);
+ if (this.paragraph) {
+ this.setResults();
+ this.originalText = this.paragraph.text;
+ this.initializeDefault(this.paragraph.config);
+ }
+ }
+ this.cdr.markForCheck();
+ }
+
+ trackByIndexFn(index: number) {
+ return index;
+ }
+
+ setResults() {
+ if (this.paragraph.results) {
+ this.results = this.paragraph.results.msg;
+ this.configs = this.paragraph.config.results;
+ }
+ if (!this.paragraph.config) {
+ this.paragraph.config = {};
+ }
+ }
+
+ changeColWidth(needCommit: boolean, updateResult?: boolean): void {
+ // noop
+ }
+
+ runParagraph(): void {
+ const text = this.paragraph.text;
+ if (text && !this.isParagraphRunning) {
+ const magic = SpellResult.extractMagic(this.paragraph.text);
+ if (this.heliumService.getSpellByMagic(magic)) {
+ this.runParagraphUsingSpell(text, magic, false);
+ } else {
+ this.runParagraphUsingBackendInterpreter(text);
+ }
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/published-ruoting.module.ts b/zeppelin-web-angular/src/app/pages/workspace/published/published-ruoting.module.ts
new file mode 100644
index 00000000000..eaf001fde06
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/published/published-ruoting.module.ts
@@ -0,0 +1,28 @@
+/*
+ * Licensed 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 { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+import { PublishedParagraphComponent } from './paragraph/paragraph.component';
+
+const routes: Routes = [
+ {
+ path: ':paragraphId',
+ component: PublishedParagraphComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class PublishedRoutingModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts b/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts
new file mode 100644
index 00000000000..f6474d94896
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/published/published.module.ts
@@ -0,0 +1,11 @@
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { WorkspaceShareModule } from '../../workspace/share/share.module';
+import { PublishedParagraphComponent } from './paragraph/paragraph.component';
+import { PublishedRoutingModule } from './published-ruoting.module';
+
+@NgModule({
+ declarations: [PublishedParagraphComponent],
+ imports: [CommonModule, WorkspaceShareModule, PublishedRoutingModule]
+})
+export class PublishedModule {}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.html b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.html
new file mode 100644
index 00000000000..330b3e82220
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.html
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.less b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.less
new file mode 100644
index 00000000000..a9a4b889420
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.less
@@ -0,0 +1,46 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+:host {
+ display: block;
+}
+
+.themeMixin({
+ .control-wrap {
+ display: flex;
+ }
+ .form-item {
+ margin-bottom: 24px;
+ .item-label {
+ font-weight: 700;
+ }
+ nz-select {
+ width: 100%;
+ }
+ ::ng-deep .ant-checkbox-wrapper {
+ margin-left: 0;
+ line-height: 32px;
+ }
+ &:hover {
+ .remove-button {
+ color: @text-color-danger;
+ }
+ }
+ .remove-button {
+ margin: 0 4px;
+ transition: color ease-in-out .3s;
+ color: @text-color-secondary;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.ts b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.ts
new file mode 100644
index 00000000000..b6eb9f9bb73
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/share/dynamic-forms/dynamic-forms.component.ts
@@ -0,0 +1,126 @@
+/*
+ * Licensed 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 {
+ ChangeDetectionStrategy,
+ Component,
+ EventEmitter,
+ HostListener,
+ Input,
+ OnChanges,
+ OnDestroy,
+ OnInit,
+ Output,
+ SimpleChanges
+} from '@angular/core';
+import { Subject } from 'rxjs';
+import { debounceTime, takeUntil } from 'rxjs/operators';
+
+import { NzCheckBoxOptionInterface } from 'ng-zorro-antd/checkbox';
+
+import { DynamicForms, DynamicFormsItem, DynamicFormsType, DynamicFormParams } from '@zeppelin/sdk';
+
+@Component({
+ selector: 'zeppelin-notebook-paragraph-dynamic-forms',
+ templateUrl: './dynamic-forms.component.html',
+ styleUrls: ['./dynamic-forms.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NotebookParagraphDynamicFormsComponent implements OnInit, OnChanges, OnDestroy {
+ private destroy$ = new Subject();
+
+ @Input() formDefs: DynamicForms;
+ @Input() paramDefs: DynamicFormParams;
+ @Input() runOnChange = false;
+ @Input() disable = false;
+ @Input() removable = false;
+ @Output() readonly formChange = new EventEmitter();
+ @Output() readonly formRemove = new EventEmitter();
+
+ formChange$ = new Subject();
+ forms: DynamicFormsItem[] = [];
+ formType = DynamicFormsType;
+ checkboxGroups: {
+ [key: string]: NzCheckBoxOptionInterface[];
+ } = {};
+
+ @HostListener('keydown.enter')
+ onEnter() {
+ if (!this.runOnChange) {
+ this.formChange.emit();
+ }
+ }
+
+ trackByNameFn(_index, form: DynamicFormsItem) {
+ return form.name;
+ }
+
+ setForms() {
+ this.forms = Object.values(this.formDefs);
+ this.checkboxGroups = {};
+ this.forms.forEach(e => {
+ if (!this.paramDefs[e.name]) {
+ this.paramDefs[e.name] = e.defaultValue;
+ }
+ if (e.type === DynamicFormsType.CheckBox) {
+ this.checkboxGroups[e.name] = e.options.map(opt => {
+ let checked = false;
+ if (this.paramDefs[e.name] && Array.isArray(this.paramDefs[e.name])) {
+ const param = this.paramDefs[e.name] as string[];
+ checked = param.indexOf(opt.value) !== -1;
+ }
+ return {
+ checked,
+ label: opt.displayName || opt.value,
+ value: opt.value
+ };
+ });
+ }
+ });
+ }
+
+ checkboxChange(value: NzCheckBoxOptionInterface[], name) {
+ this.paramDefs[name] = value.filter(e => e.checked).map(e => e.value);
+ this.onFormChange();
+ }
+
+ onFormChange() {
+ if (this.runOnChange) {
+ this.formChange$.next();
+ }
+ }
+
+ remove(item: DynamicFormsItem) {
+ this.formRemove.emit(item);
+ }
+
+ constructor() {}
+
+ ngOnInit() {
+ this.setForms();
+ this.formChange$
+ .pipe(
+ debounceTime(800),
+ takeUntil(this.destroy$)
+ )
+ .subscribe(() => this.formChange.emit());
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.setForms();
+ }
+
+ ngOnDestroy(): void {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/pages/workspace/share/index.ts b/zeppelin-web-angular/src/app/pages/workspace/share/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/share/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/pages/workspace/share/public-api.ts b/zeppelin-web-angular/src/app/pages/workspace/share/public-api.ts
new file mode 100644
index 00000000000..e865360a6bb
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/share/public-api.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './share.module';
diff --git a/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html
new file mode 100644
index 00000000000..cb8369f273e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html
@@ -0,0 +1,75 @@
+
+
+
diff --git a/zeppelin-web-angular/src/app/share/about-zeppelin/about-zeppelin.component.less b/zeppelin-web-angular/src/app/share/about-zeppelin/about-zeppelin.component.less
new file mode 100644
index 00000000000..73be35e8bc8
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/about-zeppelin/about-zeppelin.component.less
@@ -0,0 +1,38 @@
+/*
+ * Licensed 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 'theme-mixin';
+
+.themeMixin({
+ .modal {
+ .about-logo {
+ img {
+ width: 95%;
+ }
+ }
+
+ .content {
+ text-align: center;
+
+ h3 {
+ font-family: 'Patua One', cursive;
+ color: #3071A9;
+ font-size: 30px;
+ margin: 0 auto;
+ }
+
+ .about-version {
+ font-weight: 500;
+ }
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/share/about-zeppelin/about-zeppelin.component.ts b/zeppelin-web-angular/src/app/share/about-zeppelin/about-zeppelin.component.ts
new file mode 100644
index 00000000000..41e8e777324
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/about-zeppelin/about-zeppelin.component.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { TicketService } from '@zeppelin/services';
+
+@Component({
+ selector: 'zeppelin-about-zeppelin',
+ templateUrl: './about-zeppelin.component.html',
+ styleUrls: ['./about-zeppelin.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class AboutZeppelinComponent implements OnInit {
+ constructor(public ticketService: TicketService) {}
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/share/code-editor/code-editor.component.html b/zeppelin-web-angular/src/app/share/code-editor/code-editor.component.html
new file mode 100644
index 00000000000..1b40367eb04
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/code-editor/code-editor.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/share/code-editor/code-editor.component.ts b/zeppelin-web-angular/src/app/share/code-editor/code-editor.component.ts
new file mode 100644
index 00000000000..2fdbaa5950d
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/code-editor/code-editor.component.ts
@@ -0,0 +1,244 @@
+/*
+ * Licensed 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 {
+ forwardRef,
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ Component,
+ ElementRef,
+ EventEmitter,
+ Input,
+ NgZone,
+ OnDestroy,
+ Output,
+ TemplateRef,
+ ViewEncapsulation
+} from '@angular/core';
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+import { combineLatest, fromEvent, BehaviorSubject, Subject } from 'rxjs';
+import { debounceTime, distinctUntilChanged, filter, map, takeUntil } from 'rxjs/operators';
+
+import { warn, InputBoolean } from 'ng-zorro-antd/core';
+
+import { CodeEditorService } from './code-editor.service';
+import { DiffEditorOptions, EditorOptions, JoinedEditorOptions, NzEditorMode } from './nz-code-editor.definitions';
+
+// Import types from monaco editor.
+import { editor } from 'monaco-editor';
+import IEditor = editor.IEditor;
+import IDiffEditor = editor.IDiffEditor;
+import ITextModel = editor.ITextModel;
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ encapsulation: ViewEncapsulation.None,
+ selector: 'zeppelin-code-editor',
+ exportAs: 'CodeEditor',
+ templateUrl: './code-editor.component.html',
+ host: {
+ '[class.ant-code-editor]': 'true'
+ },
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => CodeEditorComponent),
+ multi: true
+ }
+ ]
+})
+export class CodeEditorComponent implements OnDestroy, AfterViewInit {
+ @Input() nzEditorMode: NzEditorMode = 'normal';
+ @Input() nzOriginalText = '';
+ @Input() @InputBoolean() nzLoading = false;
+ @Input() @InputBoolean() nzFullControl = false;
+ @Input() nzToolkit: TemplateRef;
+
+ @Input() set nzEditorOption(value: JoinedEditorOptions) {
+ this.editorOption$.next(value);
+ }
+
+ @Output() readonly nzEditorInitialized = new EventEmitter();
+
+ editorOptionCached: JoinedEditorOptions = {};
+
+ private readonly el: HTMLElement;
+ private destroy$ = new Subject();
+ private resize$ = new Subject();
+ private editorOption$ = new BehaviorSubject({});
+ private editorInstance: IEditor | IDiffEditor;
+ private value = '';
+ private modelSet = false;
+
+ constructor(private nzCodeEditorService: CodeEditorService, private ngZone: NgZone, elementRef: ElementRef) {
+ this.el = elementRef.nativeElement;
+ }
+
+ /**
+ * Initialize a monaco editor instance.
+ */
+ ngAfterViewInit(): void {
+ this.nzCodeEditorService.requestToInit().subscribe(option => this.setup(option));
+ }
+
+ ngOnDestroy(): void {
+ if (this.editorInstance) {
+ this.editorInstance.dispose();
+ }
+
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+
+ writeValue(value: string): void {
+ this.value = value;
+ this.setValue();
+ }
+
+ // tslint:disable-next-line no-any
+ registerOnChange(fn: (value: string) => void): any {
+ this.onChange = fn;
+ }
+
+ // tslint:disable-next-line no-any
+ registerOnTouched(fn: any): void {
+ this.onTouch = fn;
+ }
+
+ onChange(_value: string): void {}
+
+ onTouch(): void {}
+
+ layout(): void {
+ this.resize$.next();
+ }
+
+ private setup(option: JoinedEditorOptions): void {
+ this.editorOptionCached = option;
+ this.registerOptionChanges();
+ this.initMonacoEditorInstance();
+ this.registerResizeChange();
+ this.setValue();
+
+ if (!this.nzFullControl) {
+ this.setValueEmitter();
+ }
+ this.nzEditorInitialized.emit(this.editorInstance);
+ }
+
+ private registerOptionChanges(): void {
+ combineLatest([this.editorOption$, this.nzCodeEditorService.option$])
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(([selfOpt, defaultOpt]) => {
+ this.editorOptionCached = {
+ ...this.editorOptionCached,
+ ...defaultOpt,
+ ...selfOpt
+ };
+ this.updateOptionToMonaco();
+ });
+ }
+
+ private initMonacoEditorInstance(): void {
+ this.ngZone.runOutsideAngular(() => {
+ this.editorInstance =
+ this.nzEditorMode === 'normal'
+ ? editor.create(this.el, { ...this.editorOptionCached })
+ : editor.createDiffEditor(this.el, {
+ ...(this.editorOptionCached as DiffEditorOptions)
+ });
+ });
+ }
+
+ private registerResizeChange(): void {
+ this.ngZone.runOutsideAngular(() => {
+ fromEvent(window, 'resize')
+ .pipe(
+ debounceTime(300),
+ takeUntil(this.destroy$)
+ )
+ .subscribe(() => {
+ this.layout();
+ });
+
+ this.resize$
+ .pipe(
+ takeUntil(this.destroy$),
+ filter(() => !!this.editorInstance),
+ map(() => ({
+ width: this.el.clientWidth,
+ height: this.el.clientHeight
+ })),
+ distinctUntilChanged((a, b) => a.width === b.width && a.height === b.height),
+ debounceTime(50)
+ )
+ .subscribe(() => {
+ this.editorInstance.layout();
+ });
+ });
+ }
+
+ private setValue(): void {
+ if (!this.editorInstance) {
+ return;
+ }
+
+ if (this.nzFullControl && this.value) {
+ warn(`should not set value when you are using full control mode! It would result in ambiguous data flow!`);
+ return;
+ }
+
+ if (this.nzEditorMode === 'normal') {
+ if (this.modelSet) {
+ (this.editorInstance.getModel() as ITextModel).setValue(this.value);
+ } else {
+ (this.editorInstance as IEditor).setModel(
+ editor.createModel(this.value, (this.editorOptionCached as EditorOptions).language)
+ );
+ this.modelSet = true;
+ }
+ } else {
+ if (this.modelSet) {
+ const model = (this.editorInstance as IDiffEditor).getModel()!;
+ model.modified.setValue(this.value);
+ model.original.setValue(this.nzOriginalText);
+ } else {
+ const language = (this.editorOptionCached as EditorOptions).language;
+ (this.editorInstance as IDiffEditor).setModel({
+ original: editor.createModel(this.value, language),
+ modified: editor.createModel(this.nzOriginalText, language)
+ });
+ }
+ }
+ }
+
+ private setValueEmitter(): void {
+ const model = (this.nzEditorMode === 'normal'
+ ? (this.editorInstance as IEditor).getModel()
+ : (this.editorInstance as IDiffEditor).getModel()!.modified) as ITextModel;
+
+ model.onDidChangeContent(() => {
+ this.emitValue(model.getValue());
+ });
+ }
+
+ private emitValue(value: string): void {
+ this.value = value;
+ this.onChange(value);
+ }
+
+ private updateOptionToMonaco(): void {
+ if (this.editorInstance) {
+ this.editorInstance.updateOptions({ ...this.editorOptionCached });
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/code-editor/code-editor.module.ts b/zeppelin-web-angular/src/app/share/code-editor/code-editor.module.ts
new file mode 100644
index 00000000000..6af049bc1b4
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/code-editor/code-editor.module.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzSpinModule } from 'ng-zorro-antd/spin';
+
+import { CodeEditorComponent } from './code-editor.component';
+
+@NgModule({
+ declarations: [CodeEditorComponent],
+ imports: [CommonModule, NzIconModule, NzSpinModule],
+ exports: [CodeEditorComponent]
+})
+export class CodeEditorModule {}
diff --git a/zeppelin-web-angular/src/app/share/code-editor/code-editor.service.ts b/zeppelin-web-angular/src/app/share/code-editor/code-editor.service.ts
new file mode 100644
index 00000000000..f24d9ec4d89
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/code-editor/code-editor.service.ts
@@ -0,0 +1,104 @@
+/*
+ * Licensed 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 { DOCUMENT } from '@angular/common';
+import { Inject, Injectable } from '@angular/core';
+import { of as observableOf, BehaviorSubject, Observable, Subject } from 'rxjs';
+import { map, tap } from 'rxjs/operators';
+
+import {
+ JoinedEditorOptions,
+ NzCodeEditorConfig,
+ NzCodeEditorLoadingStatus,
+ NZ_CODE_EDITOR_CONFIG
+} from './nz-code-editor.definitions';
+
+import { editor } from 'monaco-editor';
+
+// tslint:disable no-any
+function tryTriggerFunc(fn?: (...args: any[]) => any): (...args: any) => void {
+ return (...args: any[]) => {
+ if (fn) {
+ fn(...args);
+ }
+ };
+}
+// tslint:enable no-any
+
+@Injectable({
+ providedIn: 'root'
+})
+export class CodeEditorService {
+ private document: Document;
+ private firstEditorInitialized = false;
+ private loaded$ = new Subject();
+ private loadingStatus = NzCodeEditorLoadingStatus.UNLOAD;
+ private option: JoinedEditorOptions;
+
+ option$ = new BehaviorSubject(this.option);
+
+ constructor(
+ @Inject(NZ_CODE_EDITOR_CONFIG) private config: NzCodeEditorConfig,
+ @Inject(DOCUMENT) _document: any // tslint:disable-line no-any
+ ) {
+ this.document = _document;
+ this.option = this.config.defaultEditorOption || {};
+ }
+
+ // TODO(hsuanxyz): use config service later.
+ updateDefaultOption(option: JoinedEditorOptions): void {
+ this.option = { ...this.option, ...option };
+ this.option$.next(this.option);
+
+ if (option.theme) {
+ editor.setTheme(option.theme);
+ }
+ }
+
+ requestToInit(): Observable {
+ if (this.loadingStatus === NzCodeEditorLoadingStatus.LOADED) {
+ this.onInit();
+ return observableOf(this.getLatestOption());
+ }
+
+ if (this.loadingStatus === NzCodeEditorLoadingStatus.UNLOAD) {
+ this.loadingStatus = NzCodeEditorLoadingStatus.LOADED;
+ this.loaded$.next(true);
+ this.loaded$.complete();
+ this.onLoad();
+ this.onInit();
+ return observableOf(this.getLatestOption());
+ }
+
+ return this.loaded$.asObservable().pipe(
+ tap(() => this.onInit()),
+ map(() => this.getLatestOption())
+ );
+ }
+
+ private onInit(): void {
+ if (!this.firstEditorInitialized) {
+ this.firstEditorInitialized = true;
+ tryTriggerFunc(this.config.onFirstEditorInit)();
+ }
+
+ tryTriggerFunc(this.config.onInit)();
+ }
+
+ private onLoad(): void {
+ tryTriggerFunc(this.config.onLoad)();
+ }
+
+ private getLatestOption(): JoinedEditorOptions {
+ return { ...this.option };
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/code-editor/index.ts b/zeppelin-web-angular/src/app/share/code-editor/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/code-editor/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/share/code-editor/nz-code-editor.definitions.ts b/zeppelin-web-angular/src/app/share/code-editor/nz-code-editor.definitions.ts
new file mode 100644
index 00000000000..103b10db1d4
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/code-editor/nz-code-editor.definitions.ts
@@ -0,0 +1,46 @@
+/*
+ * Licensed 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 { InjectionToken } from '@angular/core';
+import { SafeUrl } from '@angular/platform-browser';
+import { editor } from 'monaco-editor';
+import IEditorConstructionOptions = editor.IEditorConstructionOptions;
+import IDiffEditorConstructionOptions = editor.IDiffEditorConstructionOptions;
+
+export type EditorOptions = IEditorConstructionOptions;
+export type DiffEditorOptions = IDiffEditorConstructionOptions;
+export type JoinedEditorOptions = EditorOptions | DiffEditorOptions;
+
+export type NzEditorMode = 'normal' | 'diff';
+
+export enum NzCodeEditorLoadingStatus {
+ UNLOAD = 'unload',
+ LOADING = 'loading',
+ LOADED = 'LOADED'
+}
+
+export interface NzCodeEditorConfig {
+ assetsRoot?: string | SafeUrl;
+ defaultEditorOption?: JoinedEditorOptions;
+ onLoad?(): void;
+ onFirstEditorInit?(): void;
+ onInit?(): void;
+}
+
+export const NZ_CODE_EDITOR_CONFIG = new InjectionToken('nz-code-editor-config', {
+ providedIn: 'root',
+ factory: NZ_CODE_EDITOR_CONFIG_FACTORY
+});
+
+export function NZ_CODE_EDITOR_CONFIG_FACTORY(): NzCodeEditorConfig {
+ return {};
+}
diff --git a/zeppelin-web-angular/src/app/share/code-editor/public-api.ts b/zeppelin-web-angular/src/app/share/code-editor/public-api.ts
new file mode 100644
index 00000000000..b2144c936c7
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/code-editor/public-api.ts
@@ -0,0 +1,16 @@
+/*
+ * Licensed 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 * from './nz-code-editor.definitions';
+export * from './code-editor.component';
+export * from './code-editor.module';
+export * from './code-editor.service';
diff --git a/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.html b/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.html
new file mode 100644
index 00000000000..71c4eac1a72
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+ The folder will be merged into {{newFolderPath}}. Are you sure?
+
diff --git a/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.less b/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.less
new file mode 100644
index 00000000000..019b5ca53b5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.less
@@ -0,0 +1,12 @@
+/*
+ * Licensed 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.
+ */
+
diff --git a/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.ts b/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.ts
new file mode 100644
index 00000000000..4b50f3be374
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/folder-rename/folder-rename.component.ts
@@ -0,0 +1,80 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
+
+import { NzModalRef } from 'ng-zorro-antd/modal';
+
+import { MessageService } from '@zeppelin/services/message.service';
+import { NoteListService } from '@zeppelin/services/note-list.service';
+
+@Component({
+ selector: 'zeppelin-folder-rename',
+ templateUrl: './folder-rename.component.html',
+ styleUrls: ['./folder-rename.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class FolderRenameComponent implements OnInit {
+ @Input() newFolderPath: string;
+ @Input() folderId: string;
+ willMerged = false;
+
+ checkMerged() {
+ const newFolderPath = this.normalizeFolderId(this.newFolderPath);
+ this.willMerged = this.folderId !== this.newFolderPath && !!this.noteListService.notes.flatFolderMap[newFolderPath];
+ this.cdr.markForCheck();
+ }
+
+ rename() {
+ this.messageService.folderRename(this.folderId, this.newFolderPath);
+ this.nzModalRef.destroy();
+ }
+
+ normalizeFolderId(folderId) {
+ let normalizeFolderId = folderId.trim();
+
+ while (normalizeFolderId.indexOf('\\') > -1) {
+ normalizeFolderId = normalizeFolderId.replace('\\', '/');
+ }
+
+ while (normalizeFolderId.indexOf('///') > -1) {
+ normalizeFolderId = normalizeFolderId.replace('///', '/');
+ }
+
+ normalizeFolderId = normalizeFolderId.replace('//', '/');
+
+ if (normalizeFolderId === '/') {
+ return '/';
+ }
+
+ if (normalizeFolderId[0] === '/') {
+ normalizeFolderId = normalizeFolderId.substring(1);
+ }
+
+ if (normalizeFolderId.slice(-1) === '/') {
+ normalizeFolderId = normalizeFolderId.slice(0, -1);
+ }
+
+ return normalizeFolderId;
+ }
+
+ constructor(
+ private noteListService: NoteListService,
+ private cdr: ChangeDetectorRef,
+ private messageService: MessageService,
+ private nzModalRef: NzModalRef
+ ) {}
+
+ ngOnInit() {
+ this.checkMerged();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/header/header.component.html b/zeppelin-web-angular/src/app/share/header/header.component.html
new file mode 100644
index 00000000000..76f93c2cef5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/header/header.component.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/share/note-import/note-import.component.less b/zeppelin-web-angular/src/app/share/note-import/note-import.component.less
new file mode 100644
index 00000000000..bb67745eec8
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/note-import/note-import.component.less
@@ -0,0 +1,19 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ nz-alert {
+ margin-top: 12px;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/share/note-import/note-import.component.ts b/zeppelin-web-angular/src/app/share/note-import/note-import.component.ts
new file mode 100644
index 00000000000..7317b341157
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/note-import/note-import.component.ts
@@ -0,0 +1,111 @@
+/*
+ * Licensed 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 { HttpClient } from '@angular/common/http';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+
+import { get } from 'lodash';
+import { NzModalRef } from 'ng-zorro-antd/modal';
+import { UploadFile } from 'ng-zorro-antd/upload';
+
+import { MessageListener, MessageListenersManager } from '@zeppelin/core';
+import { OP } from '@zeppelin/sdk';
+import { MessageService } from '@zeppelin/services/message.service';
+import { TicketService } from '@zeppelin/services/ticket.service';
+
+@Component({
+ selector: 'zeppelin-note-import',
+ templateUrl: './note-import.component.html',
+ styleUrls: ['./note-import.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NoteImportComponent extends MessageListenersManager implements OnInit {
+ noteImportName: string;
+ importUrl: string;
+ errorText: string;
+ importLoading = false;
+ maxLimit = get(this.ticketService.configuration, ['zeppelin.websocket.max.text.message.size'], null);
+
+ @MessageListener(OP.NOTES_INFO)
+ getNotes() {
+ this.nzModalRef.destroy();
+ }
+
+ importNote() {
+ this.errorText = '';
+ this.importLoading = true;
+ this.httpClient.get(this.importUrl).subscribe(
+ data => {
+ this.importLoading = false;
+ this.processImportJson(data);
+ this.cdr.markForCheck();
+ },
+ () => {
+ this.errorText = 'Unable to Fetch URL';
+ this.importLoading = false;
+ this.cdr.markForCheck();
+ },
+ () => {}
+ );
+ }
+
+ beforeUpload = (file: UploadFile): boolean => {
+ this.errorText = '';
+ if (file.size > this.maxLimit) {
+ this.errorText = 'File size limit Exceeded!';
+ } else {
+ const reader = new FileReader();
+ // tslint:disable-next-line:no-any
+ reader.readAsText(file as any);
+ reader.onloadend = () => {
+ this.processImportJson(reader.result);
+ };
+ }
+ this.cdr.markForCheck();
+ return false;
+ };
+
+ processImportJson(data) {
+ let result = data;
+ if (typeof result !== 'object') {
+ try {
+ result = JSON.parse(result);
+ } catch (e) {
+ this.errorText = 'JSON parse exception';
+ return;
+ }
+ }
+ if (result.paragraphs && result.paragraphs.length > 0) {
+ if (!this.noteImportName) {
+ this.noteImportName = result.name;
+ } else {
+ result.name = this.noteImportName;
+ }
+ this.messageService.importNote(result);
+ } else {
+ this.errorText = 'Invalid JSON';
+ }
+ this.cdr.markForCheck();
+ }
+
+ constructor(
+ private ticketService: TicketService,
+ public messageService: MessageService,
+ private cdr: ChangeDetectorRef,
+ private nzModalRef: NzModalRef,
+ private httpClient: HttpClient
+ ) {
+ super(messageService);
+ }
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.html b/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.html
new file mode 100644
index 00000000000..f37415e04c0
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.html
@@ -0,0 +1,23 @@
+
+
+
diff --git a/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.less b/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.less
new file mode 100644
index 00000000000..019b5ca53b5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.less
@@ -0,0 +1,12 @@
+/*
+ * Licensed 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.
+ */
+
diff --git a/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.ts b/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.ts
new file mode 100644
index 00000000000..1413e1db315
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/note-rename/note-rename.component.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+
+import { NzModalRef } from 'ng-zorro-antd/modal';
+
+import { MessageService } from '@zeppelin/services/message.service';
+
+@Component({
+ selector: 'zeppelin-note-rename',
+ templateUrl: './note-rename.component.html',
+ styleUrls: ['./note-rename.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class NoteRenameComponent implements OnInit {
+ @Input() newName: string;
+ @Input() id: string;
+
+ rename() {
+ this.messageService.noteRename(this.id, this.newName);
+ this.nzModalRef.destroy();
+ }
+
+ constructor(private messageService: MessageService, private nzModalRef: NzModalRef) {}
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/share/page-header/page-header.component.html b/zeppelin-web-angular/src/app/share/page-header/page-header.component.html
new file mode 100644
index 00000000000..c214ab41ff4
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/page-header/page-header.component.html
@@ -0,0 +1,18 @@
+
+
+
+
{{title}}
+
{{description}}
+
+
+
diff --git a/zeppelin-web-angular/src/app/share/page-header/page-header.component.less b/zeppelin-web-angular/src/app/share/page-header/page-header.component.less
new file mode 100644
index 00000000000..f0b6ac78b0e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/page-header/page-header.component.less
@@ -0,0 +1,17 @@
+/*
+ * Licensed 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.
+ */
+
+:host {
+ .header-extra {
+ float: right;
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/page-header/page-header.component.ts b/zeppelin-web-angular/src/app/share/page-header/page-header.component.ts
new file mode 100644
index 00000000000..0ec592531f2
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/page-header/page-header.component.ts
@@ -0,0 +1,31 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef } from '@angular/core';
+import { InputBoolean } from 'ng-zorro-antd/core';
+
+@Component({
+ selector: 'zeppelin-page-header',
+ templateUrl: './page-header.component.html',
+ styleUrls: ['./page-header.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class PageHeaderComponent implements OnInit {
+ @Input() title: string;
+ @Input() description: string | TemplateRef;
+ @Input() @InputBoolean() divider = false;
+ @Input() extra: TemplateRef;
+
+ constructor() {}
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/share/pipes/humanize-bytes.pipe.ts b/zeppelin-web-angular/src/app/share/pipes/humanize-bytes.pipe.ts
new file mode 100644
index 00000000000..5460413af8e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/pipes/humanize-bytes.pipe.ts
@@ -0,0 +1,40 @@
+/*
+ * Licensed 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 { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'humanizeBytes'
+})
+export class HumanizeBytesPipe implements PipeTransform {
+ transform(value: number): string {
+ if (value === null || value === undefined) {
+ return '-';
+ }
+ const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'];
+ const converter = (v: number, p: number): string => {
+ const base = Math.pow(1024, p);
+ if (v < base) {
+ return `${(v / base).toFixed(2)} ${units[p]}`;
+ } else if (v < base * 1000) {
+ return `${(v / base).toPrecision(3)} ${units[p]}`;
+ } else {
+ return converter(v, p + 1);
+ }
+ };
+ if (value < 1000) {
+ return value + ' B';
+ } else {
+ return converter(value, 1);
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/pipes/index.ts b/zeppelin-web-angular/src/app/share/pipes/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/pipes/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/share/pipes/public-api.ts b/zeppelin-web-angular/src/app/share/pipes/public-api.ts
new file mode 100644
index 00000000000..0397f58d260
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/pipes/public-api.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './humanize-bytes.pipe';
diff --git a/zeppelin-web-angular/src/app/share/public-api.ts b/zeppelin-web-angular/src/app/share/public-api.ts
new file mode 100644
index 00000000000..b8c2732003e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/public-api.ts
@@ -0,0 +1,15 @@
+/*
+ * Licensed 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 * from './pipes';
+export * from './resize-handle';
+export * from './share.module';
diff --git a/zeppelin-web-angular/src/app/share/resize-handle/index.ts b/zeppelin-web-angular/src/app/share/resize-handle/index.ts
new file mode 100644
index 00000000000..49e47404422
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/resize-handle/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './public-api';
diff --git a/zeppelin-web-angular/src/app/share/resize-handle/public-api.ts b/zeppelin-web-angular/src/app/share/resize-handle/public-api.ts
new file mode 100644
index 00000000000..3744d417562
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/resize-handle/public-api.ts
@@ -0,0 +1,13 @@
+/*
+ * Licensed 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 * from './resize-handle.component';
diff --git a/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.html b/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.html
new file mode 100644
index 00000000000..72092e39325
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.html
@@ -0,0 +1,23 @@
+
+
+
diff --git a/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.less b/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.less
new file mode 100644
index 00000000000..8eb1214ddb1
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.less
@@ -0,0 +1,23 @@
+/*
+ * Licensed 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.
+ */
+
+:host {
+ width: 24px;
+ height: 24px;
+ position: absolute;
+ right: 1px;
+ bottom: 1px;
+ color: #595959;
+ transition: opacity ease-out .2s;
+ cursor: se-resize;
+ z-index: 9;
+}
diff --git a/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.ts b/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.ts
new file mode 100644
index 00000000000..b9d43322d33
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/resize-handle/resize-handle.component.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component } from '@angular/core';
+
+@Component({
+ selector: 'zeppelin-resize-handle',
+ templateUrl: './resize-handle.component.html',
+ styleUrls: ['./resize-handle.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: {
+ role: 'resize-handle'
+ }
+})
+export class ResizeHandleComponent {
+ constructor() {}
+}
diff --git a/zeppelin-web-angular/src/app/share/run-scripts/run-scripts.directive.ts b/zeppelin-web-angular/src/app/share/run-scripts/run-scripts.directive.ts
new file mode 100644
index 00000000000..db6a7fc243a
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/run-scripts/run-scripts.directive.ts
@@ -0,0 +1,80 @@
+/*
+ * Licensed 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 { Directive, ElementRef, Input, NgZone, OnChanges, Renderer2, SimpleChanges } from '@angular/core';
+import { SafeHtml } from '@angular/platform-browser';
+import { take } from 'rxjs/operators';
+
+const loadedExternalScripts = new Set();
+
+@Directive({
+ selector: '[zeppelinRunScripts]'
+})
+export class RunScriptsDirective implements OnChanges {
+ @Input() scriptsContent: string | SafeHtml;
+
+ constructor(private elementRef: ElementRef, private ngZone: NgZone, private renderer: Renderer2) {}
+
+ runScripts(): void {
+ if (!this.scriptsContent.toString()) {
+ return;
+ }
+ this.ngZone.onStable.pipe(take(1)).subscribe(() => {
+ this.ngZone.runOutsideAngular(() => {
+ const scripts = this.elementRef.nativeElement.getElementsByTagName('script');
+ const externalScripts = [];
+ const localScripts = [];
+ for (let i = 0; i < scripts.length; i++) {
+ const script = scripts[i];
+ if (script.text) {
+ localScripts.push(script);
+ } else if (script.src) {
+ externalScripts.push(script);
+ }
+ this.renderer.removeChild(this.elementRef.nativeElement, script);
+ }
+ Promise.all(externalScripts.map(s => this.loadExternalScript(s, this.elementRef.nativeElement))).then(() => {
+ localScripts.forEach(s => this.loadLocalScript(s, this.elementRef.nativeElement));
+ });
+ });
+ });
+ }
+
+ loadExternalScript(script: HTMLScriptElement, parentNode: HTMLElement): Promise {
+ return new Promise(resolve => {
+ if (loadedExternalScripts.has(script.src)) {
+ resolve();
+ }
+ const scriptCopy = this.renderer.createElement('script') as HTMLScriptElement;
+ scriptCopy.type = script.type ? script.type : 'text/javascript';
+ scriptCopy.src = script.src;
+ scriptCopy.onload = () => {
+ resolve();
+ loadedExternalScripts.add(script.src);
+ };
+ parentNode.appendChild(scriptCopy);
+ });
+ }
+
+ loadLocalScript(script: HTMLScriptElement, parentNode: HTMLElement): void {
+ const scriptCopy = this.renderer.createElement('script') as HTMLScriptElement;
+ scriptCopy.type = script.type ? script.type : 'text/javascript';
+ scriptCopy.text = `(function() { ${script.text} })();`;
+ parentNode.appendChild(scriptCopy);
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes.scriptsContent) {
+ this.runScripts();
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/share.module.ts b/zeppelin-web-angular/src/app/share/share.module.ts
new file mode 100644
index 00000000000..26047e4fa8a
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/share.module.ts
@@ -0,0 +1,102 @@
+/*
+ * Licensed 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 { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { RouterModule } from '@angular/router';
+
+import { NzAlertModule } from 'ng-zorro-antd/alert';
+import { NzBadgeModule } from 'ng-zorro-antd/badge';
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzCardModule } from 'ng-zorro-antd/card';
+import { NzAddOnModule } from 'ng-zorro-antd/core';
+import { NzDividerModule } from 'ng-zorro-antd/divider';
+import { NzDropDownModule } from 'ng-zorro-antd/dropdown';
+import { NzFormModule } from 'ng-zorro-antd/form';
+import { NzGridModule } from 'ng-zorro-antd/grid';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzInputModule } from 'ng-zorro-antd/input';
+import { NzMenuModule } from 'ng-zorro-antd/menu';
+import { NzMessageModule } from 'ng-zorro-antd/message';
+import { NzModalModule } from 'ng-zorro-antd/modal';
+import { NzNotificationModule } from 'ng-zorro-antd/notification';
+import { NzPopconfirmModule } from 'ng-zorro-antd/popconfirm';
+import { NzProgressModule } from 'ng-zorro-antd/progress';
+import { NzSelectModule } from 'ng-zorro-antd/select';
+import { NzTabsModule } from 'ng-zorro-antd/tabs';
+import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
+import { NzTreeModule } from 'ng-zorro-antd/tree';
+import { NzUploadModule } from 'ng-zorro-antd/upload';
+
+import { AboutZeppelinComponent } from '@zeppelin/share/about-zeppelin/about-zeppelin.component';
+import { CodeEditorModule } from '@zeppelin/share/code-editor';
+import { FolderRenameComponent } from '@zeppelin/share/folder-rename/folder-rename.component';
+import { HeaderComponent } from '@zeppelin/share/header/header.component';
+import { MathJaxDirective } from '@zeppelin/share/math-jax/math-jax.directive';
+import { NodeListComponent } from '@zeppelin/share/node-list/node-list.component';
+import { NoteCreateComponent } from '@zeppelin/share/note-create/note-create.component';
+import { NoteImportComponent } from '@zeppelin/share/note-import/note-import.component';
+import { NoteRenameComponent } from '@zeppelin/share/note-rename/note-rename.component';
+import { PageHeaderComponent } from '@zeppelin/share/page-header/page-header.component';
+import { HumanizeBytesPipe } from '@zeppelin/share/pipes';
+import { RunScriptsDirective } from '@zeppelin/share/run-scripts/run-scripts.directive';
+import { SpinComponent } from '@zeppelin/share/spin/spin.component';
+import { Ng1MigrationComponent } from './ng1-migration/ng1-migration.component';
+import { ResizeHandleComponent } from './resize-handle';
+
+const MODAL_LIST = [
+ AboutZeppelinComponent,
+ NoteImportComponent,
+ NoteCreateComponent,
+ NoteRenameComponent,
+ FolderRenameComponent,
+ Ng1MigrationComponent
+];
+const EXPORT_LIST = [HeaderComponent, NodeListComponent, PageHeaderComponent, SpinComponent, ResizeHandleComponent];
+const PIPES = [HumanizeBytesPipe];
+
+@NgModule({
+ declarations: [MODAL_LIST, EXPORT_LIST, PIPES, MathJaxDirective, RunScriptsDirective],
+ entryComponents: [MODAL_LIST],
+ exports: [EXPORT_LIST, PIPES, MathJaxDirective, RunScriptsDirective, CodeEditorModule],
+ imports: [
+ FormsModule,
+ CommonModule,
+ NzMenuModule,
+ NzAddOnModule,
+ NzIconModule,
+ NzInputModule,
+ NzDropDownModule,
+ NzBadgeModule,
+ NzGridModule,
+ NzModalModule,
+ NzTreeModule,
+ RouterModule,
+ NzButtonModule,
+ NzNotificationModule,
+ NzToolTipModule,
+ NzDividerModule,
+ NzMessageModule,
+ NzCardModule,
+ NzPopconfirmModule,
+ NzPopconfirmModule,
+ NzFormModule,
+ NzTabsModule,
+ NzUploadModule,
+ NzSelectModule,
+ NzAlertModule,
+ NzProgressModule,
+ CodeEditorModule
+ ]
+})
+export class ShareModule {}
diff --git a/zeppelin-web-angular/src/app/share/spin/spin.component.html b/zeppelin-web-angular/src/app/share/spin/spin.component.html
new file mode 100644
index 00000000000..81b188308b1
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/spin/spin.component.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
Zeppelin
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/share/spin/spin.component.less b/zeppelin-web-angular/src/app/share/spin/spin.component.less
new file mode 100644
index 00000000000..019b5ca53b5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/spin/spin.component.less
@@ -0,0 +1,12 @@
+/*
+ * Licensed 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.
+ */
+
diff --git a/zeppelin-web-angular/src/app/share/spin/spin.component.ts b/zeppelin-web-angular/src/app/share/spin/spin.component.ts
new file mode 100644
index 00000000000..34e7cedd01a
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/spin/spin.component.ts
@@ -0,0 +1,26 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+
+@Component({
+ selector: 'zeppelin-spin',
+ templateUrl: './spin.component.html',
+ styleUrls: ['./spin.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class SpinComponent implements OnInit {
+ @Input() transparent = false;
+ constructor() {}
+
+ ngOnInit() {}
+}
diff --git a/zeppelin-web-angular/src/app/spell/spell-result.ts b/zeppelin-web-angular/src/app/spell/spell-result.ts
new file mode 100644
index 00000000000..50051d2e94a
--- /dev/null
+++ b/zeppelin-web-angular/src/app/spell/spell-result.ts
@@ -0,0 +1,27 @@
+/*
+ * Licensed 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 class SpellResult {
+ static extractMagic(allParagraphText) {
+ const pattern = /^\s*%(\S+)\s*/g;
+ try {
+ const match = pattern.exec(allParagraphText);
+ if (match) {
+ return `%${match[1].trim()}`;
+ }
+ } catch (error) {
+ // failed to parse, ignore
+ }
+
+ return undefined;
+ }
+}
diff --git a/zeppelin-web-angular/src/app/utility/css-unit-conversion.ts b/zeppelin-web-angular/src/app/utility/css-unit-conversion.ts
new file mode 100644
index 00000000000..19d4057f45c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/utility/css-unit-conversion.ts
@@ -0,0 +1,15 @@
+/*
+ * Licensed 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 function pt2px(pt: number): number {
+ return pt / (3 / 4);
+}
diff --git a/zeppelin-web-angular/src/app/utility/element.ts b/zeppelin-web-angular/src/app/utility/element.ts
new file mode 100644
index 00000000000..cdae6de0045
--- /dev/null
+++ b/zeppelin-web-angular/src/app/utility/element.ts
@@ -0,0 +1,19 @@
+/*
+ * Licensed 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 function scrollIntoViewIfNeeded(element: HTMLElement, center = true): void {
+ // tslint:disable-next-line:no-any
+ if (element && typeof (element as any).scrollIntoViewIfNeeded === 'function') {
+ // tslint:disable-next-line:no-any
+ (element as any).scrollIntoViewIfNeeded(center);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/utility/get-keyword-positions.ts b/zeppelin-web-angular/src/app/utility/get-keyword-positions.ts
new file mode 100644
index 00000000000..c2eb7aa3965
--- /dev/null
+++ b/zeppelin-web-angular/src/app/utility/get-keyword-positions.ts
@@ -0,0 +1,43 @@
+/*
+ * Licensed 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 { computeLineStartsMap, getLineAndCharacterFromPosition } from '@zeppelin/utility/line-map';
+
+export interface KeywordPosition {
+ line: number;
+ character: number;
+ length: number;
+}
+
+export function getKeywordPositions(keywords: string[], str: string): KeywordPosition[] {
+ const highlightPositions = [];
+ const lineMap = computeLineStartsMap(str);
+
+ keywords.forEach((keyword: string) => {
+ const positions = [];
+ const keywordReg = new RegExp(keyword, 'ig');
+ let posMatch = keywordReg.exec(str);
+
+ while (posMatch !== null) {
+ const { line, character } = getLineAndCharacterFromPosition(lineMap, posMatch.index);
+ positions.push({
+ line,
+ character,
+ length: keyword.length
+ });
+ posMatch = keywordReg.exec(str);
+ }
+ highlightPositions.push(...positions);
+ });
+
+ return highlightPositions;
+}
diff --git a/zeppelin-web-angular/src/app/utility/line-map.ts b/zeppelin-web-angular/src/app/utility/line-map.ts
new file mode 100644
index 00000000000..30c40793c81
--- /dev/null
+++ b/zeppelin-web-angular/src/app/utility/line-map.ts
@@ -0,0 +1,60 @@
+/*
+ * Licensed 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.
+ */
+
+const LF_CHAR = 10;
+const CR_CHAR = 13;
+const LINE_SEP_CHAR = 8232;
+const PARAGRAPH_CHAR = 8233;
+
+export function computeLineStartsMap(text) {
+ const result = [0];
+ let pos = 0;
+ while (pos < text.length) {
+ const char = text.charCodeAt(pos++);
+ // Handles the "CRLF" line break. In that case we peek the character
+ // after the "CR" and check if it is a line feed.
+ if (char === CR_CHAR) {
+ if (text.charCodeAt(pos) === LF_CHAR) {
+ pos++;
+ }
+ result.push(pos);
+ } else if (char === LF_CHAR || char === LINE_SEP_CHAR || char === PARAGRAPH_CHAR) {
+ result.push(pos);
+ }
+ }
+ result.push(pos);
+ return result;
+}
+
+function findClosestLineStartPosition(linesMap, position, low = 0, high = linesMap.length - 1) {
+ let _low = low;
+ let _high = high;
+ while (_low <= _high) {
+ const pivotIdx = Math.floor((_low + _high) / 2);
+ const pivotEl = linesMap[pivotIdx];
+ if (pivotEl === position) {
+ return pivotIdx;
+ } else if (position > pivotEl) {
+ _low = pivotIdx + 1;
+ } else {
+ _high = pivotIdx - 1;
+ }
+ }
+ // In case there was no exact match, return the closest "lower" line index. We also
+ // subtract the index by one because want the index of the previous line start.
+ return _low - 1;
+}
+
+export function getLineAndCharacterFromPosition(lineStartsMap, position) {
+ const lineIndex = findClosestLineStartPosition(lineStartsMap, position);
+ return { character: position - lineStartsMap[lineIndex], line: lineIndex };
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.html b/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.html
new file mode 100644
index 00000000000..4b4caf1d140
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+ View
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.less b/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.less
new file mode 100644
index 00000000000..b8beb4c3cb5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.less
@@ -0,0 +1,15 @@
+/*
+ * Licensed 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.
+ */
+
+.area-chart-setting {
+ margin: 10px 0;
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.ts b/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.ts
new file mode 100644
index 00000000000..07ffb22cc4c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.component.ts
@@ -0,0 +1,102 @@
+/*
+ * Licensed 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 {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ Inject,
+ OnInit,
+ ViewChild
+} from '@angular/core';
+
+import { G2VisualizationComponentBase, Visualization, VISUALIZATION } from '@zeppelin/visualization';
+
+import { VisualizationPivotSettingComponent } from '../common/pivot-setting/pivot-setting.component';
+import { calcTickCount } from '../common/util/calc-tick-count';
+import { setChartXAxis } from '../common/util/set-x-axis';
+import { VisualizationXAxisSettingComponent } from '../common/x-axis-setting/x-axis-setting.component';
+
+@Component({
+ selector: 'zeppelin-area-chart-visualization',
+ templateUrl: './area-chart-visualization.component.html',
+ styleUrls: ['./area-chart-visualization.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class AreaChartVisualizationComponent extends G2VisualizationComponentBase implements OnInit, AfterViewInit {
+ @ViewChild('container', { static: false }) container: ElementRef;
+ @ViewChild(VisualizationXAxisSettingComponent, { static: false })
+ xAxisSettingComponent: VisualizationXAxisSettingComponent;
+ @ViewChild(VisualizationPivotSettingComponent, { static: false })
+ pivotSettingComponent: VisualizationPivotSettingComponent;
+ style: 'stream' | 'expand' | 'stack' = 'stack';
+
+ constructor(@Inject(VISUALIZATION) public visualization: Visualization, private cdr: ChangeDetectorRef) {
+ super(visualization);
+ }
+
+ viewChange() {
+ this.config.setting.stackedAreaChart.style = this.style;
+ this.visualization.configChange$.next(this.config);
+ }
+
+ ngOnInit() {}
+
+ refreshSetting() {
+ this.style = this.config.setting.stackedAreaChart.style;
+ this.pivotSettingComponent.init();
+ this.xAxisSettingComponent.init();
+ this.cdr.markForCheck();
+ }
+
+ ngAfterViewInit(): void {
+ this.render();
+ }
+
+ setScale() {
+ const key = this.getKey();
+ const tickCount = calcTickCount(this.container.nativeElement);
+ this.chart.scale(key, {
+ tickCount,
+ type: 'cat'
+ });
+ }
+
+ renderBefore() {
+ const key = this.getKey();
+ this.setScale();
+ if (this.style === 'stack') {
+ // area:stack
+ this.chart
+ .areaStack()
+ .position(`${key}*__value__`)
+ .color('__key__');
+ } else if (this.style === 'stream') {
+ // area:stream
+ this.chart
+ .area()
+ .position(`${key}*__value__`)
+ .adjust(['stack', 'symmetric'])
+ .color('__key__');
+ } else {
+ // area:percent
+ this.chart
+ .areaStack()
+ .position(`${key}*__percent__`)
+ .color('__key__');
+ }
+
+ setChartXAxis(this.visualization, 'stackedAreaChart', this.chart, key);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.ts b/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.ts
new file mode 100644
index 00000000000..a7f1b481c69
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/area-chart/area-chart-visualization.ts
@@ -0,0 +1,32 @@
+/*
+ * Licensed 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 { CdkPortalOutlet } from '@angular/cdk/portal';
+import { ViewContainerRef } from '@angular/core';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { G2VisualizationBase, VisualizationComponentPortal } from '@zeppelin/visualization';
+
+import { AreaChartVisualizationComponent } from './area-chart-visualization.component';
+
+export class AreaChartVisualization extends G2VisualizationBase {
+ componentPortal = new VisualizationComponentPortal(
+ this,
+ AreaChartVisualizationComponent,
+ this.portalOutlet,
+ this.viewContainerRef
+ );
+
+ constructor(config: GraphConfig, private portalOutlet: CdkPortalOutlet, private viewContainerRef: ViewContainerRef) {
+ super(config);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.html b/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.html
new file mode 100644
index 00000000000..eb798668215
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+ View
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.less b/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.less
new file mode 100644
index 00000000000..cd842d1ed23
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.less
@@ -0,0 +1,25 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ bar-chart-setting {
+ margin: 10px 0;
+ }
+ .field-setting-wrap {
+ margin-top: 10px;
+ }
+ .drag-wrap {
+ min-height: 23px;
+ }
+});
diff --git a/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.ts b/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.ts
new file mode 100644
index 00000000000..22944451cb2
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.component.ts
@@ -0,0 +1,110 @@
+/*
+ * Licensed 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 {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ Inject,
+ OnInit,
+ ViewChild
+} from '@angular/core';
+
+import { get } from 'lodash';
+
+import { VisualizationMultiBarChart } from '@zeppelin/sdk';
+import { G2VisualizationComponentBase, Visualization, VISUALIZATION } from '@zeppelin/visualization';
+
+import { VisualizationPivotSettingComponent } from '../common/pivot-setting/pivot-setting.component';
+import { calcTickCount } from '../common/util/calc-tick-count';
+import { setChartXAxis } from '../common/util/set-x-axis';
+import { VisualizationXAxisSettingComponent } from '../common/x-axis-setting/x-axis-setting.component';
+
+@Component({
+ selector: 'zeppelin-bar-chart-visualization',
+ templateUrl: './bar-chart-visualization.component.html',
+ styleUrls: ['./bar-chart-visualization.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class BarChartVisualizationComponent extends G2VisualizationComponentBase implements OnInit, AfterViewInit {
+ @ViewChild('container', { static: false }) container: ElementRef;
+ @ViewChild(VisualizationXAxisSettingComponent, { static: false })
+ xAxisSettingComponent: VisualizationXAxisSettingComponent;
+ @ViewChild(VisualizationPivotSettingComponent, { static: false })
+ pivotSettingComponent: VisualizationPivotSettingComponent;
+ stacked = false;
+
+ viewChange() {
+ if (!this.config.setting.multiBarChart) {
+ this.config.setting.multiBarChart = new VisualizationMultiBarChart();
+ }
+ this.config.setting.multiBarChart.stacked = this.stacked;
+ this.visualization.configChange$.next(this.config);
+ }
+
+ constructor(@Inject(VISUALIZATION) public visualization: Visualization, private cdr: ChangeDetectorRef) {
+ super(visualization);
+ }
+
+ ngOnInit() {}
+
+ ngAfterViewInit() {
+ this.render();
+ }
+
+ refreshSetting() {
+ this.stacked = get(this.config.setting, 'multiBarChart.stacked', false);
+ this.pivotSettingComponent.init();
+ this.xAxisSettingComponent.init();
+ this.cdr.markForCheck();
+ }
+
+ setScale() {
+ const key = this.getKey();
+ const tickCount = calcTickCount(this.container.nativeElement);
+ this.chart.scale(key, {
+ tickCount,
+ type: 'cat'
+ });
+ }
+
+ renderBefore(chart) {
+ const key = this.getKey();
+ this.setScale();
+
+ this.chart.tooltip({
+ shared: false
+ });
+ if (get(this.config.setting, 'multiBarChart.stacked', false)) {
+ this.chart
+ .intervalStack()
+ .position(`${key}*__value__`)
+ .color('__key__')
+ .opacity(1);
+ } else {
+ this.chart
+ .interval()
+ .position(`${key}*__value__`)
+ .color('__key__')
+ .opacity(1)
+ .adjust([
+ {
+ type: 'dodge',
+ marginRatio: 0
+ }
+ ]);
+ }
+ setChartXAxis(this.visualization, 'multiBarChart', this.chart, key);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.ts b/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.ts
new file mode 100644
index 00000000000..736e43dadd1
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/bar-chart/bar-chart-visualization.ts
@@ -0,0 +1,31 @@
+/*
+ * Licensed 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 { CdkPortalOutlet } from '@angular/cdk/portal';
+import { ViewContainerRef } from '@angular/core';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { G2VisualizationBase, VisualizationComponentPortal } from '@zeppelin/visualization';
+
+import { BarChartVisualizationComponent } from './bar-chart-visualization.component';
+
+export class BarChartVisualization extends G2VisualizationBase {
+ componentPortal = new VisualizationComponentPortal(
+ this,
+ BarChartVisualizationComponent,
+ this.portalOutlet,
+ this.viewContainerRef
+ );
+ constructor(config: GraphConfig, private portalOutlet: CdkPortalOutlet, private viewContainerRef: ViewContainerRef) {
+ super(config);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.html b/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.html
new file mode 100644
index 00000000000..fb1497f43db
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.html
@@ -0,0 +1,85 @@
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+
+ {{item.name}} {{item.aggr | uppercase}}
+
+
+
+
+ {{aggregate}}
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.less b/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.less
new file mode 100644
index 00000000000..9091c367751
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.less
@@ -0,0 +1,33 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ display: block;
+ margin: 10px 0;
+ .field-setting-wrap {
+ margin-top: 24px;
+ }
+ .drag-wrap {
+ min-height: 23px;
+ }
+ nz-card {
+ background: #fff;
+ ::ng-deep {
+ .ant-card-head {
+ padding: 0 12px;
+ background: #fafafa;
+ }
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.ts b/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.ts
new file mode 100644
index 00000000000..12abf17c79c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/pivot-setting/pivot-setting.component.ts
@@ -0,0 +1,89 @@
+/*
+ * Licensed 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 { copyArrayItem, moveItemInArray, transferArrayItem, CdkDragDrop } from '@angular/cdk/drag-drop';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { TableData, Visualization } from '@zeppelin/visualization';
+
+@Component({
+ selector: 'zeppelin-visualization-pivot-setting',
+ templateUrl: './pivot-setting.component.html',
+ styleUrls: ['./pivot-setting.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class VisualizationPivotSettingComponent implements OnInit {
+ @Input() visualization: Visualization;
+
+ tableData: TableData;
+ config: GraphConfig;
+ columns = [];
+ aggregates = ['sum', 'count', 'avg', 'min', 'max'];
+
+ // tslint:disable-next-line
+ drop(event: CdkDragDrop) {
+ if (event.container.id === 'columns-list') {
+ return;
+ }
+ if (event.previousContainer === event.container) {
+ moveItemInArray(event.container.data, event.previousIndex, event.currentIndex);
+ } else {
+ if (
+ event.container.id !== 'value-list' &&
+ event.container.data.findIndex(e => e.name === event.previousContainer.data[event.previousIndex].name) !== -1
+ ) {
+ return;
+ }
+ if (event.previousContainer.id === 'columns-list') {
+ copyArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
+ } else {
+ transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex);
+ }
+ }
+ this.visualization.configChange$.next(this.config);
+ }
+
+ // tslint:disable-next-line
+ removeFieldAt(data: any[], index: number): void {
+ data.splice(index, 1);
+ this.visualization.configChange$.next(this.config);
+ this.cdr.markForCheck();
+ }
+
+ changeAggregate(aggregates: string, index: number): void {
+ this.config.values[index].aggr = aggregates;
+ this.visualization.configChange$.next(this.config);
+ this.cdr.markForCheck();
+ }
+
+ noReturnPredicate() {
+ return false;
+ }
+
+ init() {
+ this.tableData = this.visualization.getTransformation().getTableData() as TableData;
+ this.config = this.visualization.getConfig();
+ this.columns = this.tableData.columns.map((name, index) => ({
+ name,
+ index,
+ aggr: 'sum'
+ }));
+ this.cdr.markForCheck();
+ }
+
+ constructor(private cdr: ChangeDetectorRef) {}
+
+ ngOnInit() {
+ this.init();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.html b/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.html
new file mode 100644
index 00000000000..7333cee57ba
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.html
@@ -0,0 +1,98 @@
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.less b/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.less
new file mode 100644
index 00000000000..35d9c4e3892
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.less
@@ -0,0 +1,33 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ display: block;
+ margin: 10px 0;
+ .field-setting-wrap {
+ margin-top: 10px;
+ }
+ .drag-wrap {
+ min-height: 23px;
+ }
+ nz-card {
+ background: #fff;
+ ::ng-deep {
+ .ant-card-head {
+ padding: 0 12px;
+ background: #fafafa;
+ }
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.ts b/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.ts
new file mode 100644
index 00000000000..e154549e43e
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/scatter-setting/scatter-setting.component.ts
@@ -0,0 +1,102 @@
+/*
+ * Licensed 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 { CdkDragDrop } from '@angular/cdk/drag-drop';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
+
+import { get } from 'lodash';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { TableData, Visualization } from '@zeppelin/visualization';
+
+@Component({
+ selector: 'zeppelin-visualization-scatter-setting',
+ templateUrl: './scatter-setting.component.html',
+ styleUrls: ['./scatter-setting.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class VisualizationScatterSettingComponent implements OnInit {
+ @Input() visualization: Visualization;
+
+ tableData: TableData;
+ config: GraphConfig;
+ columns = [];
+
+ field = {
+ xAxis: [],
+ yAxis: [],
+ group: [],
+ size: []
+ };
+
+ // tslint:disable-next-line
+ drop(event: CdkDragDrop) {
+ this.clean(event.container.data, false);
+ event.container.data.push(event.previousContainer.data[event.previousIndex]);
+ this.cdr.markForCheck();
+ this.updateConfig();
+ }
+
+ // tslint:disable-next-line
+ clean(data: any[], update = true): void {
+ while (data.length > 0) {
+ data.splice(0, 1);
+ }
+ if (update) {
+ this.updateConfig();
+ }
+ this.cdr.markForCheck();
+ }
+
+ noReturnPredicate() {
+ return false;
+ }
+
+ updateConfig() {
+ if (!this.config.setting.scatterChart) {
+ this.config.setting.scatterChart = {};
+ }
+ const scatterSetting = this.config.setting.scatterChart;
+ scatterSetting.xAxis = this.field.xAxis[0];
+ scatterSetting.yAxis = this.field.yAxis[0];
+ scatterSetting.size = this.field.size[0];
+ scatterSetting.group = this.field.group[0];
+ this.visualization.configChange$.next(this.config);
+ }
+
+ constructor(private cdr: ChangeDetectorRef) {}
+
+ init() {
+ this.tableData = this.visualization.getTransformation().getTableData() as TableData;
+ this.config = this.visualization.getConfig();
+ this.columns = this.tableData.columns.map((name, index) => ({
+ name,
+ index,
+ aggr: 'sum'
+ }));
+
+ const xAxis = get(this.config.setting, 'scatterChart.xAxis', this.columns[0]);
+ const yAxis = get(this.config.setting, 'scatterChart.yAxis', this.columns[1]);
+ const group = get(this.config.setting, 'scatterChart.group');
+ const size = get(this.config.setting, 'scatterChart.size');
+ const arrayWrapper = value => (value ? [value] : []);
+ this.field.xAxis = arrayWrapper(xAxis);
+ this.field.yAxis = arrayWrapper(yAxis);
+ this.field.group = arrayWrapper(group);
+ this.field.size = arrayWrapper(size);
+ this.cdr.markForCheck();
+ }
+
+ ngOnInit() {
+ this.init();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/common/util/calc-tick-count.ts b/zeppelin-web-angular/src/app/visualizations/common/util/calc-tick-count.ts
new file mode 100644
index 00000000000..bfa81a8df81
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/util/calc-tick-count.ts
@@ -0,0 +1,22 @@
+/*
+ * Licensed 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 DEFAULT_TICK_COUNT = 16;
+
+export function calcTickCount(el: HTMLElement) {
+ if (el && el.getBoundingClientRect) {
+ const tickCount = Math.round(el.getBoundingClientRect().width / 60);
+ return Number.isNaN(tickCount) ? DEFAULT_TICK_COUNT : tickCount;
+ } else {
+ return DEFAULT_TICK_COUNT;
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/common/util/set-x-axis.ts b/zeppelin-web-angular/src/app/visualizations/common/util/set-x-axis.ts
new file mode 100644
index 00000000000..e41ad7983a0
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/util/set-x-axis.ts
@@ -0,0 +1,46 @@
+/*
+ * Licensed 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 * as G2 from '@antv/g2';
+import { get } from 'lodash';
+
+import { Visualization } from '@zeppelin/visualization';
+
+export function setChartXAxis(
+ visualization: Visualization,
+ mode: 'lineChart' | 'multiBarChart' | 'stackedAreaChart',
+ chart: G2.Chart,
+ key: string
+) {
+ const config = visualization.getConfig();
+ const setting = config.setting[mode];
+ chart.axis(key, {
+ label: {
+ textStyle: {
+ rotate: 0
+ }
+ }
+ });
+ switch (setting.xLabelStatus) {
+ case 'hide':
+ chart.axis(key, false);
+ break;
+ case 'rotate':
+ chart.axis(key, {
+ label: {
+ textStyle: {
+ rotate: Number.parseInt(get(setting, 'rotate.degree', '-45'), 10)
+ }
+ }
+ });
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.html b/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.html
new file mode 100644
index 00000000000..cd11081ab04
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.html
@@ -0,0 +1,38 @@
+
+
+
diff --git a/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.less b/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.less
new file mode 100644
index 00000000000..3e7f1f963ba
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.less
@@ -0,0 +1,18 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ display: block;
+ margin: 10px 0;
+});
diff --git a/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.ts b/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.ts
new file mode 100644
index 00000000000..0a5d5460de7
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/common/x-axis-setting/x-axis-setting.component.ts
@@ -0,0 +1,78 @@
+/*
+ * Licensed 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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
+
+import { get } from 'lodash';
+
+import { GraphConfig, XAxisSetting, XLabelStatus } from '@zeppelin/sdk';
+import { Visualization } from '@zeppelin/visualization';
+
+@Component({
+ selector: 'zeppelin-visualization-x-axis-setting',
+ templateUrl: './x-axis-setting.component.html',
+ styleUrls: ['./x-axis-setting.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class VisualizationXAxisSettingComponent implements OnInit {
+ @Input() visualization: Visualization;
+ @Input() mode: 'lineChart' | 'multiBarChart' | 'stackedAreaChart';
+
+ setting: XAxisSetting;
+ config: GraphConfig;
+ xLabelStatus: XLabelStatus = 'default';
+ degree = '-45';
+ previousDegree: string;
+ constructor(private cdr: ChangeDetectorRef) {}
+
+ onStatusChange() {
+ this.setting.xLabelStatus = this.xLabelStatus;
+ this.updateConfig();
+ }
+
+ onDegreeChange() {
+ if (this.degree === this.previousDegree) {
+ return;
+ }
+ const degree = Number.parseInt(this.degree, 10);
+ if (Number.isNaN(degree)) {
+ this.degree = this.previousDegree;
+ return;
+ } else {
+ this.degree = `${degree}`;
+ this.previousDegree = this.degree;
+ }
+ this.updateConfig();
+ }
+
+ updateConfig() {
+ this.setting.rotate.degree = this.degree;
+ this.setting.xLabelStatus = this.xLabelStatus;
+ this.visualization.configChange$.next(this.config);
+ }
+
+ init() {
+ this.config = this.visualization.getConfig();
+ this.setting = this.config.setting[this.mode];
+ if (!this.setting.rotate) {
+ this.setting.rotate = { degree: '-45' };
+ }
+ this.xLabelStatus = get(this.setting, ['xLabelStatus'], 'default');
+ this.degree = get(this.setting, ['rotate', 'degree'], '-45');
+ this.previousDegree = this.degree;
+ this.cdr.markForCheck();
+ }
+
+ ngOnInit() {
+ this.init();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/g2.config.ts b/zeppelin-web-angular/src/app/visualizations/g2.config.ts
new file mode 100644
index 00000000000..622d25ddb74
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/g2.config.ts
@@ -0,0 +1,129 @@
+/*
+ * Licensed 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 * as G2 from '@antv/g2';
+
+const DEFAULT_COLOR = '#03578c';
+const COLOR_PLATE_8 = ['#03578c', '#179bd4', '#bf4f07', '#005041', '#8543E0', '#57c2e9', '#03138c', '#8c0357'];
+
+const COLOR_PLATE_16 = [
+ '#03588C',
+ '#82B8D9',
+ '#025959',
+ '#FACC14',
+ '#E6965C',
+ '#223273',
+ '#7564CC',
+ '#8543E0',
+ '#5C8EE6',
+ '#13C2C2',
+ '#5CA3E6',
+ '#3436C7',
+ '#B381E6',
+ '#F04864',
+ '#D598D9'
+];
+const COLOR_PLATE_24 = [
+ '#03588C',
+ '#66B5FF',
+ '#82B8D9',
+ '#025959',
+ '#027368',
+ '#9AE65C',
+ '#FACC14',
+ '#E6965C',
+ '#57AD71',
+ '#223273',
+ '#738AE6',
+ '#7564CC',
+ '#8543E0',
+ '#A877ED',
+ '#5C8EE6',
+ '#13C2C2',
+ '#70E0E0',
+ '#5CA3E6',
+ '#3436C7',
+ '#8082FF',
+ '#DD81E6',
+ '#F04864',
+ '#FA7D92',
+ '#D598D9'
+];
+const COLOR_PIE = ['#03588C', '#13C2C2', '#025959', '#FACC14', '#F04864', '#8543E0', '#3436C7', '#223273'];
+const COLOR_PIE_16 = [
+ '#03588C',
+ '#73C9E6',
+ '#13C2C2',
+ '#6CD9B3',
+ '#025959',
+ '#9DD96C',
+ '#FACC14',
+ '#E6965C',
+ '#F04864',
+ '#D66BCA',
+ '#8543E0',
+ '#8E77ED',
+ '#3436C7',
+ '#737EE6',
+ '#223273',
+ '#7EA2E6'
+];
+
+const zeppelinTheme = {
+ defaultColor: DEFAULT_COLOR,
+ colors: COLOR_PLATE_8,
+ colors_16: COLOR_PLATE_16,
+ colors_24: COLOR_PLATE_24,
+ colors_pie: COLOR_PIE,
+ colors_pie_16: COLOR_PIE_16,
+ shape: {
+ point: {
+ fill: DEFAULT_COLOR
+ },
+ hollowPoint: {
+ stroke: DEFAULT_COLOR
+ },
+ interval: {
+ fill: DEFAULT_COLOR
+ },
+ hollowInterval: {
+ stroke: DEFAULT_COLOR
+ },
+ area: {
+ fill: DEFAULT_COLOR
+ },
+ polygon: {
+ fill: DEFAULT_COLOR
+ },
+ hollowPolygon: {
+ stroke: DEFAULT_COLOR
+ },
+ hollowArea: {
+ stroke: DEFAULT_COLOR
+ },
+ line: {
+ stroke: DEFAULT_COLOR
+ },
+ edge: {
+ stroke: DEFAULT_COLOR
+ },
+ schema: {
+ stroke: DEFAULT_COLOR
+ }
+ }
+};
+
+export function setTheme() {
+ const theme = G2.Util.deepMix(G2.Global, zeppelinTheme);
+ // tslint:disable-next-line:no-any
+ (G2.Global as any).setTheme(theme);
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.html b/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.html
new file mode 100644
index 00000000000..cb8faab7ed4
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.less b/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.less
new file mode 100644
index 00000000000..8e84bc5be30
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.less
@@ -0,0 +1,25 @@
+/*
+ * Licensed 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 "theme-mixin";
+
+.themeMixin({
+ .zoom-tips {
+ color: @text-color-secondary;
+ }
+ .line-setting {
+ margin: 10px 0;
+ input.format-input {
+ width: 160px;
+ }
+ }
+});
diff --git a/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.ts b/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.ts
new file mode 100644
index 00000000000..02938e55327
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.component.ts
@@ -0,0 +1,137 @@
+/*
+ * Licensed 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 {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ Inject,
+ OnInit,
+ ViewChild
+} from '@angular/core';
+
+import { G2VisualizationComponentBase, Visualization, VISUALIZATION } from '@zeppelin/visualization';
+
+import { VisualizationPivotSettingComponent } from '../common/pivot-setting/pivot-setting.component';
+import { calcTickCount } from '../common/util/calc-tick-count';
+import { setChartXAxis } from '../common/util/set-x-axis';
+import { VisualizationXAxisSettingComponent } from '../common/x-axis-setting/x-axis-setting.component';
+
+@Component({
+ selector: 'zeppelin-line-chart-visualization',
+ templateUrl: './line-chart-visualization.component.html',
+ styleUrls: ['./line-chart-visualization.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class LineChartVisualizationComponent extends G2VisualizationComponentBase implements OnInit, AfterViewInit {
+ @ViewChild('container', { static: false }) container: ElementRef;
+ @ViewChild(VisualizationXAxisSettingComponent, { static: false })
+ xAxisSettingComponent: VisualizationXAxisSettingComponent;
+ @ViewChild(VisualizationPivotSettingComponent, { static: false })
+ pivotSettingComponent: VisualizationPivotSettingComponent;
+ forceY = false;
+ lineWithFocus = false;
+ isDateFormat = false;
+ dateFormat = '';
+
+ settingChange(): void {
+ const setting = this.config.setting.lineChart;
+ setting.lineWithFocus = this.lineWithFocus;
+ setting.forceY = this.forceY;
+ setting.isDateFormat = this.isDateFormat;
+ setting.dateFormat = this.dateFormat;
+ this.visualization.configChange$.next(this.config);
+ }
+
+ constructor(@Inject(VISUALIZATION) public visualization: Visualization, private cdr: ChangeDetectorRef) {
+ super(visualization);
+ }
+
+ ngOnInit() {}
+
+ refreshSetting() {
+ const setting = this.config.setting.lineChart;
+ this.forceY = setting.forceY || false;
+ this.lineWithFocus = setting.lineWithFocus || false;
+ this.isDateFormat = setting.isDateFormat || false;
+ this.dateFormat = setting.dateFormat || '';
+ this.pivotSettingComponent.init();
+ this.xAxisSettingComponent.init();
+ this.cdr.markForCheck();
+ }
+
+ setScale() {
+ const key = this.getKey();
+ const tickCount = calcTickCount(this.container.nativeElement);
+ this.chart.scale(key, {
+ tickCount,
+ type: 'cat'
+ });
+ }
+
+ renderBefore() {
+ const key = this.getKey();
+ const setting = this.config.setting.lineChart;
+ this.setScale();
+ this.chart
+ .line()
+ .position(`${key}*__value__`)
+ .color('__key__');
+ setChartXAxis(this.visualization, 'lineChart', this.chart, key);
+
+ if (setting.isDateFormat) {
+ if (this.visualization.transformed && this.visualization.transformed.rows) {
+ const invalid = this.visualization.transformed.rows.some(r => {
+ const isInvalidDate = Number.isNaN(new Date(r[key]).valueOf());
+ if (isInvalidDate) {
+ console.warn(`${r[key]} is [Invalid Date]`);
+ }
+ return isInvalidDate;
+ });
+ if (invalid) {
+ return;
+ }
+ this.chart.scale({
+ [key]: {
+ type: 'time',
+ mask: setting.dateFormat || 'YYYY-MM-DD'
+ }
+ });
+ }
+ }
+
+ if (setting.forceY) {
+ this.chart.scale({
+ __value__: {
+ min: 0
+ }
+ });
+ }
+ }
+
+ renderAfter() {
+ const setting = this.config.setting.lineChart;
+ if (setting.lineWithFocus) {
+ // tslint:disable-next-line
+ (this.chart as any).interact('brush');
+ } else {
+ // tslint:disable-next-line:no-any
+ (this.chart as any).clearInteraction();
+ }
+ }
+
+ ngAfterViewInit() {
+ this.render();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.ts b/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.ts
new file mode 100644
index 00000000000..0be29c7676b
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/line-chart/line-chart-visualization.ts
@@ -0,0 +1,32 @@
+/*
+ * Licensed 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 { CdkPortalOutlet } from '@angular/cdk/portal';
+import { ViewContainerRef } from '@angular/core';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { G2VisualizationBase, VisualizationComponentPortal } from '@zeppelin/visualization';
+
+import { LineChartVisualizationComponent } from './line-chart-visualization.component';
+
+export class LineChartVisualization extends G2VisualizationBase {
+ componentPortal = new VisualizationComponentPortal(
+ this,
+ LineChartVisualizationComponent,
+ this.portalOutlet,
+ this.viewContainerRef
+ );
+
+ constructor(config: GraphConfig, private portalOutlet: CdkPortalOutlet, private viewContainerRef: ViewContainerRef) {
+ super(config);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.html b/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.html
new file mode 100644
index 00000000000..5ddf300130b
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.less b/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.less
new file mode 100644
index 00000000000..019b5ca53b5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.less
@@ -0,0 +1,12 @@
+/*
+ * Licensed 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.
+ */
+
diff --git a/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.ts b/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.ts
new file mode 100644
index 00000000000..0edebaa9d44
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.component.ts
@@ -0,0 +1,73 @@
+/*
+ * Licensed 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 {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ Component,
+ ElementRef,
+ Inject,
+ OnInit,
+ ViewChild
+} from '@angular/core';
+
+import { G2VisualizationComponentBase, Visualization, VISUALIZATION } from '@zeppelin/visualization';
+
+import { VisualizationPivotSettingComponent } from '../common/pivot-setting/pivot-setting.component';
+
+@Component({
+ selector: 'zeppelin-pie-chart-visualization',
+ templateUrl: './pie-chart-visualization.component.html',
+ styleUrls: ['./pie-chart-visualization.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class PieChartVisualizationComponent extends G2VisualizationComponentBase implements OnInit, AfterViewInit {
+ @ViewChild('container', { static: false }) container: ElementRef;
+ @ViewChild(VisualizationPivotSettingComponent, { static: false })
+ pivotSettingComponent: VisualizationPivotSettingComponent;
+
+ constructor(@Inject(VISUALIZATION) public visualization: Visualization) {
+ super(visualization);
+ }
+
+ ngOnInit() {}
+
+ refreshSetting() {
+ this.pivotSettingComponent.init();
+ }
+
+ setScale() {
+ // Noop
+ }
+
+ renderBefore() {
+ this.chart.tooltip({
+ showTitle: false
+ });
+ this.chart.coord('theta', {
+ radius: 0.75
+ });
+ this.chart
+ .intervalStack()
+ .position('__value__')
+ .color('__key__')
+ .style({
+ lineWidth: 1,
+ stroke: '#fff'
+ })
+ .tooltip('__key__*__value__', (name, value) => ({ name, value }));
+ }
+
+ ngAfterViewInit() {
+ this.render();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.ts b/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.ts
new file mode 100644
index 00000000000..d70572dda10
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/pie-chart/pie-chart-visualization.ts
@@ -0,0 +1,32 @@
+/*
+ * Licensed 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 { CdkPortalOutlet } from '@angular/cdk/portal';
+import { ViewContainerRef } from '@angular/core';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { G2VisualizationBase, VisualizationComponentPortal } from '@zeppelin/visualization';
+
+import { PieChartVisualizationComponent } from './pie-chart-visualization.component';
+
+export class PieChartVisualization extends G2VisualizationBase {
+ componentPortal = new VisualizationComponentPortal(
+ this,
+ PieChartVisualizationComponent,
+ this.portalOutlet,
+ this.viewContainerRef
+ );
+
+ constructor(config: GraphConfig, private portalOutlet: CdkPortalOutlet, private viewContainerRef: ViewContainerRef) {
+ super(config);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.html b/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.html
new file mode 100644
index 00000000000..ea1e3792c2c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.less b/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.less
new file mode 100644
index 00000000000..019b5ca53b5
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.less
@@ -0,0 +1,12 @@
+/*
+ * Licensed 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.
+ */
+
diff --git a/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.ts b/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.ts
new file mode 100644
index 00000000000..f1b8ae5d8e4
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.component.ts
@@ -0,0 +1,89 @@
+/*
+ * Licensed 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 {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ Inject,
+ OnInit,
+ ViewChild
+} from '@angular/core';
+
+import { get } from 'lodash';
+
+import { G2VisualizationComponentBase, Visualization, VISUALIZATION } from '@zeppelin/visualization';
+
+import { VisualizationScatterSettingComponent } from '../common/scatter-setting/scatter-setting.component';
+import { calcTickCount } from '../common/util/calc-tick-count';
+
+@Component({
+ selector: 'zeppelin-scatter-chart-visualization',
+ templateUrl: './scatter-chart-visualization.component.html',
+ styleUrls: ['./scatter-chart-visualization.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class ScatterChartVisualizationComponent extends G2VisualizationComponentBase implements OnInit, AfterViewInit {
+ @ViewChild('container', { static: false }) container: ElementRef;
+ @ViewChild(VisualizationScatterSettingComponent, { static: false })
+ scatterSettingComponent: VisualizationScatterSettingComponent;
+
+ constructor(@Inject(VISUALIZATION) public visualization: Visualization, private cdr: ChangeDetectorRef) {
+ super(visualization);
+ }
+
+ refreshSetting() {
+ this.scatterSettingComponent.init();
+ this.cdr.markForCheck();
+ }
+
+ setScale() {
+ const key = this.getKey();
+ const tickCount = calcTickCount(this.container.nativeElement);
+ this.chart.scale(key, {
+ tickCount,
+ type: 'cat'
+ });
+ }
+
+ renderBefore() {
+ const key = this.getKey();
+ const size = get(this.config.setting, 'scatterChart.size.name');
+ this.setScale();
+ this.chart.tooltip({
+ crosshairs: {
+ type: 'cross'
+ }
+ });
+ this.chart.legend('__value__', false);
+ // point
+ const geom = this.chart
+ .point()
+ .position(`${key}*__value__`)
+ .color('__key__')
+ // .adjust('jitter')
+ .opacity(0.65)
+ .shape('circle');
+
+ if (size) {
+ geom.size('__value__');
+ }
+ }
+
+ ngOnInit() {}
+
+ ngAfterViewInit() {
+ this.render();
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.ts b/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.ts
new file mode 100644
index 00000000000..0bfafab328c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/scatter-chart/scatter-chart-visualization.ts
@@ -0,0 +1,32 @@
+/*
+ * Licensed 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 { CdkPortalOutlet } from '@angular/cdk/portal';
+import { ViewContainerRef } from '@angular/core';
+
+import { GraphConfig } from '@zeppelin/sdk';
+import { G2VisualizationBase, VisualizationComponentPortal } from '@zeppelin/visualization';
+
+import { ScatterChartVisualizationComponent } from './scatter-chart-visualization.component';
+
+export class ScatterChartVisualization extends G2VisualizationBase {
+ componentPortal = new VisualizationComponentPortal(
+ this,
+ ScatterChartVisualizationComponent,
+ this.portalOutlet,
+ this.viewContainerRef
+ );
+
+ constructor(config: GraphConfig, private portalOutlet: CdkPortalOutlet, private viewContainerRef: ViewContainerRef) {
+ super(config);
+ }
+}
diff --git a/zeppelin-web-angular/src/app/visualizations/table/table-visualization.component.html b/zeppelin-web-angular/src/app/visualizations/table/table-visualization.component.html
new file mode 100644
index 00000000000..db43a53f0db
--- /dev/null
+++ b/zeppelin-web-angular/src/app/visualizations/table/table-visualization.component.html
@@ -0,0 +1,123 @@
+
+
+
+
+