Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('Entity Renderer Component', () => {

expect(rendererElement).toHaveClass('navigable');
spectator.dispatchFakeEvent(rendererElement, 'click');
expect(entityNavService.navigateToEntity).toHaveBeenCalledWith(entity);
expect(entityNavService.navigateToEntity).toHaveBeenCalledWith(entity, false);
});

test('renders an entity without icon by default', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class EntityRendererComponent implements OnChanges {
@Input()
public entity?: Entity;

@Input()
public inactive: boolean = false;

@Input()
public navigable: boolean = true;

Expand Down Expand Up @@ -59,8 +62,9 @@ export class EntityRendererComponent implements OnChanges {
this.setIconType();
}
}

public onClickNavigate(): void {
this.navigable && this.entity && this.entityNavService.navigateToEntity(this.entity);
this.navigable && this.entity && this.entityNavService.navigateToEntity(this.entity, this.inactive);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does inactive do? why do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an entity is inactive, it means we haven't seen any spans in the time range. Therefore, some of the screens won't show any data available and we may want to navigate to alternative pages instead for those APIs when drilled in to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is it same as under discovery entity?
The name is not clear honestly. navigationFunction(entityId, sourceRoute, isInactive) i guess this alternate url would be defined in the entity map?

}

private setName(): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import { TableCellParser, TableCellParserBase, TableRow } from '@hypertrace/components';
import { Entity, entityIdKey } from '../../../../graphql/model/schema/entity';
import { ObservabilityTableCellType } from '../../observability-table-cell-type';
import { parseEntityFromTableRow } from './entity-table-cell-renderer-util';
import { isInactiveEntity, parseEntityFromTableRow } from './entity-table-cell-renderer-util';

@TableCellParser({
type: ObservabilityTableCellType.Entity
})
export class EntityTableCellParser extends TableCellParserBase<CellData, CellData, string | undefined> {
public parseValue(cellData: CellData, row: TableRow): CellData {
return parseEntityFromTableRow(cellData, row);
const entity = parseEntityFromTableRow(cellData, row);

if (entity === undefined) {
return undefined;
}

return {
...entity,
isInactive: isInactiveEntity(row) === true
};
}

public parseFilterValue(cellData: CellData): string | undefined {
return cellData !== undefined ? cellData[entityIdKey] : undefined;
}
}

type CellData = Entity | undefined;
type CellData = MaybeInactiveEntity | undefined;

export interface MaybeInactiveEntity extends Entity {
isInactive: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Dictionary } from '@hypertrace/common';
import { TableRow } from '@hypertrace/components';
import { isMetricAggregation, MetricAggregation } from '@hypertrace/distributed-tracing';
import { isNull } from 'lodash-es';
import { Entity, Interaction } from '../../../../graphql/model/schema/entity';
import { EntitySpecificationBuilder } from '../../../../graphql/request/builders/specification/entity/entity-specification-builder';

Expand Down Expand Up @@ -27,3 +30,18 @@ export const parseEntityFromTableRow = (cell: Entity | undefined, row: Dictionar
};

const isInteraction = (neighbor: unknown): neighbor is Interaction => typeof neighbor === 'object';

export const isInactiveEntity = (row: TableRow): boolean | undefined => {
const metricAggregations = filterMetricAggregations(row);

if (metricAggregations.length === 0) {
// If an aggregation wasn't fetched, we have no way of knowing if this Entity is inactive.
return undefined;
}

return metricAggregations.every(metricAggregation => !isValidMetricAggregation(metricAggregation));
};

const filterMetricAggregations = (row: TableRow): MetricAggregation[] => Object.values(row).filter(isMetricAggregation);

const isValidMetricAggregation = (metricAggregation: MetricAggregation): boolean => !isNull(metricAggregation.value);
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ describe('Entity table cell renderer component', () => {

test('should render a milliseconds only value', () => {
const spectator = buildComponent();
expect(spectator.component.value).toEqual(entity);
expect(spectator.component.value).toEqual({
...entity,
isInactive: false
});
});

test('should render first column with additional css class', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { TableCellAlignmentType, TableCellRenderer, TableCellRendererBase } from '@hypertrace/components';
import { Entity } from '../../../../graphql/model/schema/entity';
import { ObservabilityTableCellType } from '../../observability-table-cell-type';
import { MaybeInactiveEntity } from './entity-table-cell-parser';

@Component({
selector: 'ht-entity-table-cell-renderer',
styleUrls: ['./entity-table-cell-renderer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="fill-container entity-cell" [ngClass]="{ 'first-column': this.isFirstColumn }">
<ht-entity-renderer [entity]="this.value" [navigable]="true"></ht-entity-renderer>
<ht-entity-renderer
[entity]="this.value"
[inactive]="this.value?.isInactive === true"
[navigable]="true"
></ht-entity-renderer>
</div>
`
})
Expand All @@ -18,4 +22,4 @@ import { ObservabilityTableCellType } from '../../observability-table-cell-type'
alignment: TableCellAlignmentType.Left,
parser: ObservabilityTableCellType.Entity
})
export class EntityTableCellRendererComponent extends TableCellRendererBase<Entity | undefined> {}
export class EntityTableCellRendererComponent extends TableCellRendererBase<MaybeInactiveEntity | undefined> {}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { InjectionToken } from '@angular/core';
export interface EntityMetadata {
entityType: string;
icon: string;
detailPath(id: string, sourceRoute?: string): string[];
detailPath(id: string, sourceRoute?: string, inactive?: boolean): string[];
listPath?: string[];
apisListPath?(id: string): string[];
sourceRoutes?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ export class EntityNavigationService {
@Inject(ENTITY_METADATA) private readonly entityMetadata: EntityMetadataMap
) {
Array.from(this.entityMetadata.values()).forEach(item => {
this.registerEntityNavigationAction(item.entityType, (id, sourceRoute) =>
this.navigationService.navigateWithinApp(item.detailPath(id, sourceRoute))
this.registerEntityNavigationAction(item.entityType, (id, sourceRoute, inactive) =>
this.navigationService.navigateWithinApp(item.detailPath(id, sourceRoute, inactive))
);
});
}

private readonly entityNavigationMap: Map<
EntityType,
(id: string, sourceRoute?: string) => Observable<boolean>
(id: string, sourceRoute?: string, inactive?: boolean) => Observable<boolean>
> = new Map();

public navigateToEntity(entity: Entity): Observable<boolean> {
public navigateToEntity(entity: Entity, isInactive?: boolean): Observable<boolean> {
const entityType = entity[entityTypeKey];
const entityId = entity[entityIdKey];
const navigationFunction = this.entityNavigationMap.get(entityType);
Expand All @@ -35,13 +35,13 @@ export class EntityNavigationService {
);

return navigationFunction
? navigationFunction(entityId, sourceRoute)
? navigationFunction(entityId, sourceRoute, isInactive)
: throwError(`Requested entity type not registered for navigation: ${entityType}`);
}

public registerEntityNavigationAction(
entityType: EntityType,
action: (id: string, sourceRoute?: string) => Observable<boolean>
action: (id: string, sourceRoute?: string, inactive?: boolean) => Observable<boolean>
): void {
this.entityNavigationMap.set(entityType, action);
}
Expand Down