This is an automated email from the ASF dual-hosted git repository. zjffdu pushed a commit to branch web_angular in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/web_angular by this push: new d95b73e [ZEPPELIN-4548] Support search notebook d95b73e is described below commit d95b73efeaaf247aa07599d5f52a04c911a5a679 Author: Hsuan Lee <hsua...@gmail.com> AuthorDate: Wed Jan 8 16:23:25 2020 +0800 [ZEPPELIN-4548] Support search notebook ### What is this PR for? support search notebook ### What type of PR is it? [Feature] ### What is the Jira issue? https://issues.apache.org/jira/browse/ZEPPELIN-4548 ### How should this be tested? N/A ### Screenshots (if appropriate) ![image](https://user-images.githubusercontent.com/22736418/71962091-ad462e00-3233-11ea-9d85-5b579f280953.png) ### Questions: * Does the licenses files need update? No * Is there breaking changes for older versions? No * Does this needs documentation? No Author: Hsuan Lee <hsua...@gmail.com> Closes #3592 from hsuanxyz/feat/search and squashes the following commits: 6e41f3e36 [Hsuan Lee] feat: support search notebook --- .../{public-api.ts => notebook-search.ts} | 14 +- .../src/app/interfaces/public-api.ts | 1 + .../notebook-search-routing.module.ts} | 24 ++- .../notebook-search/notebook-search.component.html | 17 +++ .../notebook-search.component.less} | 19 ++- .../notebook-search/notebook-search.component.ts | 58 ++++++++ .../notebook-search/notebook-search.module.ts | 29 ++++ .../result-item/result-item.component.html | 22 +++ .../result-item/result-item.component.less} | 14 +- .../result-item/result-item.component.ts | 162 +++++++++++++++++++++ .../paragraph/code-editor/code-editor.component.ts | 4 +- .../pages/workspace/workspace-routing.module.ts | 5 + .../src/app/services/notebook-search.service.ts | 47 ++++++ .../src/app/share/header/header.component.html | 6 +- .../src/app/share/header/header.component.ts | 15 ++ .../src/app/utility/get-keyword-positions.ts | 43 ++++++ zeppelin-web-angular/src/app/utility/line-map.ts | 60 ++++++++ .../src/app/visualizations/g2.config.ts | 1 - 18 files changed, 510 insertions(+), 31 deletions(-) diff --git a/zeppelin-web-angular/src/app/interfaces/public-api.ts b/zeppelin-web-angular/src/app/interfaces/notebook-search.ts similarity index 71% copy from zeppelin-web-angular/src/app/interfaces/public-api.ts copy to zeppelin-web-angular/src/app/interfaces/notebook-search.ts index 8c54e3d..267ee94 100644 --- a/zeppelin-web-angular/src/app/interfaces/public-api.ts +++ b/zeppelin-web-angular/src/app/interfaces/notebook-search.ts @@ -10,10 +10,10 @@ * 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 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 8c54e3d..e762a5c 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/interfaces/public-api.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search-routing.module.ts similarity index 58% copy from zeppelin-web-angular/src/app/interfaces/public-api.ts copy to zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search-routing.module.ts index 8c54e3d..9127792 100644 --- a/zeppelin-web-angular/src/app/interfaces/public-api.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search-routing.module.ts @@ -10,10 +10,20 @@ * 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'; +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 0000000..e1db6d9 --- /dev/null +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.html @@ -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. + --> +<div class="main"> + <zeppelin-notebook-search-result-item *ngFor="let item of results" + [result]="item"> + </zeppelin-notebook-search-result-item> +</div> + diff --git a/zeppelin-web-angular/src/app/interfaces/public-api.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.less similarity index 71% copy from zeppelin-web-angular/src/app/interfaces/public-api.ts copy to zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.less index 8c54e3d..108b1a4 100644 --- a/zeppelin-web-angular/src/app/interfaces/public-api.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/notebook-search.component.less @@ -10,10 +10,15 @@ * 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'; +@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 0000000..2036d23 --- /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 0000000..69dafa8 --- /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 0000000..dcd78dc --- /dev/null +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.html @@ -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. + --> + +<nz-card [nzTitle]="titleTemplateRef"> + <ng-template #titleTemplateRef> + <a [routerLink]="routerLink">{{displayName}}</a> + </ng-template> + <zeppelin-code-editor + [style.height.px]="height" + [nzEditorOption]="editorOption" + (nzEditorInitialized)="initializedEditor($event)"> + </zeppelin-code-editor> +</nz-card> diff --git a/zeppelin-web-angular/src/app/interfaces/public-api.ts b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.less similarity index 71% copy from zeppelin-web-angular/src/app/interfaces/public-api.ts copy to zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.less index 8c54e3d..cb24d4e 100644 --- a/zeppelin-web-angular/src/app/interfaces/public-api.ts +++ b/zeppelin-web-angular/src/app/pages/workspace/notebook-search/result-item/result-item.component.less @@ -10,10 +10,10 @@ * 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'; +::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 0000000..e911a66 --- /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>(.+?)<\/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 8cf4bd7..bfae433 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 16928a3..d4faf20 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 @@ -35,6 +35,11 @@ const routes: Routes = [ 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: () => import('@zeppelin/pages/workspace/job-manager/job-manager.module').then(m => m.JobManagerModule) 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 0000000..7371eec --- /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<string | null>(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<NotebookSearchResultItem[]>(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 33d8ecd..76f93c2 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 @@ </div> <div class="search"> <nz-input-group [nzPrefixIcon]="'search'"> - <input type="text" nz-input placeholder="Search"/> + <input type="text" + nz-input + placeholder="Search" + (keyup.enter)="onSearch()" + [(ngModel)]="queryStr"/> </nz-input-group> </div> </div> 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 e69b89e..b4c20c2 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 0000000..c2eb7aa --- /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 0000000..30c4079 --- /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 aa5a4e9..622d25d 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); }