From d3ba2fbc824b86e7397c5c88ad0e2c1680054f08 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Tue, 29 Mar 2022 17:32:18 +0530 Subject: [PATCH 1/8] feat: code viewer component and download file component --- projects/common/src/color/color.ts | 4 +- .../code-viewer/code-viewer.component.scss | 72 ++++++++++++ .../src/code-viewer/code-viewer.component.ts | 106 ++++++++++++++++++ .../src/code-viewer/code-viewer.module.ts | 12 ++ .../download-file/download-file-metadata.ts | 6 + .../download-file.component.scss} | 2 +- .../download-file.component.ts} | 47 ++++---- .../download-file.module.ts} | 8 +- .../download-json.component.test.ts | 65 ----------- projects/components/src/public-api.ts | 10 +- .../src/search-box/search-box.component.ts | 13 ++- .../trace-detail.page.component.ts | 13 ++- .../trace-detail/trace-detail.page.module.ts | 4 +- 13 files changed, 252 insertions(+), 110 deletions(-) create mode 100644 projects/components/src/code-viewer/code-viewer.component.scss create mode 100644 projects/components/src/code-viewer/code-viewer.component.ts create mode 100644 projects/components/src/code-viewer/code-viewer.module.ts create mode 100644 projects/components/src/download-file/download-file-metadata.ts rename projects/components/src/{download-json/download-json.component.scss => download-file/download-file.component.scss} (88%) rename projects/components/src/{download-json/download-json.component.ts => download-file/download-file.component.ts} (63%) rename projects/components/src/{download-json/download-json.module.ts => download-file/download-file.module.ts} (67%) delete mode 100644 projects/components/src/download-json/download-json.component.test.ts diff --git a/projects/common/src/color/color.ts b/projects/common/src/color/color.ts index bcf86245d..fca38cf11 100644 --- a/projects/common/src/color/color.ts +++ b/projects/common/src/color/color.ts @@ -56,5 +56,7 @@ export const enum Color { Yellow6 = '#facf00', Yellow7 = '#bd9d00', Yellow8 = '#6d5b00', - Yellow9 = '#181400' + Yellow9 = '#181400', + Transparent = 'transparent', + OffWhite = '#f6f6f64d' } diff --git a/projects/components/src/code-viewer/code-viewer.component.scss b/projects/components/src/code-viewer/code-viewer.component.scss new file mode 100644 index 000000000..def7629cf --- /dev/null +++ b/projects/components/src/code-viewer/code-viewer.component.scss @@ -0,0 +1,72 @@ +@import 'mixins'; + +@mixin line-base { + display: flex; + align-items: center; + width: 100%; + height: 20px; + + &.highlight { + background-color: $blue-2; + } +} + +.code-viewer { + @include fill-container; + display: grid; + grid-template-rows: 54px auto; + + .header { + height: 100%; + display: flex; + align-items: center; + padding: 0 12px; + border-bottom: 1px solid $gray-2; + + .title { + @include overline; + } + + .header-content { + min-width: 0; + flex: 1 1 auto; + display: flex; + justify-content: flex-end; + + .search-box { + width: 140px; + } + } + } + + .content { + @include code; + height: 100%; + overflow-y: auto; + display: grid; + grid-template-columns: 40px auto; + + .line-numbers { + width: 100%; + display: flex; + flex-direction: column; + + .line-number { + @include line-base; + padding-left: 8px; + } + } + + .code-lines { + width: 100%; + display: flex; + flex-direction: column; + overflow-x: auto; + + .code-line { + @include line-base; + white-space: break-spaces; + } + } + } +} diff --git a/projects/components/src/code-viewer/code-viewer.component.ts b/projects/components/src/code-viewer/code-viewer.component.ts new file mode 100644 index 000000000..ab563d7a9 --- /dev/null +++ b/projects/components/src/code-viewer/code-viewer.component.ts @@ -0,0 +1,106 @@ +import { ChangeDetectionStrategy, Component, Input, TemplateRef } from '@angular/core'; +import { isEmpty, isNil } from 'lodash-es'; +import { Color, TypedSimpleChanges } from '@hypertrace/common'; +import { DownloadFileMetadata } from '../download-file/download-file-metadata'; + +@Component({ + selector: 'ht-code-viewer', + styleUrls: ['./code-viewer.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+
+
{{ this.titleText }}
+
+ + + + +
+
+
+
+
+ {{ lineNumber }} +
+
+
+
+
{{ codeLine }}
+
+
+
+
+ ` +}) +export class CodeViewerComponent { + @Input() + public code: string[] = []; // Pre-formatted code lines as string + + @Input() + public highlightText: string = ''; // To highlight the entire line + + @Input() + public titleText: string = 'Code Viewer'; + + @Input() + public backgroundColor: string = Color.OffWhite; + + @Input() + public downloadCodeMetadata?: DownloadFileMetadata; + + @Input() + public additionalHeaderContent?: TemplateRef; + + public codeLineElements?: HTMLElement[]; + + public ngOnChanges(changes: TypedSimpleChanges): void { + if (changes.code) { + this.codeLineElements = undefined; + } + } + + public get lineNumbers(): number[] { + return new Array(this.code.length).fill(0).map((_, index) => index + 1); + } + + public isHighlighted(lineNum: number): boolean { + return !isEmpty(this.highlightText) && this.code[lineNum].toLowerCase().includes(this.highlightText.toLowerCase()); + } + + public onSearch(searchText: string): void { + if (isNil(this.codeLineElements)) { + this.codeLineElements = Array.from(document.getElementsByClassName('code-line') ?? []) as HTMLElement[]; + } + + this.codeLineElements.forEach((codeLineElem, index) => { + const codeLine: string = this.code[index]; + codeLineElem.innerHTML = this.getReplacedHtmlString(codeLine, searchText); + }); + } + + /** + * This gives the replaced HTML string after search. + * It uses regex to replace the searched text with a span if found + */ + private getReplacedHtmlString(codeLine: string, searchText: string): string { + const searchRegex: RegExp = new RegExp(searchText, 'gi'); + const resultString: string = !isEmpty(searchText) + ? codeLine.replace(searchRegex, value => `${value}`) + : codeLine; + return `
${resultString}
`; + } +} diff --git a/projects/components/src/code-viewer/code-viewer.module.ts b/projects/components/src/code-viewer/code-viewer.module.ts new file mode 100644 index 000000000..1379b0da6 --- /dev/null +++ b/projects/components/src/code-viewer/code-viewer.module.ts @@ -0,0 +1,12 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { DownloadFileModule } from '../download-file/download-file.module'; +import { TraceSearchBoxModule } from '../search-box/search-box.module'; +import { CodeViewerComponent } from './code-viewer.component'; + +@NgModule({ + imports: [CommonModule, DownloadFileModule, TraceSearchBoxModule], + declarations: [CodeViewerComponent], + exports: [CodeViewerComponent] +}) +export class CodeViewerModule {} diff --git a/projects/components/src/download-file/download-file-metadata.ts b/projects/components/src/download-file/download-file-metadata.ts new file mode 100644 index 000000000..b7891cc63 --- /dev/null +++ b/projects/components/src/download-file/download-file-metadata.ts @@ -0,0 +1,6 @@ +import { Observable } from 'rxjs'; + +export interface DownloadFileMetadata { + dataSource: Observable; // This should be a stringified data for any file + fileName: string; +} diff --git a/projects/components/src/download-json/download-json.component.scss b/projects/components/src/download-file/download-file.component.scss similarity index 88% rename from projects/components/src/download-json/download-json.component.scss rename to projects/components/src/download-file/download-file.component.scss index 652ed3456..738d2a9ba 100644 --- a/projects/components/src/download-json/download-json.component.scss +++ b/projects/components/src/download-file/download-file.component.scss @@ -1,6 +1,6 @@ @import 'color-palette'; -.download-json { +.download-file { width: 40px; height: 40px; display: flex; diff --git a/projects/components/src/download-json/download-json.component.ts b/projects/components/src/download-file/download-file.component.ts similarity index 63% rename from projects/components/src/download-json/download-json.component.ts rename to projects/components/src/download-file/download-file.component.ts index 42120fd19..2cbba774c 100644 --- a/projects/components/src/download-json/download-json.component.ts +++ b/projects/components/src/download-file/download-file.component.ts @@ -1,18 +1,18 @@ import { DOCUMENT } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, Renderer2 } from '@angular/core'; import { IconType } from '@hypertrace/assets-library'; -import { IconSize } from '@hypertrace/components'; -import { Observable } from 'rxjs'; import { catchError, finalize, take } from 'rxjs/operators'; import { ButtonSize, ButtonStyle } from '../button/button'; +import { IconSize } from '../icon/icon-size'; import { NotificationService } from '../notification/notification.service'; +import { DownloadFileMetadata } from './download-file-metadata'; @Component({ - selector: 'ht-download-json', + selector: 'ht-download-file', changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['./download-json.component.scss'], + styleUrls: ['./download-file.component.scss'], template: ` -
+
` }) -export class DownloadJsonComponent { +export class DownloadFileComponent { @Input() - public dataSource!: Observable; - - @Input() - public fileName: string = 'download.json'; + public metadata?: DownloadFileMetadata; public dataLoading: boolean = false; private readonly dlJsonAnchorElement: HTMLAnchorElement; @@ -45,31 +42,25 @@ export class DownloadJsonComponent { public triggerDownload(): void { this.dataLoading = true; - this.dataSource - .pipe( - take(1), - catchError(() => this.notificationService.createFailureToast('Download failed')), - finalize(() => { - this.dataLoading = false; - this.changeDetector.detectChanges(); - }) - ) - .subscribe((data: unknown) => { - if (typeof data === 'string') { - this.downloadData(data); - } else { - this.downloadData(JSON.stringify(data)); - } - }); + this.metadata!.dataSource.pipe( + take(1), + catchError(() => this.notificationService.createFailureToast('Download failed')), + finalize(() => { + this.dataLoading = false; + this.changeDetector.detectChanges(); + }) + ).subscribe((data: string) => { + this.downloadData(data); + }); } private downloadData(data: string): void { this.renderer.setAttribute( this.dlJsonAnchorElement, 'href', - `data:text/json;charset=utf-8,${encodeURIComponent(data)}` + `data:text/plain;charset=utf-8,${encodeURIComponent(data)}` ); - this.renderer.setAttribute(this.dlJsonAnchorElement, 'download', this.fileName); + this.renderer.setAttribute(this.dlJsonAnchorElement, 'download', this.metadata!.fileName); this.renderer.setAttribute(this.dlJsonAnchorElement, 'display', 'none'); this.dlJsonAnchorElement.click(); } diff --git a/projects/components/src/download-json/download-json.module.ts b/projects/components/src/download-file/download-file.module.ts similarity index 67% rename from projects/components/src/download-json/download-json.module.ts rename to projects/components/src/download-file/download-file.module.ts index f731e0897..8660ff652 100644 --- a/projects/components/src/download-json/download-json.module.ts +++ b/projects/components/src/download-file/download-file.module.ts @@ -3,11 +3,11 @@ import { NgModule } from '@angular/core'; import { ButtonModule } from '../button/button.module'; import { IconModule } from '../icon/icon.module'; import { NotificationModule } from '../notification/notification.module'; -import { DownloadJsonComponent } from './download-json.component'; +import { DownloadFileComponent } from './download-file.component'; @NgModule({ - declarations: [DownloadJsonComponent], + declarations: [DownloadFileComponent], imports: [CommonModule, ButtonModule, NotificationModule, IconModule], - exports: [DownloadJsonComponent] + exports: [DownloadFileComponent] }) -export class DownloadJsonModule {} +export class DownloadFileModule {} diff --git a/projects/components/src/download-json/download-json.component.test.ts b/projects/components/src/download-json/download-json.component.test.ts deleted file mode 100644 index c5f7fd56c..000000000 --- a/projects/components/src/download-json/download-json.component.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Renderer2 } from '@angular/core'; -import { fakeAsync } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; -import { MockComponent } from 'ng-mocks'; -import { Observable, of } from 'rxjs'; -import { ButtonComponent } from '../button/button.component'; -import { IconComponent } from '../icon/icon.component'; -import { DownloadJsonComponent } from './download-json.component'; -import { DownloadJsonModule } from './download-json.module'; - -describe('Download Json Component', () => { - let spectator: Spectator; - const mockElement = document.createElement('a'); - const createElementSpy = jest.fn().mockReturnValue(mockElement); - - const createHost = createHostFactory({ - component: DownloadJsonComponent, - imports: [DownloadJsonModule, RouterTestingModule], - declarations: [MockComponent(ButtonComponent), MockComponent(IconComponent)], - providers: [ - mockProvider(Document, { - createElement: createElementSpy - }), - mockProvider(Renderer2, { - setAttribute: jest.fn() - }) - ], - shallow: true - }); - - const dataSource$: Observable = of({ - spans: [] - }); - - test('should have only download button, when data is not loading', () => { - spectator = createHost(``, { - hostProps: { - dataSource: dataSource$ - } - }); - - expect(spectator.query(ButtonComponent)).toExist(); - }); - - test('should download json file', fakeAsync(() => { - spectator = createHost(``, { - hostProps: { - dataSource: dataSource$ - } - }); - - spyOn(spectator.component, 'triggerDownload'); - - expect(spectator.component.dataLoading).toBe(false); - expect(spectator.component.fileName).toBe('download.json'); - const element = spectator.query('.download-json'); - expect(element).toExist(); - - spectator.click(element!); - spectator.tick(); - - expect(spectator.component.triggerDownload).toHaveBeenCalledTimes(1); - })); -}); diff --git a/projects/components/src/public-api.ts b/projects/components/src/public-api.ts index 8bfd68434..636399d65 100644 --- a/projects/components/src/public-api.ts +++ b/projects/components/src/public-api.ts @@ -24,6 +24,10 @@ export * from './checkbox/checkbox.module'; export * from './collapsible-sidebar/collapsible-sidebar.component'; export * from './collapsible-sidebar/collapsible-sidebar.module'; +// Collapsible sidebar +export * from './code-viewer/code-viewer.component'; +export * from './code-viewer/code-viewer.module'; + // Combo Box export * from './combo-box/combo-box.module'; export * from './combo-box/combo-box.component'; @@ -75,9 +79,9 @@ export { MenuDropdownComponent } from './menu-dropdown/menu-dropdown.component'; export { MenuItemComponent } from './menu-dropdown/menu-item/menu-item.component'; export { MenuDropdownModule } from './menu-dropdown/menu-dropdown.module'; -// Download JSON -export * from './download-json/download-json.component'; -export * from './download-json/download-json.module'; +// Download File +export * from './download-file/download-file.component'; +export * from './download-file/download-file.module'; // Dynamic label export * from './highlighted-label/highlighted-label.component'; diff --git a/projects/components/src/search-box/search-box.component.ts b/projects/components/src/search-box/search-box.component.ts index 44b21b3bd..db2daa881 100644 --- a/projects/components/src/search-box/search-box.component.ts +++ b/projects/components/src/search-box/search-box.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { IconType } from '@hypertrace/assets-library'; -import { SubscriptionLifecycle, TypedSimpleChanges } from '@hypertrace/common'; +import { Color, SubscriptionLifecycle, TypedSimpleChanges } from '@hypertrace/common'; import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { IconSize } from '../icon/icon-size'; @@ -11,13 +11,19 @@ import { IconSize } from '../icon/icon-size'; changeDetection: ChangeDetectionStrategy.OnPush, providers: [SubscriptionLifecycle], template: ` - @@ -92,6 +92,11 @@ export class TraceDetailPageComponent { this.logEvents$ = this.traceDetailService.fetchLogEvents(); } + public getDownloadTraceDetailsJsonMetadata = (traceId: string): DownloadFileMetadata => ({ + dataSource: this.exportSpans$, + fileName: `${traceId}.json` + }); + public onClickBack(): void { this.navigationService.navigateBack(); } diff --git a/projects/observability/src/pages/trace-detail/trace-detail.page.module.ts b/projects/observability/src/pages/trace-detail/trace-detail.page.module.ts index 6d03e54a3..a2c3b8511 100644 --- a/projects/observability/src/pages/trace-detail/trace-detail.page.module.ts +++ b/projects/observability/src/pages/trace-detail/trace-detail.page.module.ts @@ -4,7 +4,7 @@ import { RouterModule } from '@angular/router'; import { FormattingModule, HtRoute, MemoizeModule } from '@hypertrace/common'; import { CopyShareableLinkToClipboardModule, - DownloadJsonModule, + DownloadFileModule, IconModule, LabelModule, LoadAsyncModule, @@ -58,7 +58,7 @@ const ROUTE_CONFIG: HtRoute[] = [ LoadAsyncModule, FormattingModule, CopyShareableLinkToClipboardModule, - DownloadJsonModule, + DownloadFileModule, NavigableDashboardModule.withDefaultDashboards(traceSequenceDashboard), NavigableTabModule, LogEventsTableModule From c51aa0056cf7b466a18c319c542c37a5acaf9d26 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Tue, 29 Mar 2022 17:38:45 +0530 Subject: [PATCH 2/8] fix: add searched class --- .../components/src/code-viewer/code-viewer.component.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/projects/components/src/code-viewer/code-viewer.component.ts b/projects/components/src/code-viewer/code-viewer.component.ts index ab563d7a9..707552345 100644 --- a/projects/components/src/code-viewer/code-viewer.component.ts +++ b/projects/components/src/code-viewer/code-viewer.component.ts @@ -65,7 +65,7 @@ export class CodeViewerComponent { @Input() public additionalHeaderContent?: TemplateRef; - public codeLineElements?: HTMLElement[]; + private codeLineElements?: HTMLElement[]; public ngOnChanges(changes: TypedSimpleChanges): void { if (changes.code) { @@ -99,7 +99,10 @@ export class CodeViewerComponent { private getReplacedHtmlString(codeLine: string, searchText: string): string { const searchRegex: RegExp = new RegExp(searchText, 'gi'); const resultString: string = !isEmpty(searchText) - ? codeLine.replace(searchRegex, value => `${value}`) + ? codeLine.replace( + searchRegex, + value => `${value}` + ) : codeLine; return `
${resultString}
`; } From 892edcce1c187410f64a594adedd2f097e18bbaf Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Tue, 29 Mar 2022 18:09:12 +0530 Subject: [PATCH 3/8] fix: lint and test cases --- .../code-viewer/code-viewer.component.test.ts | 53 +++++++++++++++ .../src/code-viewer/code-viewer.component.ts | 7 +- .../download-file.component.test.ts | 66 +++++++++++++++++++ projects/components/src/public-api.ts | 1 + .../trace-detail.page.component.ts | 3 +- 5 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 projects/components/src/code-viewer/code-viewer.component.test.ts create mode 100644 projects/components/src/download-file/download-file.component.test.ts diff --git a/projects/components/src/code-viewer/code-viewer.component.test.ts b/projects/components/src/code-viewer/code-viewer.component.test.ts new file mode 100644 index 000000000..03340682e --- /dev/null +++ b/projects/components/src/code-viewer/code-viewer.component.test.ts @@ -0,0 +1,53 @@ +import { createComponentFactory } from '@ngneat/spectator/jest'; +import { MockComponent } from 'ng-mocks'; +import { of } from 'rxjs'; +import { DownloadFileMetadata } from '../download-file/download-file-metadata'; +import { DownloadFileComponent } from '../download-file/download-file.component'; +import { SearchBoxComponent } from '../search-box/search-box.component'; +import { CodeViewerComponent } from './code-viewer.component'; + +describe('Code Viewer Component', () => { + const createComponent = createComponentFactory({ + component: CodeViewerComponent, + declarations: [MockComponent(SearchBoxComponent), MockComponent(DownloadFileComponent)], + shallow: true + }); + const code: string[] = ['{', ` "key": "value"`, '}']; + const downloadMetadata: DownloadFileMetadata = { + dataSource: of(''), + fileName: 'code.json' + }; + + test('should render everything correctly', () => { + const spectator = createComponent({ + props: { + code: [], + titleText: 'New code viewer' + } + }); + expect(spectator.query('.code-viewer')).toExist(); + expect(spectator.query(SearchBoxComponent)).toExist(); + expect(spectator.query(DownloadFileComponent)).not.toExist(); + expect(spectator.query('.code-viewer')).toExist(); + expect(spectator.query('.title')).toHaveText('New code viewer'); + expect(spectator.queryAll('.line-number').length).toBe(0); + expect(spectator.queryAll('.code-line').length).toBe(0); + + // Set code + spectator.setInput({ + code: code, + highlightText: 'key', + downloadCodeMetadata: downloadMetadata + }); + + expect(spectator.query(DownloadFileComponent)?.metadata).toMatchObject(downloadMetadata); + expect(spectator.queryAll('.line-number').length).toBe(3); + expect(spectator.queryAll('.line-number.highlight').length).toBe(1); + expect(spectator.queryAll('.code-line').length).toBe(3); + expect(spectator.queryAll('.code-line.highlight').length).toBe(1); + + // Search + spectator.triggerEventHandler(SearchBoxComponent, 'valueChange', 'e'); + expect(spectator.queryAll('.searched').length).toBe(2); + }); +}); diff --git a/projects/components/src/code-viewer/code-viewer.component.ts b/projects/components/src/code-viewer/code-viewer.component.ts index 707552345..1bdea9a0f 100644 --- a/projects/components/src/code-viewer/code-viewer.component.ts +++ b/projects/components/src/code-viewer/code-viewer.component.ts @@ -1,6 +1,6 @@ -import { ChangeDetectionStrategy, Component, Input, TemplateRef } from '@angular/core'; -import { isEmpty, isNil } from 'lodash-es'; +import { ChangeDetectionStrategy, Component, Input, OnChanges, TemplateRef } from '@angular/core'; import { Color, TypedSimpleChanges } from '@hypertrace/common'; +import { isEmpty, isNil } from 'lodash-es'; import { DownloadFileMetadata } from '../download-file/download-file-metadata'; @Component({ @@ -46,7 +46,7 @@ import { DownloadFileMetadata } from '../download-file/download-file-metadata';
` }) -export class CodeViewerComponent { +export class CodeViewerComponent implements OnChanges { @Input() public code: string[] = []; // Pre-formatted code lines as string @@ -104,6 +104,7 @@ export class CodeViewerComponent { value => `${value}` ) : codeLine; + return `
${resultString}
`; } } diff --git a/projects/components/src/download-file/download-file.component.test.ts b/projects/components/src/download-file/download-file.component.test.ts new file mode 100644 index 000000000..7cf721373 --- /dev/null +++ b/projects/components/src/download-file/download-file.component.test.ts @@ -0,0 +1,66 @@ +import { Renderer2 } from '@angular/core'; +import { fakeAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; +import { MockComponent } from 'ng-mocks'; +import { of } from 'rxjs'; +import { ButtonComponent } from '../button/button.component'; +import { IconComponent } from '../icon/icon.component'; +import { DownloadFileMetadata } from './download-file-metadata'; +import { DownloadFileComponent } from './download-file.component'; +import { DownloadFileModule } from './download-file.module'; + +describe('Download File Component', () => { + let spectator: Spectator; + const mockElement = document.createElement('a'); + const createElementSpy = jest.fn().mockReturnValue(mockElement); + + const createHost = createHostFactory({ + component: DownloadFileComponent, + imports: [DownloadFileModule, RouterTestingModule], + declarations: [MockComponent(ButtonComponent), MockComponent(IconComponent)], + providers: [ + mockProvider(Document, { + createElement: createElementSpy + }), + mockProvider(Renderer2, { + setAttribute: jest.fn() + }) + ], + shallow: true + }); + + const metadata: DownloadFileMetadata = { + dataSource: of(''), + fileName: 'download.txt' + }; + + test('should have only download button, when data is not loading', () => { + spectator = createHost(``, { + hostProps: { + metadata: metadata + } + }); + + expect(spectator.query(ButtonComponent)).toExist(); + }); + + test('should download file', fakeAsync(() => { + spectator = createHost(``, { + hostProps: { + metadata: metadata + } + }); + + spyOn(spectator.component, 'triggerDownload'); + + expect(spectator.component.dataLoading).toBe(false); + const element = spectator.query('.download-file'); + expect(element).toExist(); + + spectator.click(element!); + spectator.tick(); + + expect(spectator.component.triggerDownload).toHaveBeenCalledTimes(1); + })); +}); diff --git a/projects/components/src/public-api.ts b/projects/components/src/public-api.ts index 636399d65..b12fdd57f 100644 --- a/projects/components/src/public-api.ts +++ b/projects/components/src/public-api.ts @@ -81,6 +81,7 @@ export { MenuDropdownModule } from './menu-dropdown/menu-dropdown.module'; // Download File export * from './download-file/download-file.component'; +export * from './download-file/download-file-metadata'; export * from './download-file/download-file.module'; // Dynamic label diff --git a/projects/observability/src/pages/trace-detail/trace-detail.page.component.ts b/projects/observability/src/pages/trace-detail/trace-detail.page.component.ts index b0da2d7e7..a3ce09a6e 100644 --- a/projects/observability/src/pages/trace-detail/trace-detail.page.component.ts +++ b/projects/observability/src/pages/trace-detail/trace-detail.page.component.ts @@ -1,9 +1,8 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { IconType } from '@hypertrace/assets-library'; import { NavigationParams, NavigationService, SubscriptionLifecycle } from '@hypertrace/common'; -import { FilterOperator, IconSize } from '@hypertrace/components'; +import { DownloadFileMetadata, FilterOperator, IconSize } from '@hypertrace/components'; import { Observable } from 'rxjs'; -import { DownloadFileMetadata } from '../../../../components/src/download-file/download-file-metadata'; import { LogEvent } from '../../shared/dashboard/widgets/waterfall/waterfall/waterfall-chart'; import { ApiTraceDetails } from '../api-trace-detail/api-trace-detail.service'; import { ExplorerService } from '../explorer/explorer-service'; From f7dcc92966a685a85b5fcdc68172f052799639ca Mon Sep 17 00:00:00 2001 From: Sandeep Kumar Sharma Date: Wed, 30 Mar 2022 21:35:59 +0530 Subject: [PATCH 4/8] fix: removinfg snippet viewer and additional changes --- .../code-viewer/code-viewer.component.test.ts | 53 -------- .../src/code-viewer/code-viewer.component.ts | 110 --------------- .../src/code-viewer/code-viewer.module.ts | 12 -- projects/components/src/public-api.ts | 8 +- .../src/search-box/search-box.component.ts | 13 +- .../code-viewer/code-viewer.component.scss | 15 +++ .../code-viewer/code-viewer.component.ts | 127 ++++++++++++++++++ .../viewer/code-viewer/code-viewer.module.ts | 14 ++ .../snippet-viewer.component.scss | 41 ------ .../snippet-viewer.component.test.ts | 46 ------- .../snippet-viewer.component.ts | 50 ------- .../snippet-viewer/snippet-viewer.module.ts | 11 -- 12 files changed, 160 insertions(+), 340 deletions(-) delete mode 100644 projects/components/src/code-viewer/code-viewer.component.test.ts delete mode 100644 projects/components/src/code-viewer/code-viewer.component.ts delete mode 100644 projects/components/src/code-viewer/code-viewer.module.ts rename projects/components/src/{ => viewer}/code-viewer/code-viewer.component.scss (82%) create mode 100644 projects/components/src/viewer/code-viewer/code-viewer.component.ts create mode 100644 projects/components/src/viewer/code-viewer/code-viewer.module.ts delete mode 100644 projects/components/src/viewer/snippet-viewer/snippet-viewer.component.scss delete mode 100644 projects/components/src/viewer/snippet-viewer/snippet-viewer.component.test.ts delete mode 100644 projects/components/src/viewer/snippet-viewer/snippet-viewer.component.ts delete mode 100644 projects/components/src/viewer/snippet-viewer/snippet-viewer.module.ts diff --git a/projects/components/src/code-viewer/code-viewer.component.test.ts b/projects/components/src/code-viewer/code-viewer.component.test.ts deleted file mode 100644 index 03340682e..000000000 --- a/projects/components/src/code-viewer/code-viewer.component.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createComponentFactory } from '@ngneat/spectator/jest'; -import { MockComponent } from 'ng-mocks'; -import { of } from 'rxjs'; -import { DownloadFileMetadata } from '../download-file/download-file-metadata'; -import { DownloadFileComponent } from '../download-file/download-file.component'; -import { SearchBoxComponent } from '../search-box/search-box.component'; -import { CodeViewerComponent } from './code-viewer.component'; - -describe('Code Viewer Component', () => { - const createComponent = createComponentFactory({ - component: CodeViewerComponent, - declarations: [MockComponent(SearchBoxComponent), MockComponent(DownloadFileComponent)], - shallow: true - }); - const code: string[] = ['{', ` "key": "value"`, '}']; - const downloadMetadata: DownloadFileMetadata = { - dataSource: of(''), - fileName: 'code.json' - }; - - test('should render everything correctly', () => { - const spectator = createComponent({ - props: { - code: [], - titleText: 'New code viewer' - } - }); - expect(spectator.query('.code-viewer')).toExist(); - expect(spectator.query(SearchBoxComponent)).toExist(); - expect(spectator.query(DownloadFileComponent)).not.toExist(); - expect(spectator.query('.code-viewer')).toExist(); - expect(spectator.query('.title')).toHaveText('New code viewer'); - expect(spectator.queryAll('.line-number').length).toBe(0); - expect(spectator.queryAll('.code-line').length).toBe(0); - - // Set code - spectator.setInput({ - code: code, - highlightText: 'key', - downloadCodeMetadata: downloadMetadata - }); - - expect(spectator.query(DownloadFileComponent)?.metadata).toMatchObject(downloadMetadata); - expect(spectator.queryAll('.line-number').length).toBe(3); - expect(spectator.queryAll('.line-number.highlight').length).toBe(1); - expect(spectator.queryAll('.code-line').length).toBe(3); - expect(spectator.queryAll('.code-line.highlight').length).toBe(1); - - // Search - spectator.triggerEventHandler(SearchBoxComponent, 'valueChange', 'e'); - expect(spectator.queryAll('.searched').length).toBe(2); - }); -}); diff --git a/projects/components/src/code-viewer/code-viewer.component.ts b/projects/components/src/code-viewer/code-viewer.component.ts deleted file mode 100644 index 1bdea9a0f..000000000 --- a/projects/components/src/code-viewer/code-viewer.component.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges, TemplateRef } from '@angular/core'; -import { Color, TypedSimpleChanges } from '@hypertrace/common'; -import { isEmpty, isNil } from 'lodash-es'; -import { DownloadFileMetadata } from '../download-file/download-file-metadata'; - -@Component({ - selector: 'ht-code-viewer', - styleUrls: ['./code-viewer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - template: ` -
-
-
{{ this.titleText }}
-
- - - - -
-
-
-
-
- {{ lineNumber }} -
-
-
-
-
{{ codeLine }}
-
-
-
-
- ` -}) -export class CodeViewerComponent implements OnChanges { - @Input() - public code: string[] = []; // Pre-formatted code lines as string - - @Input() - public highlightText: string = ''; // To highlight the entire line - - @Input() - public titleText: string = 'Code Viewer'; - - @Input() - public backgroundColor: string = Color.OffWhite; - - @Input() - public downloadCodeMetadata?: DownloadFileMetadata; - - @Input() - public additionalHeaderContent?: TemplateRef; - - private codeLineElements?: HTMLElement[]; - - public ngOnChanges(changes: TypedSimpleChanges): void { - if (changes.code) { - this.codeLineElements = undefined; - } - } - - public get lineNumbers(): number[] { - return new Array(this.code.length).fill(0).map((_, index) => index + 1); - } - - public isHighlighted(lineNum: number): boolean { - return !isEmpty(this.highlightText) && this.code[lineNum].toLowerCase().includes(this.highlightText.toLowerCase()); - } - - public onSearch(searchText: string): void { - if (isNil(this.codeLineElements)) { - this.codeLineElements = Array.from(document.getElementsByClassName('code-line') ?? []) as HTMLElement[]; - } - - this.codeLineElements.forEach((codeLineElem, index) => { - const codeLine: string = this.code[index]; - codeLineElem.innerHTML = this.getReplacedHtmlString(codeLine, searchText); - }); - } - - /** - * This gives the replaced HTML string after search. - * It uses regex to replace the searched text with a span if found - */ - private getReplacedHtmlString(codeLine: string, searchText: string): string { - const searchRegex: RegExp = new RegExp(searchText, 'gi'); - const resultString: string = !isEmpty(searchText) - ? codeLine.replace( - searchRegex, - value => `${value}` - ) - : codeLine; - - return `
${resultString}
`; - } -} diff --git a/projects/components/src/code-viewer/code-viewer.module.ts b/projects/components/src/code-viewer/code-viewer.module.ts deleted file mode 100644 index 1379b0da6..000000000 --- a/projects/components/src/code-viewer/code-viewer.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { DownloadFileModule } from '../download-file/download-file.module'; -import { TraceSearchBoxModule } from '../search-box/search-box.module'; -import { CodeViewerComponent } from './code-viewer.component'; - -@NgModule({ - imports: [CommonModule, DownloadFileModule, TraceSearchBoxModule], - declarations: [CodeViewerComponent], - exports: [CodeViewerComponent] -}) -export class CodeViewerModule {} diff --git a/projects/components/src/public-api.ts b/projects/components/src/public-api.ts index b12fdd57f..fc183c97c 100644 --- a/projects/components/src/public-api.ts +++ b/projects/components/src/public-api.ts @@ -25,8 +25,8 @@ export * from './collapsible-sidebar/collapsible-sidebar.component'; export * from './collapsible-sidebar/collapsible-sidebar.module'; // Collapsible sidebar -export * from './code-viewer/code-viewer.component'; -export * from './code-viewer/code-viewer.module'; +export * from './viewer/code-viewer/code-viewer.component'; +export * from './viewer/code-viewer/code-viewer.module'; // Combo Box export * from './combo-box/combo-box.module'; @@ -277,10 +277,6 @@ export * from './overlay/overlay'; export * from './overlay/overlay.module'; export * from './overlay/sheet/sheet'; -// Snippet -export { SnippetViewerComponent } from './viewer/snippet-viewer/snippet-viewer.component'; -export { SnippetViewerModule } from './viewer/snippet-viewer/snippet-viewer.module'; - // Spinner export * from './spinner/spinner.component'; export * from './spinner/spinner.module'; diff --git a/projects/components/src/search-box/search-box.component.ts b/projects/components/src/search-box/search-box.component.ts index db2daa881..44b21b3bd 100644 --- a/projects/components/src/search-box/search-box.component.ts +++ b/projects/components/src/search-box/search-box.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { IconType } from '@hypertrace/assets-library'; -import { Color, SubscriptionLifecycle, TypedSimpleChanges } from '@hypertrace/common'; +import { SubscriptionLifecycle, TypedSimpleChanges } from '@hypertrace/common'; import { Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { IconSize } from '../icon/icon-size'; @@ -11,19 +11,13 @@ import { IconSize } from '../icon/icon-size'; changeDetection: ChangeDetectionStrategy.OnPush, providers: [SubscriptionLifecycle], template: ` -