Skip to content

Commit 2bb7ff1

Browse files
committed
Merge branch 'adding-supporting-functions' of github.com:hypertrace/hypertrace-ui into adding-supporting-functions
2 parents 5bcb65a + 1287dc8 commit 2bb7ff1

25 files changed

+653
-395
lines changed

package-lock.json

Lines changed: 56 additions & 297 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@angular/platform-browser": "^11.2.0",
3636
"@angular/platform-browser-dynamic": "^11.2.0",
3737
"@angular/router": "^11.2.0",
38-
"@apollo/client": "^3.3.14",
38+
"@apollo/client": "^3.3.19",
3939
"@hypertrace/hyperdash": "^1.2.1",
4040
"@hypertrace/hyperdash-angular": "^2.5.0",
4141
"@types/d3-hierarchy": "^2.0.0",
@@ -87,13 +87,13 @@
8787
"@types/d3-selection": "^1.4.2",
8888
"@types/d3-shape": "^1.3.1",
8989
"@types/d3-zoom": "^1.7.5",
90-
"@types/jest": "^26.0.22",
90+
"@types/jest": "^26.0.23",
9191
"@types/lodash-es": "^4.17.4",
9292
"@types/node": "^14.14.41",
9393
"@types/uuid": "^8.3.0",
9494
"@types/webpack-env": "^1.14.0",
9595
"codelyzer": "^6.0.1",
96-
"commitizen": "^4.2.3",
96+
"commitizen": "^4.2.4",
9797
"cz-conventional-changelog": "^3.3.0",
9898
"husky": "^4.3.8",
9999
"jest": "^26.6.3",

projects/assets-library/assets/styles/_interaction.scss

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99

1010
@mixin card-hover {
1111
&:hover {
12-
@include dropdown-box-shadow();
12+
@include box-shadow-2dp();
1313
cursor: pointer;
1414
}
1515
}
1616

1717
@mixin filter-box-shadow {
18+
@include box-shadow-2dp();
19+
}
20+
21+
@mixin box-shadow-2dp {
1822
box-shadow: 2px 2px 2px rgba(225, 228, 229, 0.5), 2px -2px 4px rgba(225, 228, 229, 0.2),
1923
0px 2px 4px rgba(225, 228, 229, 0.2);
2024
}

projects/common/src/navigation/navigation.service.test.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import { Router, UrlSegment } from '@angular/router';
33
import { RouterTestingModule } from '@angular/router/testing';
44
import { patchRouterNavigateForTest } from '@hypertrace/test-utils';
55
import { createServiceFactory, mockProvider, SpectatorService } from '@ngneat/spectator/jest';
6-
import { NavigationParamsType, NavigationService } from './navigation.service';
6+
import {
7+
ExternalNavigationPathParams,
8+
ExternalNavigationWindowHandling,
9+
NavigationParams,
10+
NavigationParamsType,
11+
NavigationService
12+
} from './navigation.service';
713

814
describe('Navigation Service', () => {
915
const firstChildRouteConfig = {
@@ -234,4 +240,49 @@ describe('Navigation Service', () => {
234240
]);
235241
expect(spectator.service.getCurrentActivatedRoute().snapshot.queryParams).toEqual({ global: 'foo' });
236242
});
243+
244+
test('construct external url in case useGlobalParams is set to true', () => {
245+
const externalNavigationParams: NavigationParams = {
246+
navType: NavigationParamsType.External,
247+
useGlobalParams: true,
248+
url: '/some/internal/path/of/app',
249+
windowHandling: ExternalNavigationWindowHandling.NewWindow
250+
};
251+
252+
spectator.service.addQueryParametersToUrl({ time: '1h', environment: 'development' });
253+
spectator.service.registerGlobalQueryParamKey('time');
254+
spectator.service.registerGlobalQueryParamKey('environment');
255+
256+
externalNavigationParams.useGlobalParams = true;
257+
258+
expect(Array.isArray(spectator.service.buildNavigationParams(externalNavigationParams).path)).toBe(true);
259+
expect(spectator.service.buildNavigationParams(externalNavigationParams).path[1]).toHaveProperty(
260+
ExternalNavigationPathParams.Url
261+
);
262+
263+
let pathParam = spectator.service.buildNavigationParams(externalNavigationParams).path[1];
264+
expect(typeof pathParam).not.toBe('string');
265+
266+
if (typeof pathParam !== 'string') {
267+
expect(pathParam[ExternalNavigationPathParams.Url]).toBe(
268+
`${externalNavigationParams.url}?time=1h&environment=development`
269+
);
270+
}
271+
272+
externalNavigationParams.url = '/some/internal/path/of/app?type=json';
273+
274+
expect(Array.isArray(spectator.service.buildNavigationParams(externalNavigationParams).path)).toBe(true);
275+
expect(spectator.service.buildNavigationParams(externalNavigationParams).path[1]).toHaveProperty(
276+
ExternalNavigationPathParams.Url
277+
);
278+
279+
pathParam = spectator.service.buildNavigationParams(externalNavigationParams).path[1];
280+
expect(typeof pathParam).not.toBe('string');
281+
282+
if (typeof pathParam !== 'string') {
283+
expect(pathParam[ExternalNavigationPathParams.Url]).toBe(
284+
`/some/internal/path/of/app?type=json&time=1h&environment=development`
285+
);
286+
}
287+
});
237288
});

projects/common/src/navigation/navigation.service.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ export class NavigationService {
7373
return this.currentParamMap.getAll(parameterName);
7474
}
7575

76+
public constructExternalUrl(urlString: string): string {
77+
const inputUrlTree: UrlTree = this.router.parseUrl(urlString);
78+
const globalQueryParams: Params = {};
79+
80+
this.globalQueryParams.forEach(key => {
81+
const paramValue = this.getQueryParameter(key, '');
82+
if (paramValue !== '') {
83+
globalQueryParams[key] = paramValue;
84+
}
85+
});
86+
87+
inputUrlTree.queryParams = { ...inputUrlTree.queryParams, ...globalQueryParams };
88+
89+
return this.router.serializeUrl(inputUrlTree);
90+
}
91+
7692
public buildNavigationParams(
7793
paramsOrUrl: NavigationParams | string
7894
): { path: NavigationPath; extras?: NavigationExtras } {
@@ -84,7 +100,9 @@ export class NavigationService {
84100
path: [
85101
'/external',
86102
{
87-
[ExternalNavigationPathParams.Url]: params.url,
103+
[ExternalNavigationPathParams.Url]: params.useGlobalParams
104+
? this.constructExternalUrl(params.url)
105+
: params.url,
88106
[ExternalNavigationPathParams.WindowHandling]: params.windowHandling
89107
}
90108
],
@@ -318,7 +336,7 @@ export interface QueryParamObject extends Params {
318336

319337
export type NavigationPath = string | (string | Dictionary<string>)[];
320338

321-
export type NavigationParams = InAppNavigationParams | ExternalNavigationParamsNew;
339+
export type NavigationParams = InAppNavigationParams | ExternalNavigationParams;
322340
export interface InAppNavigationParams {
323341
navType: NavigationParamsType.InApp;
324342
path: NavigationPath;
@@ -328,11 +346,11 @@ export interface InAppNavigationParams {
328346
relativeTo?: ActivatedRoute;
329347
}
330348

331-
export interface ExternalNavigationParamsNew {
349+
export interface ExternalNavigationParams {
332350
navType: NavigationParamsType.External;
333351
url: string;
334352
windowHandling: ExternalNavigationWindowHandling; // Currently an enum called NavigationType
335-
queryParams?: QueryParamObject;
353+
useGlobalParams?: boolean;
336354
}
337355

338356
export const enum ExternalNavigationPathParams {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@import 'color-palette';
2+
3+
.download-json {
4+
width: 40px;
5+
height: 40px;
6+
display: flex;
7+
justify-content: center;
8+
align-items: center;
9+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Renderer2 } from '@angular/core';
2+
import { fakeAsync } from '@angular/core/testing';
3+
import { RouterTestingModule } from '@angular/router/testing';
4+
import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
5+
import { MockComponent } from 'ng-mocks';
6+
import { Observable, of } from 'rxjs';
7+
import { ButtonComponent } from '../button/button.component';
8+
import { IconComponent } from '../icon/icon.component';
9+
import { DownloadJsonComponent } from './download-json.component';
10+
import { DownloadJsonModule } from './download-json.module';
11+
12+
describe('Download Json Component', () => {
13+
let spectator: Spectator<DownloadJsonComponent>;
14+
const mockElement = document.createElement('a');
15+
const createElementSpy = jest.fn().mockReturnValue(mockElement);
16+
17+
const createHost = createHostFactory({
18+
component: DownloadJsonComponent,
19+
imports: [DownloadJsonModule, RouterTestingModule],
20+
declarations: [MockComponent(ButtonComponent), MockComponent(IconComponent)],
21+
providers: [
22+
mockProvider(Document, {
23+
createElement: createElementSpy
24+
}),
25+
mockProvider(Renderer2, {
26+
setAttribute: jest.fn()
27+
})
28+
],
29+
shallow: true
30+
});
31+
32+
const dataSource$: Observable<unknown> = of({
33+
spans: []
34+
});
35+
36+
test('should have only download button, when data is not loading', () => {
37+
spectator = createHost(`<ht-download-json [dataSource]="dataSource"></ht-download-json>`, {
38+
hostProps: {
39+
dataSource: dataSource$
40+
}
41+
});
42+
43+
expect(spectator.query(ButtonComponent)).toExist();
44+
});
45+
46+
test('should download json file', fakeAsync(() => {
47+
spectator = createHost(`<ht-download-json [dataSource]="dataSource"></ht-download-json>`, {
48+
hostProps: {
49+
dataSource: dataSource$
50+
}
51+
});
52+
53+
spyOn(spectator.component, 'triggerDownload');
54+
55+
expect(spectator.component.dataLoading).toBe(false);
56+
expect(spectator.component.fileName).toBe('download.json');
57+
const element = spectator.query('.download-json');
58+
expect(element).toExist();
59+
60+
spectator.click(element!);
61+
spectator.tick();
62+
63+
expect(spectator.component.triggerDownload).toHaveBeenCalledTimes(1);
64+
}));
65+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { DOCUMENT } from '@angular/common';
2+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, Renderer2 } from '@angular/core';
3+
import { IconType } from '@hypertrace/assets-library';
4+
import { IconSize } from '@hypertrace/components';
5+
import { Observable } from 'rxjs';
6+
import { catchError, finalize, take } from 'rxjs/operators';
7+
import { ButtonSize, ButtonStyle } from '../button/button';
8+
import { NotificationService } from '../notification/notification.service';
9+
10+
@Component({
11+
selector: 'ht-download-json',
12+
changeDetection: ChangeDetectionStrategy.OnPush,
13+
styleUrls: ['./download-json.component.scss'],
14+
template: `
15+
<div class="download-json" (click)="this.triggerDownload()">
16+
<ht-button
17+
*ngIf="!this.dataLoading"
18+
class="download-button"
19+
icon="${IconType.Download}"
20+
display="${ButtonStyle.Text}"
21+
size="${ButtonSize.Large}"
22+
></ht-button>
23+
<ht-icon *ngIf="this.dataLoading" icon="${IconType.Loading}" size="${IconSize.Large}"></ht-icon>
24+
</div>
25+
`
26+
})
27+
export class DownloadJsonComponent {
28+
@Input()
29+
public dataSource!: Observable<unknown>;
30+
31+
@Input()
32+
public fileName: string = 'download.json';
33+
34+
public dataLoading: boolean = false;
35+
private readonly dlJsonAnchorElement: HTMLAnchorElement;
36+
37+
public constructor(
38+
@Inject(DOCUMENT) private readonly document: Document,
39+
private readonly renderer: Renderer2,
40+
private readonly changeDetector: ChangeDetectorRef,
41+
private readonly notificationService: NotificationService
42+
) {
43+
this.dlJsonAnchorElement = this.document.createElement('a');
44+
}
45+
46+
public triggerDownload(): void {
47+
this.dataLoading = true;
48+
this.dataSource
49+
.pipe(
50+
take(1),
51+
catchError(() => this.notificationService.createFailureToast('Download failed')),
52+
finalize(() => {
53+
this.dataLoading = false;
54+
this.changeDetector.detectChanges();
55+
})
56+
)
57+
.subscribe((data: unknown) => {
58+
if (typeof data === 'string') {
59+
this.downloadData(data);
60+
} else {
61+
this.downloadData(JSON.stringify(data));
62+
}
63+
});
64+
}
65+
66+
private downloadData(data: string): void {
67+
this.renderer.setAttribute(
68+
this.dlJsonAnchorElement,
69+
'href',
70+
`data:text/json;charset=utf-8,${encodeURIComponent(data)}`
71+
);
72+
this.renderer.setAttribute(this.dlJsonAnchorElement, 'download', this.fileName);
73+
this.renderer.setAttribute(this.dlJsonAnchorElement, 'display', 'none');
74+
this.dlJsonAnchorElement.click();
75+
}
76+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { CommonModule } from '@angular/common';
2+
import { NgModule } from '@angular/core';
3+
import { ButtonModule } from '../button/button.module';
4+
import { IconModule } from '../icon/icon.module';
5+
import { NotificationModule } from '../notification/notification.module';
6+
import { DownloadJsonComponent } from './download-json.component';
7+
8+
@NgModule({
9+
declarations: [DownloadJsonComponent],
10+
imports: [CommonModule, ButtonModule, NotificationModule, IconModule],
11+
exports: [DownloadJsonComponent]
12+
})
13+
export class DownloadJsonModule {}

projects/components/src/multi-select/multi-select.component.scss

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,22 @@
5454
display: flex;
5555
width: 100%;
5656
align-items: center;
57-
padding-left: 8px;
5857

5958
.trigger-label {
6059
@include body-2-medium();
6160
padding-left: 8px;
61+
width: calc(100% - 32px - 38px);
62+
flex-grow: 1;
63+
}
64+
65+
.trigger-more-items {
66+
@include semi-bold();
67+
@include font-label($gray-9);
68+
background-color: $gray-2;
69+
padding: 0 4px;
70+
border-radius: 4px;
71+
margin-left: 8px;
72+
max-width: 38px;
6273
}
6374

6475
.trigger-icon {

0 commit comments

Comments
 (0)