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: `
+
+ `
+})
+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
];
}