diff --git a/projects/assets-library/assets/icons/external.svg b/projects/assets-library/assets/icons/external.svg new file mode 100644 index 000000000..40f1e04e2 --- /dev/null +++ b/projects/assets-library/assets/icons/external.svg @@ -0,0 +1,4 @@ + + + + diff --git a/projects/assets-library/assets/icons/internal.svg b/projects/assets-library/assets/icons/internal.svg new file mode 100644 index 000000000..fe628a42f --- /dev/null +++ b/projects/assets-library/assets/icons/internal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/projects/assets-library/src/icons/icon-library.module.ts b/projects/assets-library/src/icons/icon-library.module.ts index 2ef4d6606..5aac4efd2 100644 --- a/projects/assets-library/src/icons/icon-library.module.ts +++ b/projects/assets-library/src/icons/icon-library.module.ts @@ -43,6 +43,7 @@ const iconsRootPath = 'assets/icons'; { key: IconType.Edge, url: `${iconsRootPath}/edge.svg` }, { key: IconType.ExpandAll, url: `${iconsRootPath}/expand-all.svg` }, { key: IconType.Expanded, url: `${iconsRootPath}/minus-circle.svg` }, + { key: IconType.External, url: `${iconsRootPath}/external.svg` }, { key: IconType.Eye, url: `${iconsRootPath}/eye.svg` }, { key: IconType.FileCode, url: `${iconsRootPath}/file-code.svg` }, { key: IconType.Filter, url: `${iconsRootPath}/filter.svg` }, @@ -52,6 +53,7 @@ const iconsRootPath = 'assets/icons'; { key: IconType.IpAddress, url: `${iconsRootPath}/ip-address.svg` }, { key: IconType.Info, url: `${iconsRootPath}/info.svg` }, { key: IconType.Infrastructure, url: `${iconsRootPath}/infrastructure.svg` }, + { key: IconType.Internal, url: `${iconsRootPath}/internal.svg` }, { key: IconType.Java, url: `${iconsRootPath}/java.svg` }, { key: IconType.KnowledgeGraph, url: `${iconsRootPath}/knowledge-graph.svg` }, { key: IconType.Kong, url: `${iconsRootPath}/kong.svg` }, diff --git a/projects/assets-library/src/icons/icon-type.ts b/projects/assets-library/src/icons/icon-type.ts index fd13b4c72..5da912c4e 100644 --- a/projects/assets-library/src/icons/icon-type.ts +++ b/projects/assets-library/src/icons/icon-type.ts @@ -43,6 +43,7 @@ export const enum IconType { Expand = 'launch', ExpandAll = 'svg:expand-all', Expanded = 'svg:minus-square', + External = 'svg:external', Eye = 'svg:eye', Favorite = 'favorite_border', FileCode = 'svg:file-code', @@ -55,6 +56,7 @@ export const enum IconType { IpAddress = 'svg:ip-address', Info = 'svg:info', Infrastructure = 'svg:infrastructure', + Internal = 'svg:internal', Java = 'svg:java', KeyboardArrowLeft = 'keyboard_arrow_left', KeyboardBackspace = 'keyboard_backspace', diff --git a/projects/components/src/public-api.ts b/projects/components/src/public-api.ts index 40e414f03..1813880a5 100644 --- a/projects/components/src/public-api.ts +++ b/projects/components/src/public-api.ts @@ -2,6 +2,10 @@ * Public API Surface of components */ +// Beta tag +export * from './beta-tag/beta-tag.component'; +export * from './beta-tag/beta-tag.module'; + // Breadcrumbs export * from './breadcrumbs/breadcrumbs.component'; export * from './breadcrumbs/breadcrumbs.module'; @@ -21,6 +25,10 @@ export * from './combo-box/combo-box.module'; export * from './combo-box/combo-box.component'; export * from './combo-box/combo-box-api'; +// Confirmation +export * from './confirmation/confirmation.module'; +export * from './confirmation/confirmation.service'; + // Content Holder export * from './content/content-holder'; @@ -37,18 +45,27 @@ export * from './copy-to-clipboard/copy-to-clipboard.module'; export * from './copy-shareable-link-to-clipboard/copy-shareable-link-to-clipboard.component'; export * from './copy-shareable-link-to-clipboard/copy-shareable-link-to-clipboard.module'; -// Open in new tab -export * from './open-in-new-tab/open-in-new-tab.component'; -export * from './open-in-new-tab/open-in-new-tab.module'; - // Date Time picker export * from './datetime-picker/datetime-picker.component'; export * from './datetime-picker/datetime-picker.module'; +// Description +export * from './description/description.component'; +export * from './description/description.module'; + // Divider export * from './divider/divider.component'; export * from './divider/divider.module'; +// Dropdown menu +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'; + +// Dynamic label +export * from './highlighted-label/highlighted-label.component'; +export * from './highlighted-label/highlighted-label.module'; + // Event Blocker export * from './event-blocker/event-blocker.component'; export * from './event-blocker/event-blocker.module'; @@ -83,6 +100,10 @@ export * from './filtering/filter-modal/in-filter-modal.component'; // Filter Parser export * from './filtering/filter/parser/filter-parser-lookup.service'; +// Greeting label +export { GreetingLabelModule } from './greeting-label/greeting-label.module'; +export { GreetingLabelComponent } from './greeting-label/greeting-label.component'; + // Header export * from './header/application/application-header.component'; export * from './header/application/application-header.module'; @@ -108,9 +129,9 @@ export { JsonViewerModule } from './viewer/json-viewer/json-viewer.module'; export * from './label/label.component'; export * from './label/label.module'; -// Dynamic label -export * from './highlighted-label/highlighted-label.component'; -export * from './highlighted-label/highlighted-label.module'; +// Label tag +export * from './label-tag/label-tag.component'; +export * from './label-tag/label-tag.module'; // Layout Change export { LayoutChangeTriggerDirective } from './layout/layout-change-trigger.directive'; @@ -142,6 +163,15 @@ export { LoadAsyncModule } from './load-async/load-async.module'; export { MessageDisplayComponent } from './message-display/message-display.component'; export { MessageDisplayModule } from './message-display/message-display.module'; +// Modal +export * from './modal/modal'; +export * from './modal/modal.module'; +export * from './modal/modal.service'; + +// Multi-select +export * from './multi-select/multi-select.component'; +export * from './multi-select/multi-select.module'; + // Navigable Tab export * from './tabs/navigable/navigable-tab'; export * from './tabs/navigable/navigable-tab-group.component'; @@ -152,6 +182,14 @@ export * from './tabs/navigable/navigable-tab.module'; export * from './not-found/not-found.component'; export * from './not-found/not-found.module'; +// Notification +export * from './notification/notification.service'; +export * from './notification/notification.module'; + +// Open in new tab +export * from './open-in-new-tab/open-in-new-tab.component'; +export * from './open-in-new-tab/open-in-new-tab.module'; + // Paginator export * from './paginator/page.event'; export * from './paginator/paginator.component'; @@ -187,15 +225,16 @@ export * from './select/select-control-option.component'; export * from './select/select.component'; export * from './select/select.module'; -// Multi-select -export * from './multi-select/multi-select.component'; -export * from './multi-select/multi-select.module'; - // Sequence export { SequenceSegment } from './sequence/sequence'; export * from './sequence/sequence-chart.component'; export * from './sequence/sequence-chart.module'; +// Summary List +export * from './summary-list/summary-list.module'; +export * from './summary-list/summary-list.component'; +export * from './summary-list/summary-list-api'; + // Overlay export { OverlayService } from './overlay/overlay.service'; export * from './overlay/overlay'; @@ -283,36 +322,3 @@ export { ToggleSwitchSize } from './toggle-switch/toggle-switch-size'; // Tooltip export { TooltipModule } from './tooltip/tooltip.module'; export { TooltipDirective } from './tooltip/tooltip.directive'; - -// Greeting label -export { GreetingLabelModule } from './greeting-label/greeting-label.module'; -export { GreetingLabelComponent } from './greeting-label/greeting-label.component'; - -// Dropdown menu -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'; - -// Beta tag -export * from './beta-tag/beta-tag.component'; -export * from './beta-tag/beta-tag.module'; - -// Label tag -export * from './label-tag/label-tag.component'; -export * from './label-tag/label-tag.module'; - -// Modal -export * from './modal/modal'; -export * from './modal/modal.module'; -export * from './modal/modal.service'; - -export * from './confirmation/confirmation.module'; -export * from './confirmation/confirmation.service'; - -// Notification -export * from './notification/notification.service'; -export * from './notification/notification.module'; - -// Description -export * from './description/description.component'; -export * from './description/description.module'; diff --git a/projects/components/src/summary-list/summary-list-api.ts b/projects/components/src/summary-list/summary-list-api.ts new file mode 100644 index 000000000..090d205b6 --- /dev/null +++ b/projects/components/src/summary-list/summary-list-api.ts @@ -0,0 +1,6 @@ +import { PrimitiveValue } from '@hypertrace/common'; + +export interface SummaryItem { + label: string; + value: PrimitiveValue | PrimitiveValue[]; +} diff --git a/projects/components/src/summary-list/summary-list.component.scss b/projects/components/src/summary-list/summary-list.component.scss new file mode 100644 index 000000000..fb2609b8b --- /dev/null +++ b/projects/components/src/summary-list/summary-list.component.scss @@ -0,0 +1,43 @@ +@import 'mixins'; + +.summary-list { + padding: 16px; + + .summary-header { + display: flex; + align-items: center; + padding-top: 4px; + padding-bottom: 16px; + border-bottom: 1px solid $gray-2; + + .summary-icon { + color: $gray-7; + padding-right: 8px; + } + + .summary-title { + @include body-2-medium($gray-7); + } + } + + .summary-value-title { + @include body-2-medium($gray-7); + display: block; + padding-top: 16px; + } + + .summary-value-list { + list-style-type: none; + margin: 0; + padding: 0; + + .summary-value { + @include body-2-regular($gray-7); + padding-top: 3px; + + &:first-child { + padding-top: 6px; + } + } + } +} diff --git a/projects/components/src/summary-list/summary-list.component.test.ts b/projects/components/src/summary-list/summary-list.component.test.ts new file mode 100644 index 000000000..762ce8894 --- /dev/null +++ b/projects/components/src/summary-list/summary-list.component.test.ts @@ -0,0 +1,79 @@ +import { IconType } from '@hypertrace/assets-library'; +import { IconComponent, LabelComponent, SummaryListComponent } from '@hypertrace/components'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { MockComponent } from 'ng-mocks'; + +describe('Summary List component', () => { + let spectator: SpectatorHost; + + const createHost = createHostFactory({ + component: SummaryListComponent, + shallow: true, + declarations: [MockComponent(IconComponent), MockComponent(LabelComponent)] + }); + + beforeEach(() => { + spectator = createHost( + ` + `, + { + hostProps: { + title: 'My Title', + icon: IconType.Add, + items: [ + { + label: 'number', + value: 0 + }, + { + label: 'Number-Array', + value: [0, 1, 2] + }, + { + label: 'STRING', + value: 'zero' + }, + { + label: 'STRING_ARRAY', + value: ['zero', 'one', 'two', 'three'] + }, + { + label: 'bOOleAN', + value: true + }, + { + label: 'boolean-array', + value: [true, false] + } + ] + } + } + ); + }); + + test('sets title and icon', () => { + expect(spectator.query('.summary-icon', { read: IconComponent })?.icon).toEqual(IconType.Add); + expect(spectator.query('.summary-title', { read: LabelComponent })?.label).toEqual('My Title'); + }); + + test('formats label', () => { + const labelComponents = spectator.queryAll('.summary-value-title', { read: LabelComponent }); + expect(labelComponents.map(c => c.label)).toEqual([ + 'Number', + 'Number Array', + 'String', + 'String Array', + 'Boolean', + 'Boolean Array' + ]); + }); + + test('gets value array', () => { + const values = spectator.queryAll('li').map(e => e.textContent); + expect(values).toEqual(['0', '0', '1', '2', 'zero', 'zero', 'one', 'two', 'three', 'true', 'true', 'false']); + }); +}); diff --git a/projects/components/src/summary-list/summary-list.component.ts b/projects/components/src/summary-list/summary-list.component.ts new file mode 100644 index 000000000..6b9cc8785 --- /dev/null +++ b/projects/components/src/summary-list/summary-list.component.ts @@ -0,0 +1,44 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; +import { IconType } from '@hypertrace/assets-library'; +import { PrimitiveValue } from '@hypertrace/common'; +import { startCase } from 'lodash-es'; +import { SummaryItem } from './summary-list-api'; + +@Component({ + selector: 'ht-summary-list', + styleUrls: ['./summary-list.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + template: ` +
+
+ + +
+ + +
    +
  • None
  • +
  • {{ value }}
  • +
+
+
+ ` +}) +export class SummaryListComponent { + @Input() + public title?: string; + + @Input() + public icon?: IconType; + + @Input() + public items?: SummaryItem[] = []; + + public getFormattedLabel(label: string): string { + return startCase(label.toLowerCase()); + } + + public getValuesArray(value: PrimitiveValue | PrimitiveValue[]): PrimitiveValue[] { + return Array.isArray(value) ? value : [value]; + } +} diff --git a/projects/components/src/summary-list/summary-list.module.ts b/projects/components/src/summary-list/summary-list.module.ts new file mode 100644 index 000000000..273dc61b8 --- /dev/null +++ b/projects/components/src/summary-list/summary-list.module.ts @@ -0,0 +1,14 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormattingModule } from '@hypertrace/common'; +import { IconModule } from '../icon/icon.module'; +import { LabelModule } from '../label/label.module'; +import { LoadAsyncModule } from '../load-async/load-async.module'; +import { SummaryListComponent } from './summary-list.component'; + +@NgModule({ + declarations: [SummaryListComponent], + exports: [SummaryListComponent], + imports: [CommonModule, LoadAsyncModule, FormattingModule, LabelModule, IconModule] +}) +export class SummaryListModule {} diff --git a/projects/observability/src/shared/components/cartesian/d3/chart/cartesian-chart.ts b/projects/observability/src/shared/components/cartesian/d3/chart/cartesian-chart.ts index 55f9e120e..79b787686 100644 --- a/projects/observability/src/shared/components/cartesian/d3/chart/cartesian-chart.ts +++ b/projects/observability/src/shared/components/cartesian/d3/chart/cartesian-chart.ts @@ -439,11 +439,11 @@ export class DefaultCartesianChart implements CartesianChart { ]; this.allCartesianData = [ - ...this.allSeriesData, ...this.bands.map( band => new CartesianBand(this.d3Utils, this.domRenderer, band, this.scaleBuilder, this.getTooltipTrackingStrategy()) - ) + ), + ...this.allSeriesData ]; }