Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion projects/common/src/navigation/navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ export class NavigationService {
}

export interface QueryParamObject extends Params {
[key: string]: string | string[] | number | number[] | undefined;
[key: string]: string | string[] | boolean | boolean[] | number | number[] | undefined;
}

export type NavigationPath = string | (string | Dictionary<string>)[];
Expand Down
188 changes: 105 additions & 83 deletions projects/observability/src/pages/explorer/explorer.component.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { discardPeriodicTasks, fakeAsync } from '@angular/core/testing';
import { Provider } from '@angular/core';
import { fakeAsync } from '@angular/core/testing';
import { ActivatedRoute, convertToParamMap } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { IconLibraryTestingModule } from '@hypertrace/assets-library';
Expand All @@ -16,7 +17,8 @@ import {
FilterAttributeType,
FilterBarComponent,
FilterBuilderLookupService,
FilterOperator
FilterOperator,
ToggleGroupComponent
} from '@hypertrace/components';
import { GraphQlRequestService } from '@hypertrace/graphql-client';
import { getMockFlexLayoutProviders, patchRouterNavigateForTest } from '@hypertrace/test-utils';
Expand All @@ -25,6 +27,10 @@ import { EMPTY, NEVER, of } from 'rxjs';
import { startWith } from 'rxjs/operators';
import { CartesianSeriesVisualizationType } from '../../shared/components/cartesian/chart';
import { ExploreQueryEditorComponent } from '../../shared/components/explore-query-editor/explore-query-editor.component';
import { ExploreQueryGroupByEditorComponent } from '../../shared/components/explore-query-editor/group-by/explore-query-group-by-editor.component';
import { ExploreQueryIntervalEditorComponent } from '../../shared/components/explore-query-editor/interval/explore-query-interval-editor.component';
import { ExploreQueryLimitEditorComponent } from '../../shared/components/explore-query-editor/limit/explore-query-limit-editor.component';
import { ExploreQuerySeriesEditorComponent } from '../../shared/components/explore-query-editor/series/explore-query-series-editor.component';
import { MetricAggregationType } from '../../shared/graphql/model/metrics/metric-aggregation';
import { GraphQlFieldFilter } from '../../shared/graphql/model/schema/filter/field/graphql-field-filter';
import { GraphQlOperatorType } from '../../shared/graphql/model/schema/filter/graphql-filter';
Expand Down Expand Up @@ -109,27 +115,37 @@ describe('Explorer component', () => {

const detectQueryChange = () => {
spectator.detectChanges(); // Detect whatever caused the change
spectator.tick(200); // Query emits async, tick here triggers building the DOM for the query
discardPeriodicTasks(); // Some of the newly instantiated components also uses async, need to wait for them to settle
spectator.tick(200);
spectator.tick(50); // Query emits async, tick here triggers building the DOM for the query
// Break up the ticks into multiple to account for various async handoffs
spectator.tick();
spectator.tick(100);
};

const init = (...params: Parameters<typeof createComponent>) => {
spectator = createComponent(...params);
const init = (...mockProviders: Provider[]) => {
spectator = createComponent({
providers: [
{
provide: ActivatedRoute,
useValue: {
queryParamMap: of(convertToParamMap({}))
}
},
...mockProviders
]
});
spectator.tick();
patchRouterNavigateForTest(spectator);
detectQueryChange();
querySpy = spectator.inject(GraphQlRequestService).query;
};

test('fires query on init for traces', fakeAsync(() => {
init({
providers: [
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
]
});
init(
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
);

// Traces tab is auto selected
expect(querySpy).toHaveBeenNthCalledWith(
2,
Expand All @@ -142,8 +158,10 @@ describe('Explorer component', () => {
expect.objectContaining({})
);

expect(querySpy).toHaveBeenNthCalledWith(
3,
// RunFakeRxjs(({ expectObservable }) => {
// ExpectObservable(spectator.component.resultsDashboard$).toBe('x', { x: undefined });
// });
expect(querySpy).toHaveBeenCalledWith(
expect.objectContaining({
requestType: TRACES_GQL_REQUEST,
filters: [],
Expand All @@ -154,13 +172,11 @@ describe('Explorer component', () => {
}));

test('fires query on filter change for traces', fakeAsync(() => {
init({
providers: [
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
]
});
init(
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
);
const filterBar = spectator.query(FilterBarComponent)!;

// tslint:disable-next-line: no-object-literal-type-assertion
Expand Down Expand Up @@ -202,13 +218,11 @@ describe('Explorer component', () => {
}));

test('fires query on init for spans', fakeAsync(() => {
init({
providers: [
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
]
});
init(
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
);
querySpy.mockClear();

// Select Spans tab
Expand Down Expand Up @@ -238,13 +252,11 @@ describe('Explorer component', () => {
}));

test('fires query on init for traces', fakeAsync(() => {
init({
providers: [
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
]
});
init(
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
);
// Select traces tab
spectator.click(spectator.queryAll('ht-toggle-item')[1]);
detectQueryChange();
Expand Down Expand Up @@ -291,13 +303,11 @@ describe('Explorer component', () => {
}));

test('traces table fires query on series change', fakeAsync(() => {
init({
providers: [
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
]
});
init(
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
);
spectator.query(ExploreQueryEditorComponent)!.setSeries([buildSeries('second', MetricAggregationType.Average)]);

detectQueryChange();
Expand All @@ -315,13 +325,11 @@ describe('Explorer component', () => {
}));

test('visualization fires query on series change', fakeAsync(() => {
init({
providers: [
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
]
});
init(
mockProvider(GraphQlRequestService, {
query: jest.fn().mockReturnValueOnce(of(mockAttributes)).mockReturnValue(EMPTY)
})
);
querySpy.mockClear();

spectator.query(ExploreQueryEditorComponent)!.setSeries([buildSeries('second', MetricAggregationType.Average)]);
Expand All @@ -340,46 +348,60 @@ describe('Explorer component', () => {
);
}));

test('updates URL with query param when context toggled', fakeAsync(() => {
test('updates URL with query param when query updated', fakeAsync(() => {
init();
const queryParamChangeSpy = spyOn(spectator.inject(NavigationService), 'addQueryParametersToUrl');
// Select Spans tab
spectator.click(spectator.queryAll('ht-toggle-item')[1]);
spectator.query(ExploreQueryEditorComponent)!.setSeries([buildSeries('second', MetricAggregationType.Average)]);
spectator.query(ExploreQueryEditorComponent)!.setInterval(new TimeDuration(30, TimeUnit.Second));
spectator.query(ExploreQueryEditorComponent)!.updateGroupByKey(
{
keys: ['apiName'],
limit: 6,
includeRest: true
},
'apiName'
);
detectQueryChange();
expect(queryParamChangeSpy).toHaveBeenLastCalledWith(expect.objectContaining({ scope: 'spans' }));

// Select Endpoint traces tab
spectator.click(spectator.queryAll('ht-toggle-item')[0]);
detectQueryChange();
expect(queryParamChangeSpy).toHaveBeenLastCalledWith(expect.objectContaining({ scope: 'endpoint-traces' }));
}));

test('selects tab based on url', fakeAsync(() => {
init({
providers: [
{
provide: ActivatedRoute,
useValue: {
queryParamMap: of(convertToParamMap({ scope: 'spans' }))
}
}
]
expect(queryParamChangeSpy).toHaveBeenLastCalledWith({
scope: 'spans',
series: ['column:avg(second)'],
group: 'apiName',
limit: 6,
other: true,
interval: '30s'
});
expect(spectator.component.context).toBe(SPAN_SCOPE);
}));

test('defaults to endpoints and sets url', fakeAsync(() => {
test('sets state based on url', fakeAsync(() => {
init({
providers: [
{
provide: ActivatedRoute,
useValue: {
queryParamMap: of(convertToParamMap({}))
}
}
]
provide: ActivatedRoute,
useValue: {
queryParamMap: of(
convertToParamMap({
scope: 'spans',
series: 'line:distinct_count(apiName)',
group: 'apiName',
limit: '6',
other: 'true',
interval: '30s'
})
)
}
});
expect(spectator.component.context).toBe(ObservabilityTraceType.Api);
expect(spectator.inject(NavigationService).getQueryParameter('scope', 'unset')).toEqual('endpoint-traces');
expect(spectator.query(ToggleGroupComponent)?.activeItem?.label).toBe('Spans');
expect(spectator.query(ExploreQueryGroupByEditorComponent)?.groupByKey).toBe('apiName');
expect(spectator.query(ExploreQueryLimitEditorComponent)?.limit).toBe(6);
expect(spectator.query(ExploreQueryLimitEditorComponent)?.includeRest).toBe(true);
expect(spectator.query(ExploreQuerySeriesEditorComponent)?.series).toEqual({
specification: expect.objectContaining({
aggregation: MetricAggregationType.DistinctCount,
name: 'apiName'
}),
visualizationOptions: { type: CartesianSeriesVisualizationType.Line }
});
expect(spectator.query(ExploreQueryIntervalEditorComponent)?.interval).toEqual(
new TimeDuration(30, TimeUnit.Second)
);
}));
});
Loading