Skip to content

Commit 682e80c

Browse files
alok-traceableAlok Singh
andauthored
Open in new tab (#856)
* feature: adding open in new tab icon on security overlay * feat: changes for open in new tab component * fix: file rename and unti tests * fix: lint fixes * fix: lint fixes * fix: pr suggested changes * fix: pr suggested changes * fix: lint fixes * fix: pr suggested changes * feat: pr suggested changes * feat: pr suggested changes * fix: merging main * fix: imporving code coverage * fix: removing unused utilities Co-authored-by: Alok Singh <[email protected]>
1 parent cadf4b6 commit 682e80c

File tree

5 files changed

+131
-69
lines changed

5 files changed

+131
-69
lines changed

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
],
@@ -304,7 +322,7 @@ export interface QueryParamObject extends Params {
304322

305323
export type NavigationPath = string | (string | Dictionary<string>)[];
306324

307-
export type NavigationParams = InAppNavigationParams | ExternalNavigationParamsNew;
325+
export type NavigationParams = InAppNavigationParams | ExternalNavigationParams;
308326
export interface InAppNavigationParams {
309327
navType: NavigationParamsType.InApp;
310328
path: NavigationPath;
@@ -314,11 +332,11 @@ export interface InAppNavigationParams {
314332
relativeTo?: ActivatedRoute;
315333
}
316334

317-
export interface ExternalNavigationParamsNew {
335+
export interface ExternalNavigationParams {
318336
navType: NavigationParamsType.External;
319337
url: string;
320338
windowHandling: ExternalNavigationWindowHandling; // Currently an enum called NavigationType
321-
queryParams?: QueryParamObject;
339+
useGlobalParams?: boolean;
322340
}
323341

324342
export const enum ExternalNavigationPathParams {
Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import { fakeAsync } from '@angular/core/testing';
2-
import {
3-
ExternalNavigationWindowHandling,
4-
NavigationParamsType,
5-
NavigationService,
6-
TimeRangeService
7-
} from '@hypertrace/common';
2+
import { NavigationService, TimeRangeService } from '@hypertrace/common';
83
import { createHostFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
94
import { MockComponent } from 'ng-mocks';
10-
import { ButtonComponent } from '../button/button.component';
5+
import { IconSize } from '../icon/icon-size';
6+
import { IconComponent } from '../icon/icon.component';
7+
import { LinkComponent } from '../link/link.component';
118
import { OpenInNewTabComponent } from './open-in-new-tab.component';
129

1310
describe('Open in new tab component', () => {
@@ -16,42 +13,60 @@ describe('Open in new tab component', () => {
1613
const createHost = createHostFactory({
1714
shallow: true,
1815
component: OpenInNewTabComponent,
19-
declarations: [MockComponent(ButtonComponent)],
16+
declarations: [MockComponent(LinkComponent), MockComponent(IconComponent)],
2017
providers: [
2118
mockProvider(TimeRangeService, {
2219
getShareableCurrentUrl: () => 'url-from-timerangeservice'
2320
}),
24-
mockProvider(NavigationService)
21+
mockProvider(NavigationService, {
22+
buildNavigationParams: jest.fn().mockReturnValue({
23+
path: [
24+
'/external',
25+
{
26+
url: 'http://test.hypertrace.ai',
27+
navType: 'same_window'
28+
}
29+
],
30+
extras: { skipLocationChange: true }
31+
})
32+
})
2533
]
2634
});
2735

28-
test('should call navigate as expected when URL input is not specified', fakeAsync(() => {
29-
spectator = createHost('<ht-open-in-new-tab></ht-open-in-new-tab>');
30-
31-
spectator.click('.open-in-new-tab-button');
32-
spectator.tick();
33-
34-
expect(spectator.inject(NavigationService).navigate).toHaveBeenCalledWith({
35-
navType: NavigationParamsType.External,
36-
windowHandling: ExternalNavigationWindowHandling.NewWindow,
37-
url: 'url-from-timerangeservice'
36+
test('Open in new tab component should not be displayed if paramsOrUrl is undefined', () => {
37+
spectator = createHost(`<ht-open-in-new-tab [paramsOrUrl]="paramsOrUrl"></ht-open-in-new-tab>`, {
38+
hostProps: {
39+
paramsOrUrl: undefined
40+
}
3841
});
39-
}));
42+
expect(spectator.query('.open-in-new-tab')).not.toExist();
43+
});
4044

41-
test('should call navigate as expected when URL input is specified', fakeAsync(() => {
42-
spectator = createHost('<ht-open-in-new-tab [url]="url"></ht-open-in-new-tab>', {
45+
test(`Open in new tab component should exist if paramsOrUrl is not undefined`, fakeAsync(() => {
46+
spectator = createHost(`<ht-open-in-new-tab [paramsOrUrl]="paramsOrUrl"></ht-open-in-new-tab>`, {
4347
hostProps: {
44-
url: 'input-url'
48+
paramsOrUrl: {}
4549
}
4650
});
51+
expect(spectator.query('.open-in-new-tab')).toExist();
52+
expect(spectator.query('ht-link')).toExist();
53+
// Default value of icon size
54+
expect(spectator.component.iconSize).toBe(IconSize.Medium);
55+
}));
4756

48-
spectator.click('.open-in-new-tab-button');
49-
spectator.tick();
50-
51-
expect(spectator.inject(NavigationService).navigate).toHaveBeenCalledWith({
52-
navType: NavigationParamsType.External,
53-
windowHandling: ExternalNavigationWindowHandling.NewWindow,
54-
url: 'input-url'
55-
});
57+
test(`Open in new tab component should contain icon of passed size`, fakeAsync(() => {
58+
spectator = createHost(
59+
`<ht-open-in-new-tab [paramsOrUrl]="paramsOrUrl" [iconSize]="iconSize" ></ht-open-in-new-tab>`,
60+
{
61+
hostProps: {
62+
paramsOrUrl: {},
63+
iconSize: IconSize.Small
64+
}
65+
}
66+
);
67+
expect(spectator.query('.open-in-new-tab')).toExist();
68+
expect(spectator.query('ht-link')).toExist();
69+
// Expected value of icon size if pass
70+
expect(spectator.component.iconSize).toBe(IconSize.Small);
5671
}));
5772
});
Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,23 @@
11
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
22
import { IconType } from '@hypertrace/assets-library';
3-
import {
4-
ExternalNavigationWindowHandling,
5-
NavigationParamsType,
6-
NavigationService,
7-
TimeRangeService
8-
} from '@hypertrace/common';
9-
import { ButtonSize, ButtonStyle } from '../button/button';
3+
import { ExternalNavigationParams } from '@hypertrace/common';
4+
import { IconSize } from '../icon/icon-size';
105

116
@Component({
127
selector: 'ht-open-in-new-tab',
138
changeDetection: ChangeDetectionStrategy.OnPush,
149
template: `
15-
<div class="open-in-new-tab" htTooltip="Open in a new tab">
16-
<ht-button
17-
class="open-in-new-tab-button"
18-
display="${ButtonStyle.Outlined}"
19-
[size]="this.size"
20-
icon="${IconType.OpenInNewTab}"
21-
(click)="this.openInNewTab()"
22-
></ht-button>
10+
<div *ngIf="this.paramsOrUrl" class="open-in-new-tab" htTooltip="Open in a new tab">
11+
<ht-link [paramsOrUrl]="this.paramsOrUrl">
12+
<ht-icon icon="${IconType.OpenInNewTab}" [size]="this.iconSize"></ht-icon>
13+
</ht-link>
2314
</div>
2415
`
2516
})
2617
export class OpenInNewTabComponent {
2718
@Input()
28-
public size?: ButtonSize = ButtonSize.Small;
19+
public paramsOrUrl?: ExternalNavigationParams | string;
2920

3021
@Input()
31-
public url?: string;
32-
33-
public constructor(
34-
private readonly navigationService: NavigationService,
35-
private readonly timeRangeService: TimeRangeService
36-
) {}
37-
38-
public openInNewTab(): void {
39-
this.navigationService.navigate({
40-
navType: NavigationParamsType.External,
41-
windowHandling: ExternalNavigationWindowHandling.NewWindow,
42-
// Use input url if available. Else construct a shareable URL for the page
43-
url: this.url ?? this.timeRangeService.getShareableCurrentUrl()
44-
});
45-
}
22+
public iconSize: IconSize = IconSize.Medium;
4623
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { CommonModule } from '@angular/common';
22
import { NgModule } from '@angular/core';
3-
import { ButtonModule } from '../button/button.module';
3+
import { IconModule } from '../icon/icon.module';
4+
import { LinkModule } from '../link/link.module';
45
import { TooltipModule } from '../tooltip/tooltip.module';
56
import { OpenInNewTabComponent } from './open-in-new-tab.component';
67

78
@NgModule({
89
declarations: [OpenInNewTabComponent],
910
exports: [OpenInNewTabComponent],
10-
imports: [CommonModule, ButtonModule, TooltipModule]
11+
imports: [CommonModule, TooltipModule, LinkModule, IconModule]
1112
})
1213
export class OpenInNewTabModule {}

0 commit comments

Comments
 (0)