You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by zj...@apache.org on 2020/01/10 02:33:01 UTC
[zeppelin] branch web_angular updated: [ZEPPELIN-4548] Support
search notebook
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 <hs...@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 <hs...@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);
}