-
Notifications
You must be signed in to change notification settings - Fork 11
feat: relative timestamp table cell renderer and log events table in sheet #818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
202b00c
2e7f58b
3cb722e
8f0b5c8
b03c83d
533c904
a1d101d
8033540
cd1214b
e51b716
e2aef98
552f7bc
2e642b8
2e90a3a
171dc8a
36e8d44
8886ae2
11bee55
77d168d
fe85d1c
129b6a4
8466606
10f3d37
929f90b
b45756d
01b9bbc
e25a66c
2874cca
88f233a
411ab62
5a4126a
08ba01b
c5b8a42
29f2bdd
bf4b7ae
dd1b677
e0b2ef4
2b87274
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| @import 'font'; | ||
|
|
||
| .relative-timestamp { | ||
| @include ellipsis-overflow(); | ||
|
|
||
| &.first-column { | ||
| @include body-1-medium($gray-9); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { | ||
| TableCellNoOpParser, | ||
| tableCellProviders, | ||
| tableCellRowDataProvider, | ||
| TooltipDirective | ||
| } from '@hypertrace/components'; | ||
| import { createComponentFactory } from '@ngneat/spectator/jest'; | ||
| import { MockComponent } from 'ng-mocks'; | ||
| import { tableCellDataProvider } from '../../test/cell-providers'; | ||
| import { | ||
| RelativeTimestampTableCellRendererComponent, | ||
| RowData | ||
| } from './relative-timestamp-table-cell-renderer.component'; | ||
|
|
||
| describe('log timestamp table cell renderer component', () => { | ||
| const buildComponent = createComponentFactory({ | ||
| component: RelativeTimestampTableCellRendererComponent, | ||
| providers: [ | ||
| tableCellProviders( | ||
| { | ||
| id: 'test' | ||
| }, | ||
| new TableCellNoOpParser(undefined!) | ||
| ) | ||
| ], | ||
| declarations: [MockComponent(TooltipDirective)], | ||
| shallow: true | ||
| }); | ||
|
|
||
| test('testing component properties', () => { | ||
| const logEvent: RowData = { | ||
| baseTimestamp: 1619785437887 | ||
| }; | ||
| const spectator = buildComponent({ | ||
| providers: [tableCellRowDataProvider(logEvent), tableCellDataProvider('2021-04-30T12:23:57.889149Z')] | ||
| }); | ||
|
|
||
| expect(spectator.queryAll('.relative-timestamp')[0]).toContainText('2 ms'); | ||
| expect(spectator.component.duration).toBe(2); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; | ||
| import { DateCoercer, DateFormatMode, DateFormatOptions } from '@hypertrace/common'; | ||
| import { TableColumnConfig } from '../../../table-api'; | ||
| import { | ||
| TABLE_CELL_DATA, | ||
| TABLE_COLUMN_CONFIG, | ||
| TABLE_COLUMN_INDEX, | ||
| TABLE_DATA_PARSER, | ||
| TABLE_ROW_DATA | ||
| } from '../../table-cell-injection'; | ||
| import { TableCellParserBase } from '../../table-cell-parser-base'; | ||
| import { TableCellRenderer } from '../../table-cell-renderer'; | ||
| import { TableCellRendererBase } from '../../table-cell-renderer-base'; | ||
| import { CoreTableCellParserType } from '../../types/core-table-cell-parser-type'; | ||
| import { CoreTableCellRendererType } from '../../types/core-table-cell-renderer-type'; | ||
| import { TableCellAlignmentType } from '../../types/table-cell-alignment-type'; | ||
|
|
||
| export interface RowData { | ||
|
||
| [key: string]: unknown; | ||
| baseTimestamp: DateOrNumber; | ||
| } | ||
|
|
||
| type DateOrNumber = Date | number; | ||
| @Component({ | ||
| selector: 'ht-relative-timestamp-table-cell-renderer', | ||
| styleUrls: ['./relative-timestamp-table-cell-renderer.component.scss'], | ||
| changeDetection: ChangeDetectionStrategy.OnPush, | ||
| template: ` | ||
| <div | ||
| class="relative-timestamp" | ||
| [htTooltip]="this.value | htDisplayDate: this.dateFormat" | ||
| [ngClass]="{ 'first-column': this.isFirstColumn }" | ||
| > | ||
| {{ this.duration }} ms | ||
| </div> | ||
| ` | ||
| }) | ||
| @TableCellRenderer({ | ||
| type: CoreTableCellRendererType.RelativeTimestamp, | ||
| alignment: TableCellAlignmentType.Left, | ||
| parser: CoreTableCellParserType.NoOp | ||
| }) | ||
| export class RelativeTimestampTableCellRendererComponent extends TableCellRendererBase<DateOrNumber> implements OnInit { | ||
| public readonly dateFormat: DateFormatOptions = { | ||
| mode: DateFormatMode.DateAndTimeWithSeconds | ||
| }; | ||
| public readonly duration: number; | ||
| private readonly dateCoercer: DateCoercer = new DateCoercer(); | ||
|
|
||
| public constructor( | ||
| @Inject(TABLE_COLUMN_CONFIG) columnConfig: TableColumnConfig, | ||
| @Inject(TABLE_COLUMN_INDEX) index: number, | ||
| @Inject(TABLE_DATA_PARSER) | ||
| parser: TableCellParserBase<DateOrNumber, DateOrNumber, unknown>, | ||
| @Inject(TABLE_CELL_DATA) cellData: DateOrNumber, | ||
| @Inject(TABLE_ROW_DATA) rowData: RowData | ||
| ) { | ||
| super(columnConfig, index, parser, cellData, rowData); | ||
| this.duration = | ||
| (this.dateCoercer.coerce(cellData)?.getTime() ?? NaN) - | ||
| (this.dateCoercer.coerce(rowData.baseTimestamp)?.getTime() ?? NaN); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { Dictionary } from '@hypertrace/common'; | ||
| import { createModelFactory, SpectatorModel } from '@hypertrace/dashboards/testing'; | ||
| import { LogEvent } from '@hypertrace/distributed-tracing'; | ||
| import { GraphQlRequestService } from '@hypertrace/graphql-client'; | ||
| import { ModelApi } from '@hypertrace/hyperdash'; | ||
| import { runFakeRxjs } from '@hypertrace/test-utils'; | ||
| import { mockProvider } from '@ngneat/spectator/jest'; | ||
| import { of } from 'rxjs'; | ||
| import { LogDetailDataSourceModel } from './log-detail-data-source.model'; | ||
|
|
||
| describe('Log Detail data source model', () => { | ||
| let spectator!: SpectatorModel<LogDetailDataSourceModel>; | ||
| const attributes: Dictionary<unknown> = { | ||
| key1: 'value1', | ||
| key2: 'value2' | ||
| }; | ||
| const logEvent: LogEvent = { | ||
| traceId: 'id1', | ||
| attributes: attributes, | ||
| spanId: 's-id1', | ||
| timestamp: '2021-05-05T00:00:00Z', | ||
| summary: 'test log event' | ||
| }; | ||
|
|
||
| const buildModel = createModelFactory({ | ||
| providers: [ | ||
| mockProvider(GraphQlRequestService, { | ||
| query: jest.fn().mockReturnValue(of({})) | ||
| }) | ||
| ] | ||
| }); | ||
| beforeEach(() => { | ||
| const mockApi: Partial<ModelApi> = {}; | ||
| spectator = buildModel(LogDetailDataSourceModel); | ||
| spectator.model.logEvent = logEvent; | ||
| spectator.model.api = mockApi as ModelApi; | ||
| }); | ||
|
|
||
| test('test attribute data', () => { | ||
| runFakeRxjs(({ expectObservable }) => { | ||
| expectObservable(spectator.model.getData()).toBe('(x|)', { | ||
| x: expect.objectContaining({ | ||
| key1: 'value1', | ||
| key2: 'value2' | ||
| }) | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { Dictionary } from '@hypertrace/common'; | ||
| import { Model, ModelProperty, PLAIN_OBJECT_PROPERTY } from '@hypertrace/hyperdash'; | ||
| import { Observable, of } from 'rxjs'; | ||
| import { GraphQlDataSourceModel } from '../../../data/graphql/graphql-data-source.model'; | ||
| import { LogEvent } from '../../waterfall/waterfall/waterfall-chart'; | ||
|
|
||
| @Model({ | ||
| type: 'log-detail-data-source' | ||
|
||
| }) | ||
| export class LogDetailDataSourceModel extends GraphQlDataSourceModel<Dictionary<unknown>> { | ||
| @ModelProperty({ | ||
| key: 'log-event', | ||
| required: true, | ||
| type: PLAIN_OBJECT_PROPERTY.type | ||
| }) | ||
| public logEvent?: LogEvent; | ||
|
|
||
| public getData(): Observable<Dictionary<unknown>> { | ||
| return of(this.logEvent?.attributes ?? {}); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| @import 'font'; | ||
|
|
||
| .content { | ||
| @include body-2-regular(); | ||
| margin-left: 175px; | ||
| margin-top: 15px; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { Dictionary, FormattingModule } from '@hypertrace/common'; | ||
| import { ListViewComponent, LoadAsyncModule } from '@hypertrace/components'; | ||
| import { mockDashboardWidgetProviders } from '@hypertrace/dashboards/testing'; | ||
| import { createComponentFactory } from '@ngneat/spectator/jest'; | ||
| import { MockComponent } from 'ng-mocks'; | ||
| import { of } from 'rxjs'; | ||
| import { LogDetailWidgetRendererComponent } from './log-detail-widget-renderer.component'; | ||
| import { LogDetailWidgetModel } from './log-detail-widget.model'; | ||
|
|
||
| describe('log detail widget renderer component', () => { | ||
| let mockModel: Partial<LogDetailWidgetModel> = {}; | ||
| const buildComponent = createComponentFactory({ | ||
| component: LogDetailWidgetRendererComponent, | ||
| providers: [], | ||
| imports: [FormattingModule, LoadAsyncModule], | ||
| declarations: [MockComponent(ListViewComponent)], | ||
| shallow: true | ||
| }); | ||
| const attributes: Dictionary<unknown> = { | ||
| key1: 1, | ||
| key2: 2 | ||
| }; | ||
|
|
||
| test('should render list view with provided data', () => { | ||
| mockModel = { | ||
| getData: jest.fn(() => of(attributes)) | ||
| }; | ||
| const spectator = buildComponent({ | ||
| providers: [...mockDashboardWidgetProviders(mockModel)] | ||
| }); | ||
| expect(spectator.query('.content')).toExist(); | ||
| expect(spectator.query(ListViewComponent)!.header).toEqual(spectator.component.header); | ||
| expect(spectator.query(ListViewComponent)!.records).toEqual([ | ||
| { | ||
| key: 'key1', | ||
| value: 1 | ||
| }, | ||
| { | ||
| key: 'key2', | ||
| value: 2 | ||
| } | ||
| ]); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: relative timestamp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed!!