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 index 8c54e3d3464..e762a5c3667 100644 --- a/zeppelin-web-angular/src/app/interfaces/public-api.ts +++ b/zeppelin-web-angular/src/app/interfaces/public-api.ts @@ -17,3 +17,4 @@ 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/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..dcd78dc9180 --- /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..e911a66c56c --- /dev/null +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.ts @@ -0,0 +1,162 @@ +/* + * 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 { 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; + + 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) {} + + setDisplayNameAndRouterLink(): void { + const noteId = this.result.id.split('/', 2)[0]; + this.displayName = this.result.name ? this.result.name : `Note ${noteId}`; + + this.routerLink = `/notebook/${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/paragraph/code-editor/code-editor.component.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook/paragraph/code-editor/code-editor.component.ts index 8cf4bd798b8..bfae433371d 100644 --- 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 @@ -145,7 +145,9 @@ export class NotebookParagraphCodeEditorComponent implements OnChanges, OnDestro lineNumbers: this.lineNumbers ? 'on' : 'off', glyphMargin: false, folding: false, - scrollBeyondLastLine: false + scrollBeyondLastLine: false, + contextmenu: false, + matchBrackets: false }); } } diff --git a/zeppelin-web-angular/src/app/pages/workspace/workspace-routing.module.ts b/zeppelin-web-angular/src/app/pages/workspace/workspace-routing.module.ts index 16928a33fc9..d4faf206d8b 100644 --- a/zeppelin-web-angular/src/app/pages/workspace/workspace-routing.module.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/workspace-routing.module.ts @@ -34,6 +34,11 @@ const routes: Routes = [ path: 'notebook/:noteId/paragraph', loadChildren: () => import('@zeppelin/pages/workspace/published/published.module').then(m => m.PublishedModule) }, + { + path: 'search/:queryStr', + loadChildren: () => + import('@zeppelin/pages/workspace/notebook-search/notebook-search.module').then(m => m.NotebookSearchModule) + }, { path: 'jobmanager', loadChildren: () => diff --git a/zeppelin-web-angular/src/app/services/notebook-search.service.ts b/zeppelin-web-angular/src/app/services/notebook-search.service.ts new file mode 100644 index 00000000000..7371eec7362 --- /dev/null +++ b/zeppelin-web-angular/src/app/services/notebook-search.service.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 { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +import { NotebookSearchResultItem } from '@zeppelin/interfaces'; +import { BaseRest } from '@zeppelin/services/base-rest'; +import { BaseUrlService } from '@zeppelin/services/base-url.service'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class NotebookSearchService extends BaseRest { + private queryStr$ = new BehaviorSubject(null); + + constructor(baseUrlService: BaseUrlService, private http: HttpClient) { + super(baseUrlService); + } + + queried() { + return this.queryStr$.asObservable(); + } + + clear() { + this.queryStr$.next(null); + } + + search(query: string) { + this.queryStr$.next(query); + return this.http.get(this.restUrl`/notebook/search`, { + params: { + q: query + } + }); + } +} diff --git a/zeppelin-web-angular/src/app/share/header/header.component.html b/zeppelin-web-angular/src/app/share/header/header.component.html index 33d8ecd857b..76f93c2cef5 100644 --- a/zeppelin-web-angular/src/app/share/header/header.component.html +++ b/zeppelin-web-angular/src/app/share/header/header.component.html @@ -73,7 +73,11 @@ diff --git a/zeppelin-web-angular/src/app/share/header/header.component.ts b/zeppelin-web-angular/src/app/share/header/header.component.ts index e69b89e7c84..b4c20c242cc 100644 --- a/zeppelin-web-angular/src/app/share/header/header.component.ts +++ b/zeppelin-web-angular/src/app/share/header/header.component.ts @@ -20,6 +20,7 @@ import { filter, takeUntil } from 'rxjs/operators'; import { MessageListener, MessageListenersManager } from '@zeppelin/core'; import { MessageReceiveDataTypeMap, OP } from '@zeppelin/sdk'; import { MessageService, TicketService } from '@zeppelin/services'; +import { NotebookSearchService } from '@zeppelin/services/notebook-search.service'; import { AboutZeppelinComponent } from '@zeppelin/share/about-zeppelin/about-zeppelin.component'; @Component({ @@ -32,6 +33,7 @@ export class HeaderComponent extends MessageListenersManager implements OnInit, private destroy$ = new Subject(); connectStatus = 'error'; noteListVisible = false; + queryStr: string | null = null; about() { this.nzModalService.create({ @@ -46,6 +48,13 @@ export class HeaderComponent extends MessageListenersManager implements OnInit, this.ticketService.logout().subscribe(); } + onSearch() { + this.queryStr = this.queryStr.trim(); + if (this.queryStr) { + this.router.navigate(['/search', this.queryStr]); + } + } + @MessageListener(OP.CONFIGURATIONS_INFO) getConfiguration(data: MessageReceiveDataTypeMap[OP.CONFIGURATIONS_INFO]) { this.ticketService.setConfiguration(data); @@ -56,6 +65,7 @@ export class HeaderComponent extends MessageListenersManager implements OnInit, private nzModalService: NzModalService, public messageService: MessageService, private router: Router, + private notebookSearchService: NotebookSearchService, private cdr: ChangeDetectorRef ) { super(messageService); @@ -76,6 +86,11 @@ export class HeaderComponent extends MessageListenersManager implements OnInit, this.noteListVisible = false; this.cdr.markForCheck(); }); + + this.notebookSearchService + .queried() + .pipe(takeUntil(this.destroy$)) + .subscribe(queryStr => (this.queryStr = queryStr)); } ngOnDestroy() { 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/g2.config.ts b/zeppelin-web-angular/src/app/visualizations/g2.config.ts index aa5a4e9e30e..622d25ddb74 100644 --- a/zeppelin-web-angular/src/app/visualizations/g2.config.ts +++ b/zeppelin-web-angular/src/app/visualizations/g2.config.ts @@ -124,7 +124,6 @@ const zeppelinTheme = { export function setTheme() { const theme = G2.Util.deepMix(G2.Global, zeppelinTheme); - console.log(zeppelinTheme); // tslint:disable-next-line:no-any (G2.Global as any).setTheme(theme); }