diff --git a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.ts
index 903f72bccc5..742a9fb29a8 100644
--- a/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.ts
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/result/result.component.ts
@@ -16,7 +16,6 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
- ElementRef,
EventEmitter,
Injector,
Input,
@@ -81,6 +80,7 @@ export class NotebookParagraphResultComponent implements OnInit, AfterViewInit,
plainText: string | SafeHtml = '';
imgData: string | SafeUrl = '';
tableData = new TableData();
+ frontEndError: string;
// tslint:disable-next-line:no-any
visualizations: any[] = [
{
@@ -236,11 +236,16 @@ export class NotebookParagraphResultComponent implements OnInit, AfterViewInit,
}
renderAngular(): void {
- this.runtimeCompilerService.createAndCompileTemplate(this.id, this.result.data).then(data => {
- this.angularComponent = data;
- // this.angularComponent.moduleFactory
- this.cdr.markForCheck();
- });
+ try {
+ this.runtimeCompilerService.createAndCompileTemplate(this.id, this.result.data).then(data => {
+ this.angularComponent = data;
+ // this.angularComponent.moduleFactory
+ this.cdr.markForCheck();
+ });
+ } catch (e) {
+ this.frontEndError = e.message;
+ console.log(e);
+ }
}
renderText(): void {
diff --git a/zeppelin-web-angular/src/app/services/ng-template-adapter.service.ts b/zeppelin-web-angular/src/app/services/ng-template-adapter.service.ts
new file mode 100644
index 00000000000..651b709019c
--- /dev/null
+++ b/zeppelin-web-angular/src/app/services/ng-template-adapter.service.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.
+ */
+
+import { Injectable } from '@angular/core';
+import { Ng1MigrationComponent } from '@zeppelin/share/ng1-migration/ng1-migration.component';
+import { NzModalService } from 'ng-zorro-antd';
+import { Observable } from 'rxjs';
+
+export interface NgTemplateCheckResult {
+ index: number;
+ match: string;
+ magic: string;
+ template: string;
+ origin: string;
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class NgTemplateAdapterService {
+ constructor(private nzModalService: NzModalService) {}
+ preCheck(origin: string): NgTemplateCheckResult | null {
+ const regexp = /(%angular)([\s\S]*<[\s\S]*>)/im;
+ const math = regexp.exec(origin);
+ if (math) {
+ const index = math.index;
+ const [output, magic, template] = math;
+ return {
+ index,
+ magic,
+ template,
+ origin,
+ match: output
+ };
+ }
+ return null;
+ }
+
+ openMigrationDialog(check: NgTemplateCheckResult): Observable
{
+ const modalRef = this.nzModalService.create({
+ nzTitle: 'Angular.js Templates Migration Tool',
+ nzContent: Ng1MigrationComponent,
+ nzComponentParams: check,
+ nzFooter: null,
+ nzWidth: '980px',
+ nzStyle: {
+ top: '45px'
+ },
+ nzBodyStyle: {
+ padding: '0'
+ }
+ });
+
+ return modalRef.afterClose;
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.html b/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.html
new file mode 100644
index 00000000000..34d948ab9f7
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{errorCount}}
+
+
+
+ {{messageDetails.length - errorCount}}
+
+
+
+
+
+
+
+
({{(item.pos.line + 1) + ',' + (item.pos.character + 1)}})
+ {{item.message}}
+
more
+
+
+
+
+
+
+
diff --git a/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.less b/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.less
new file mode 100644
index 00000000000..cb1fdc26721
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.less
@@ -0,0 +1,77 @@
+:host {
+ height: 70vh;
+ display: flex;
+
+ .code-editor {
+ flex: auto;
+ }
+
+ .messages {
+ overflow: auto;
+ position: relative;
+ width: 240px;
+ border-left: 1px solid #e8e8e8;
+
+ i {
+ &.error {
+ color: red;
+ }
+ &.close {
+ color: #1f8ffb;
+ }
+ }
+
+ .fix-bar {
+ padding-right: 16px;
+ display: flex;
+ font-size: 12px;
+ border-bottom: 1px solid #e8e8e8;
+ height: 25px;
+ line-height: 25px;
+ .fix-btn {
+ flex: 0;
+ font-size: 12px;
+ }
+ .log-counts {
+ text-align: right;
+ flex: 1 auto;
+ }
+ }
+
+
+ .message {
+ font-family: Consolas, Verdana;
+ color: #1e1e1e;
+ padding: 8px 16px 8px 5px;
+ transition: background-color 0.3s;
+ word-break: break-all;
+ line-height: 17px;
+ cursor: pointer;
+ font-size: 12px;
+ .position {
+ color: #5d5d5d;
+ }
+ &:hover {
+ background-color: #ffb86c;
+ }
+ }
+ }
+}
+
+
+::ng-deep {
+ .monaco-editor {
+ .scroll-decoration {
+ box-shadow: none;
+ }
+ .decoration-link {
+ text-decoration-color: red;
+ text-decoration-line: underline;
+ text-decoration-style: wavy;
+ text-decoration-skip-ink: none;
+ }
+ .warn-content {
+ background: rgba(182, 182, 182, .3);
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.ts b/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.ts
new file mode 100644
index 00000000000..340330e92ff
--- /dev/null
+++ b/zeppelin-web-angular/src/app/share/ng1-migration/ng1-migration.component.ts
@@ -0,0 +1,174 @@
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy } from '@angular/core';
+import { editor, IDisposable, Range } from 'monaco-editor';
+import { NzModalRef } from 'ng-zorro-antd';
+import {
+ defaultTemplateUpdaterRules,
+ LogLevel,
+ Message,
+ MessageDetail,
+ TemplateUpdater,
+ ValueChangeRule
+} from 'ng1-template-updater';
+import { combineLatest, Subject } from 'rxjs';
+import IEditor = editor.IEditor;
+import ITextModel = editor.ITextModel;
+import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
+
+const zeppelinFunctionChangeRule: ValueChangeRule = (expression: string, start?: number) => {
+ let value = expression;
+ const messages: Message[] = [];
+ const funChanges = [
+ {
+ regexp: /z\.angularBind/gm,
+ replace: 'z.set'
+ },
+ {
+ regexp: /z\.angularUnbind/gm,
+ replace: 'z.unset'
+ },
+ {
+ regexp: /z\.runParagraph/gm,
+ replace: 'z.run'
+ }
+ ];
+
+ funChanges.forEach(change => {
+ let match = change.regexp.exec(value);
+ while (match !== null) {
+ messages.push({
+ position: start + match.index,
+ message: `${match[0]} has been deprecated, using ${change.replace} instead`,
+ length: match[0].length,
+ // url: 'https://angular.io/guide/ajs-quick-reference',
+ level: LogLevel.Info
+ });
+ match = change.regexp.exec(value);
+ }
+ value = value.replace(change.regexp, change.replace);
+ });
+
+ return {
+ messages,
+ value
+ };
+};
+
+@Component({
+ selector: 'zeppelin-ng1-migration',
+ templateUrl: './ng1-migration.component.html',
+ styleUrls: ['./ng1-migration.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class Ng1MigrationComponent implements OnDestroy {
+ @Input() origin: string;
+ @Input() index: number;
+ @Input() match: string;
+ @Input() template: string;
+
+ messageDetails: MessageDetail[] = [];
+ templateUpdater: TemplateUpdater;
+ errorCount = 0;
+ decorations: string[] = [];
+ timeoutId = -1;
+ editor: IStandaloneCodeEditor;
+ editorModel: ITextModel;
+ editorInit$ = new Subject();
+ editorChangeDisposable: IDisposable;
+
+ constructor(private nzModalRef: NzModalRef, private cdr: ChangeDetectorRef) {
+ const updateRules = {
+ ...defaultTemplateUpdaterRules,
+ valueChangeRules: [...defaultTemplateUpdaterRules.valueChangeRules, zeppelinFunctionChangeRule]
+ };
+ this.templateUpdater = new TemplateUpdater(updateRules);
+ combineLatest([this.nzModalRef.afterOpen, this.editorInit$]).subscribe(() => {
+ if (this.editor) {
+ this.editorModel = this.editor.getModel() as ITextModel;
+ this.editor.setValue(this.template);
+ this.editor.layout();
+ this.bindEditorEvents();
+ this.check();
+ setTimeout(() => {
+ this.editor.focus();
+ }, 150);
+ }
+ });
+ }
+
+ onEditorInit(_editor: IEditor) {
+ this.editorInit$.next();
+ this.editorInit$.complete();
+ this.editor = _editor as IStandaloneCodeEditor;
+ }
+
+ bindEditorEvents() {
+ if (this.editorModel) {
+ this.editorChangeDisposable = this.editorModel.onDidChangeContent(() => {
+ clearTimeout(this.timeoutId);
+ this.timeoutId = setTimeout(() => {
+ this.check();
+ }, 300);
+ });
+ }
+ }
+
+ scrollToLine(failure: MessageDetail) {
+ const line = failure.pos.line + 1;
+ const character = failure.pos.character + 1;
+ const range = new Range(line, character, line, character + failure.length);
+ this.editor.revealRangeAtTop(range);
+ this.editor.setSelection(range);
+ this.editor.focus();
+ }
+
+ check() {
+ const code = this.editor.getValue();
+ const { messages } = this.templateUpdater.parse(code);
+ this.messageDetails = [...messages];
+ this.errorCount = messages.filter(f => f.level === LogLevel.Error).length;
+ this.decorations = this.editor.deltaDecorations(
+ this.decorations,
+ messages.map(failure => {
+ const line = failure.pos.line + 1;
+ const character = failure.pos.character + 1;
+ return {
+ range: new Range(line, character, line, character + failure.length),
+ options: {
+ className: failure.level === LogLevel.Error ? '' : 'warn-content',
+ inlineClassName: failure.level === LogLevel.Error ? 'decoration-link' : '',
+ stickiness: 1,
+ hoverMessage: {
+ value: failure.message + (failure.url ? ` [more](${failure.url})` : '')
+ }
+ }
+ };
+ })
+ );
+ this.cdr.markForCheck();
+ }
+
+ fix() {
+ const code = this.editor.getValue();
+ const { template } = this.templateUpdater.parse(code);
+ this.editor.setValue(template);
+ }
+
+ updateAndCopy() {
+ const code = this.editor.getValue();
+ const newTemplate = this.origin.replace(this.match, `%ng\n${code}`);
+ this.nzModalRef.close(newTemplate);
+ }
+
+ cancel() {
+ this.nzModalRef.destroy();
+ }
+
+ ngOnDestroy(): void {
+ if (this.editorChangeDisposable) {
+ this.editorChangeDisposable.dispose();
+ }
+ if (this.editorModel) {
+ this.editorModel.dispose();
+ }
+ }
+}
diff --git a/zeppelin-web-angular/src/app/share/share.module.ts b/zeppelin-web-angular/src/app/share/share.module.ts
index ed5d191736a..fcb03751092 100644
--- a/zeppelin-web-angular/src/app/share/share.module.ts
+++ b/zeppelin-web-angular/src/app/share/share.module.ts
@@ -52,6 +52,7 @@ import { PageHeaderComponent } from '@zeppelin/share/page-header/page-header.com
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 = [
@@ -59,7 +60,8 @@ const MODAL_LIST = [
NoteImportComponent,
NoteCreateComponent,
NoteRenameComponent,
- FolderRenameComponent
+ FolderRenameComponent,
+ Ng1MigrationComponent
];
const EXPORT_LIST = [HeaderComponent, NodeListComponent, PageHeaderComponent, SpinComponent, ResizeHandleComponent];
const PIPES = [HumanizeBytesPipe];