Skip to content

Commit fbbf449

Browse files
authored
feat: adding a new summary values component (#615)
* feat: adding a new summary values component * refactor: addressing review comments * refactor: removed condition * refactor: addressing review comments
1 parent c28675e commit fbbf449

File tree

6 files changed

+252
-1
lines changed

6 files changed

+252
-1
lines changed

projects/components/src/public-api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ export * from './summay-card/summary-card.module';
219219
export * from './summary-value/summary-value.component';
220220
export * from './summary-value/summary-value.module';
221221

222+
// Summary Values
223+
export * from './summary-values/summary-values.component';
224+
export * from './summary-values/summary-values.module';
225+
222226
// Table
223227
export * from './table/controls/table-controls-api';
224228
export * from './table/data/table-data-source';

projects/components/src/summary-value/summary-value.component.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import { IconSize } from '../icon/icon-size';
1111
<ht-icon *ngIf="this.icon" [icon]="this.icon" size="${IconSize.Small}" class="icon"></ht-icon>
1212
<div class="dot" *ngIf="!this.icon"></div>
1313
<div class="label" *ngIf="this.label">{{ this.label }}:</div>
14-
<div class="value" [ngClass]="this.summaryValueDisplayStyle">{{ this.value }}</div>
14+
<div class="value" [ngClass]="this.summaryValueDisplayStyle">
15+
{{ this.value }}
16+
</div>
1517
</div>
1618
`
1719
})
@@ -28,12 +30,21 @@ export class SummaryValueComponent implements OnChanges {
2830
@Input()
2931
public tooltip?: string;
3032

33+
@Input()
34+
public showTooltip?: boolean = true;
35+
3136
@Input()
3237
public summaryValueDisplayStyle: SummaryValueDisplayStyle = SummaryValueDisplayStyle.Text;
3338

3439
public tooltipText?: string;
3540

3641
public ngOnChanges(): void {
42+
if (this.showTooltip) {
43+
this.setTooltipText();
44+
}
45+
}
46+
47+
private setTooltipText(): void {
3748
if (!isEmpty(this.tooltip)) {
3849
this.tooltipText = this.tooltip;
3950
} else if (!isEmpty(this.label) && !isEmpty(this.value)) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@import 'font';
2+
@import 'color-palette';
3+
4+
.ht-summary-values {
5+
display: flex;
6+
flex-direction: row;
7+
align-items: center;
8+
9+
.additional-values {
10+
margin-left: 8px;
11+
background: $gray-2;
12+
border-radius: 4px;
13+
display: flex;
14+
justify-content: center;
15+
align-items: center;
16+
padding: 2px 6px;
17+
18+
.count {
19+
@include overline($gray-7);
20+
}
21+
}
22+
}
23+
24+
.tooltip-contents {
25+
display: flex;
26+
flex-direction: column;
27+
28+
.divider {
29+
width: 100%;
30+
border-bottom: 1px solid white;
31+
}
32+
33+
.value {
34+
@include body-small(white);
35+
}
36+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { IconType } from '@hypertrace/assets-library';
2+
import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest';
3+
import { MockComponent, MockDirective } from 'ng-mocks';
4+
import { SummaryValueComponent, SummaryValueDisplayStyle } from '../summary-value/summary-value.component';
5+
import { TooltipDirective } from '../tooltip/tooltip.directive';
6+
import { SummaryValuesComponent } from './summary-values.component';
7+
8+
describe('Summary Values Component', () => {
9+
let spectator: SpectatorHost<SummaryValuesComponent>;
10+
11+
const createHost = createHostFactory({
12+
component: SummaryValuesComponent,
13+
shallow: true,
14+
declarations: [MockComponent(SummaryValueComponent), MockDirective(TooltipDirective)]
15+
});
16+
17+
test('should pass on inputs correct to child component when multiple values are present', () => {
18+
spectator = createHost(
19+
`<ht-summary-values [values]="values" [icon]="icon" [label]="label" [tooltip]="tooltip" [summaryValueDisplayStyle]="summaryValueDisplayStyle">
20+
</ht-summary-values>`,
21+
{
22+
hostProps: {
23+
values: ['val1', 'val2', 'val3', 'val4', 'val5'],
24+
icon: IconType.Add,
25+
label: 'label',
26+
tooltip: 'label tooltip',
27+
summaryValueDisplayStyle: SummaryValueDisplayStyle.Text
28+
}
29+
}
30+
);
31+
32+
const summaryValueComponent = spectator.query(SummaryValueComponent);
33+
expect(summaryValueComponent).toExist();
34+
expect(summaryValueComponent?.value).toEqual('val1');
35+
expect(summaryValueComponent?.icon).toEqual(IconType.Add);
36+
expect(summaryValueComponent?.label).toEqual('label');
37+
expect(summaryValueComponent?.tooltip).toEqual(undefined);
38+
expect(summaryValueComponent?.summaryValueDisplayStyle).toEqual(SummaryValueDisplayStyle.Text);
39+
40+
expect(spectator.query('.additional-values')).toHaveText('+4');
41+
expect(spectator.query(TooltipDirective)?.content).toBeDefined();
42+
});
43+
44+
test('should hide additional details if single value is present', () => {
45+
spectator = createHost(
46+
`<ht-summary-values [values]="values" [icon]="icon" [label]="label" [tooltip]="tooltip" [summaryValueDisplayStyle]="summaryValueDisplayStyle">
47+
</ht-summary-values>`,
48+
{
49+
hostProps: {
50+
values: ['val1'],
51+
icon: IconType.Add,
52+
label: 'label',
53+
tooltip: 'label tooltip',
54+
summaryValueDisplayStyle: SummaryValueDisplayStyle.Text
55+
}
56+
}
57+
);
58+
59+
const summaryValueComponent = spectator.query(SummaryValueComponent);
60+
expect(summaryValueComponent).toExist();
61+
expect(summaryValueComponent?.value).toEqual('val1');
62+
expect(summaryValueComponent?.icon).toEqual(IconType.Add);
63+
expect(summaryValueComponent?.label).toEqual('label');
64+
expect(summaryValueComponent?.tooltip).toEqual(undefined);
65+
expect(summaryValueComponent?.summaryValueDisplayStyle).toEqual(SummaryValueDisplayStyle.Text);
66+
67+
expect(spectator.query('.additional-values')).not.toExist();
68+
expect(spectator.query(TooltipDirective)?.content).toBeDefined();
69+
});
70+
71+
test('should hide additional details if empty array is passed as value', () => {
72+
spectator = createHost(
73+
`<ht-summary-values [values]="values" [icon]="icon" [label]="label" [tooltip]="tooltip" [summaryValueDisplayStyle]="summaryValueDisplayStyle">
74+
</ht-summary-values>`,
75+
{
76+
hostProps: {
77+
values: [],
78+
icon: IconType.Add,
79+
label: 'label',
80+
tooltip: 'label tooltip',
81+
summaryValueDisplayStyle: SummaryValueDisplayStyle.Text
82+
}
83+
}
84+
);
85+
86+
const summaryValueComponent = spectator.query(SummaryValueComponent);
87+
expect(summaryValueComponent).toExist();
88+
expect(summaryValueComponent?.value).not.toExist();
89+
expect(summaryValueComponent?.icon).toEqual(IconType.Add);
90+
expect(summaryValueComponent?.label).toEqual('label');
91+
expect(summaryValueComponent?.tooltip).toEqual(undefined);
92+
expect(summaryValueComponent?.summaryValueDisplayStyle).toEqual(SummaryValueDisplayStyle.Text);
93+
94+
expect(spectator.query('.additional-values')).not.toExist();
95+
expect(spectator.query(TooltipDirective)?.content).toBeDefined();
96+
});
97+
98+
test('should handle empty strings', () => {
99+
spectator = createHost(
100+
`<ht-summary-values [values]="values" [icon]="icon" [label]="label" [tooltip]="tooltip" [summaryValueDisplayStyle]="summaryValueDisplayStyle">
101+
</ht-summary-values>`,
102+
{
103+
hostProps: {
104+
values: ['', 'val2', 'val3', 'val4', 'val5'],
105+
icon: IconType.Add,
106+
label: 'label',
107+
tooltip: 'label tooltip',
108+
summaryValueDisplayStyle: SummaryValueDisplayStyle.Text
109+
}
110+
}
111+
);
112+
113+
const summaryValueComponent = spectator.query(SummaryValueComponent);
114+
expect(summaryValueComponent).toExist();
115+
expect(summaryValueComponent?.value).toEqual('val2');
116+
expect(summaryValueComponent?.icon).toEqual(IconType.Add);
117+
expect(summaryValueComponent?.label).toEqual('label');
118+
expect(summaryValueComponent?.tooltip).toEqual(undefined);
119+
expect(summaryValueComponent?.summaryValueDisplayStyle).toEqual(SummaryValueDisplayStyle.Text);
120+
121+
expect(spectator.query('.additional-values')).toHaveText('+3');
122+
expect(spectator.query(TooltipDirective)?.content).toBeDefined();
123+
});
124+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
2+
import { TypedSimpleChanges } from '@hypertrace/common';
3+
import { compact } from 'lodash-es';
4+
import { SummaryValueDisplayStyle } from '../summary-value/summary-value.component';
5+
6+
@Component({
7+
selector: 'ht-summary-values',
8+
styleUrls: ['./summary-values.component.scss'],
9+
changeDetection: ChangeDetectionStrategy.OnPush,
10+
template: `
11+
<div class="ht-summary-values" data-sensitive-pii [htTooltip]="detailsTemplate">
12+
<ht-summary-value
13+
[icon]="this.icon"
14+
[value]="this.displayValue"
15+
[label]="this.label"
16+
[showTooltip]="false"
17+
summaryValueDisplayStyle="${SummaryValueDisplayStyle.Text}"
18+
class="summary-value"
19+
></ht-summary-value>
20+
<ng-container *ngIf="this.additionalValues && this.additionalValues!.length > 0">
21+
<div class="additional-values">
22+
<span class="count">+{{ this.additionalValues!.length }}</span>
23+
</div>
24+
</ng-container>
25+
</div>
26+
27+
<ng-template #detailsTemplate>
28+
<div class="tooltip-contents">
29+
<ng-container *ngIf="this.tooltip">
30+
<span class="value">{{ this.tooltip }}</span>
31+
<div class="divider"></div>
32+
</ng-container>
33+
<span *ngFor="let value of this.allValues" class="value">
34+
{{ value }}
35+
</span>
36+
</div>
37+
</ng-template>
38+
`
39+
})
40+
export class SummaryValuesComponent implements OnChanges {
41+
@Input()
42+
public values?: string[];
43+
44+
@Input()
45+
public icon?: string;
46+
47+
@Input()
48+
public label?: string;
49+
50+
@Input()
51+
public tooltip?: string;
52+
53+
public displayValue?: string;
54+
public allValues?: string[];
55+
public additionalValues?: string[];
56+
57+
public ngOnChanges(changes: TypedSimpleChanges<this>): void {
58+
if (changes.values) {
59+
this.allValues = compact(this.values ?? []);
60+
this.displayValue = this.allValues[0] ?? '-';
61+
this.additionalValues = this.allValues?.slice(1);
62+
}
63+
}
64+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { CommonModule } from '@angular/common';
2+
import { NgModule } from '@angular/core';
3+
import { SummaryValueModule } from '../summary-value/summary-value.module';
4+
import { TooltipModule } from '../tooltip/tooltip.module';
5+
import { SummaryValuesComponent } from './summary-values.component';
6+
7+
@NgModule({
8+
imports: [CommonModule, TooltipModule, SummaryValueModule],
9+
declarations: [SummaryValuesComponent],
10+
exports: [SummaryValuesComponent]
11+
})
12+
export class SummaryValuesModule {}

0 commit comments

Comments
 (0)