Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ describe('Table Controls component', () => {
[searchEnabled]="searchEnabled"
[searchPlaceholder]="searchPlaceholder"
[filterItems]="filterItems"
[modeItems]="modeItems"
[viewItems]="viewItems"
(searchChange)="searchChange($event)"
(filterChange)="filterChange($event)"
(modeChange)="modeChange($event)"
(viewChange)="viewChange($event)"
>
</ht-table-controls>
`
Expand Down Expand Up @@ -103,10 +103,10 @@ describe('Table Controls component', () => {
expect(onChangeSpy).toHaveBeenCalled();
});

test('should provide toggle group items for each mode', () => {
test('should provide toggle group items for each view', () => {
const spectator = createHost(undefined, {
hostProps: {
modeItems: [
viewItems: [
{
label: 'test1',
value: 'TEST1'
Expand All @@ -122,12 +122,12 @@ describe('Table Controls component', () => {
expect(spectator.query(ToggleGroupComponent)?.items?.length).toEqual(2);
});

test('should emit mode when selected', () => {
test('should emit view when selected', () => {
const onChangeSpy = jest.fn();

const spectator = createHost(undefined, {
hostProps: {
modeItems: [
viewItems: [
{
label: 'test1',
value: 'TEST1'
Expand All @@ -137,7 +137,7 @@ describe('Table Controls component', () => {
value: 'TEST2'
}
],
modeChange: onChangeSpy
viewChange: onChangeSpy
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { isEmpty } from 'lodash-es';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { ToggleItem } from '../../toggle-group/toggle-item';
import { TableMode } from '../table-api';
import { SelectChange, SelectFilter } from './table-controls-api';

@Component({
Expand Down Expand Up @@ -60,10 +59,10 @@ import { SelectChange, SelectFilter } from './table-controls-api';

<!-- Mode Toggle -->
<ht-toggle-group
*ngIf="this.modeToggleEnabled"
*ngIf="this.viewToggleEnabled"
class="control mode-toggle-group"
[items]="this.modeItems"
[activeItem]="this.activeModeItem"
[items]="this.viewItems"
[activeItem]="this.activeViewItem"
(activeItemChange)="this.onModeChange($event)"
></ht-toggle-group>
</div>
Expand All @@ -86,10 +85,10 @@ export class TableControlsComponent implements OnChanges {
public activeFilterItem?: ToggleItem;

@Input()
public modeItems?: ToggleItem[] = [];
public viewItems?: ToggleItem[] = [];

@Input()
public activeModeItem?: ToggleItem;
public activeViewItem?: ToggleItem;

// Checkbox filter
@Input()
Expand All @@ -111,10 +110,10 @@ export class TableControlsComponent implements OnChanges {
public readonly filterChange: EventEmitter<ToggleItem> = new EventEmitter<ToggleItem>();

@Output()
public readonly modeChange: EventEmitter<TableMode> = new EventEmitter<TableMode>();
public readonly viewChange: EventEmitter<string> = new EventEmitter<string>();

public get modeToggleEnabled(): boolean {
return !!this.modeItems && this.modeItems.length > 0;
public get viewToggleEnabled(): boolean {
return !!this.viewItems && this.viewItems.length > 0;
}

public get checkboxEnabled(): boolean {
Expand All @@ -126,7 +125,7 @@ export class TableControlsComponent implements OnChanges {
}

public get anyControlsEnabled(): boolean {
return this.modeToggleEnabled || this.checkboxEnabled || this.filterItemsEnabled || !!this.searchEnabled;
return this.viewToggleEnabled || this.checkboxEnabled || this.filterItemsEnabled || !!this.searchEnabled;
}

private readonly searchDebounceSubject: Subject<string> = new Subject<string>();
Expand All @@ -142,8 +141,8 @@ export class TableControlsComponent implements OnChanges {
this.setActiveFilterItem();
}

if (changes.modeItems) {
this.setActiveModeItem();
if (changes.viewItems) {
this.setActiveViewItem();
}
}

Expand All @@ -153,9 +152,9 @@ export class TableControlsComponent implements OnChanges {
}
}

private setActiveModeItem(): void {
if (this.modeItems !== undefined) {
this.activeModeItem = this.modeItems.find(item => item === this.activeModeItem) ?? this.modeItems[0];
private setActiveViewItem(): void {
if (this.viewItems !== undefined) {
this.activeViewItem = this.viewItems.find(item => item === this.activeViewItem) ?? this.viewItems[0];
}
}

Expand All @@ -174,7 +173,7 @@ export class TableControlsComponent implements OnChanges {
this.searchDebounceSubject.next(text);
}

public onModeChange(item: ToggleItem<TableMode>): void {
this.modeChange.emit(item.value);
public onModeChange(item: ToggleItem<string>): void {
this.viewChange.emit(item.value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ export abstract class TableWidgetBaseModel extends BaseModel {
return TableSelectionMode.Single;
}

public setMode(_mode: TableMode): void {
public setView(_view: string): void {
// No-op here, but can be overridden
return;
}

public getModeOptions(): TableMode[] {
public getViewOptions(): string[] {
// No-op here, but can be overridden
return [];
}
Expand All @@ -152,6 +152,10 @@ export abstract class TableWidgetBaseModel extends BaseModel {
return this.filterOptions;
}

public getSearchAttribute(): string | undefined {
return this.searchAttribute;
}

public getCheckboxFilterOption(): TableWidgetCheckboxFilterModel | undefined {
return this.checkboxFilterOption;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ import { filter, first, map, pairwise, share, startWith, switchMap, tap } from '
import { AttributeMetadata, toFilterAttributeType } from '../../../graphql/model/metadata/attribute-metadata';
import { MetadataService } from '../../../services/metadata/metadata.service';
import { InteractionHandler } from '../../interaction/interaction-handler';
import { ModeToggleTableWidgetModel } from './mode-toggle-table-widget.model';
import { TableWidgetBaseModel } from './table-widget-base.model';
import { SpecificationBackedTableColumnDef } from './table-widget-column.model';
import { TableWidgetFilterModel } from './table-widget-filter-model';
import { TableWidgetViewToggleModel } from './table-widget-view-toggle.model';
import { TableWidgetModel } from './table-widget.model';

@Renderer({ modelClass: TableWidgetModel })
@Renderer({ modelClass: ModeToggleTableWidgetModel })
@Renderer({ modelClass: TableWidgetViewToggleModel })
@Component({
selector: 'ht-table-widget-renderer',
styleUrls: ['./table-widget-renderer.component.scss'],
Expand All @@ -53,17 +53,17 @@ import { TableWidgetModel } from './table-widget.model';
<div class="table-content-container">
<ht-table-controls
class="table-controls"
[searchEnabled]="!!this.api.model.searchAttribute"
[searchEnabled]="!!this.api.model.getSearchAttribute()"
[selectFilterItems]="this.selectFilterItems$ | async"
[filterItems]="this.filterItems"
[modeItems]="this.modeItems"
[viewItems]="this.viewItems"
[checkboxLabel]="this.model.getCheckboxFilterOption()?.label"
[checkboxChecked]="this.model.getCheckboxFilterOption()?.checked"
(checkboxCheckedChange)="this.onCheckboxCheckedChange($event)"
(selectChange)="this.onSelectChange($event)"
(searchChange)="this.onSearchChange($event)"
(filterChange)="this.onFilterChange($event)"
(modeChange)="this.onModeChange($event)"
(viewChange)="this.onViewChange($event)"
>
</ht-table-controls>

Expand Down Expand Up @@ -96,8 +96,8 @@ export class TableWidgetRendererComponent
extends WidgetRenderer<TableWidgetBaseModel, TableDataSource<TableRow> | undefined>
implements OnInit {
public filterItems: ToggleItem<TableWidgetFilterModel>[] = [];
public modeItems: ToggleItem<TableMode>[] = [];
public activeMode!: TableMode;
public viewItems: ToggleItem<string>[] = [];
public activeMode!: string;

public selectFilterItems$!: Observable<SelectFilter[]>;

Expand Down Expand Up @@ -151,9 +151,9 @@ export class TableWidgetRendererComponent
value: filterOption
}));

this.modeItems = this.model.getModeOptions().map(modeOption => ({
label: capitalize(modeOption),
value: modeOption
this.viewItems = this.model.getViewOptions().map(viewOption => ({
label: capitalize(viewOption),
value: viewOption
}));

this.maybeEmitInitialCheckboxFilterChange();
Expand Down Expand Up @@ -321,7 +321,7 @@ export class TableWidgetRendererComponent

public onSearchChange(text: string): void {
const searchFilter: TableFilter = {
field: this.api.model.searchAttribute!,
field: this.api.model.getSearchAttribute()!,
operator: FilterOperator.Like,
value: text
};
Expand All @@ -330,7 +330,10 @@ export class TableWidgetRendererComponent

public onModeChange(mode: TableMode): void {
this.activeMode = mode;
this.model.setMode(mode);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

on mode change is only called in on init now right? we can probably just remove this method, that call and change the template to use this.model.mode directly.


public onViewChange(view: string): void {
this.model.setView(view);
this.columnConfigs$ = this.getColumnConfigs();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FilterOperator, TableFilter } from '@hypertrace/components';
import { Model, ModelApi, ModelProperty, STRING_PROPERTY } from '@hypertrace/hyperdash';
import { ModelInject, MODEL_API } from '@hypertrace/hyperdash-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Model({
type: 'table-widget-select-filter',
Expand All @@ -26,7 +27,15 @@ export class TableWidgetSelectFilterModel {
protected readonly api!: ModelApi;

public getData(): Observable<PrimitiveValue[]> {
return this.api.getData<PrimitiveValue[]>();
return this.api.getData<PrimitiveValue[]>().pipe(
map(values => values.filter(value => !this.isEmpty(value))),
map(values => values.sort())
);
}

public isEmpty(value: unknown): boolean {
// Empty values can't be queried through filtering yet, so need to remove them so they don't appear in the dropdown
return value === undefined || value === null || value === '';
}

public getTableFilter(value: unknown): TableFilter {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,78 +1,53 @@
import { TableDataSource, TableMode, TableRow } from '@hypertrace/components';
import { ArrayPropertyTypeInstance, ENUM_TYPE, ModelTemplatePropertyType } from '@hypertrace/dashboards';
import { ARRAY_PROPERTY, Model, ModelApi, ModelJson, ModelProperty } from '@hypertrace/hyperdash';
import { TableDataSource, TableRow } from '@hypertrace/components';
import { ArrayPropertyTypeInstance } from '@hypertrace/dashboards';
import { ARRAY_PROPERTY, Model, ModelApi, ModelProperty, ModelPropertyType } from '@hypertrace/hyperdash';
import { ModelInject, MODEL_API } from '@hypertrace/hyperdash-angular';
import { NEVER, Observable } from 'rxjs';
import { TableWidgetRowSelectionModel } from './selections/table-widget-row-selection.model';
import { TableWidgetBaseModel } from './table-widget-base.model';
import { TableWidgetCheckboxFilterModel } from './table-widget-checkbox-filter-model';
import { SpecificationBackedTableColumnDef } from './table-widget-column.model';
import { TableWidgetSelectFilterModel } from './table-widget-select-filter.model';
import { TableWidgetViewModel } from './table-widget-view.model';
import { TableWidgetModel } from './table-widget.model';

@Model({
type: 'mode-toggle-table-widget',
displayName: 'Mode Toggle Table Widget'
type: 'table-widget-view-toggle'
})
export class ModeToggleTableWidgetModel extends TableWidgetBaseModel {
export class TableWidgetViewToggleModel extends TableWidgetBaseModel {
@ModelProperty({
key: 'modeOptions',
displayName: 'Modes Toggle Options',
key: 'views',
// tslint:disable-next-line: no-object-literal-type-assertion
type: {
key: ARRAY_PROPERTY.type,
subtype: {
key: ENUM_TYPE.type,
values: [TableMode.Flat, TableMode.Tree, TableMode.Detail]
key: ModelPropertyType.TYPE,
defaultModelClass: TableWidgetViewModel
}
} as ArrayPropertyTypeInstance
})
public modeOptions: TableMode[] = [];

@ModelProperty({
key: 'flat',
type: ModelTemplatePropertyType.TYPE
})
public flat!: ModelJson;

@ModelProperty({
key: 'tree',
type: ModelTemplatePropertyType.TYPE
})
public tree!: ModelJson;

@ModelProperty({
key: 'detail',
type: ModelTemplatePropertyType.TYPE
})
public detail!: ModelJson;
public views: TableWidgetViewModel[] = [];

@ModelInject(MODEL_API)
protected readonly api!: ModelApi;

private delegateModel?: TableWidgetModel;

public setMode(mode: TableMode): void {
public setView(view: string): void {
if (this.delegateModel) {
this.api.destroyChild(this.delegateModel);
}
this.delegateModel = this.createDelegate(mode);
this.delegateModel = this.createDelegate(view);
}

public getModeOptions(): TableMode[] {
return this.modeOptions;
public getViewOptions(): string[] {
return this.views.map(v => v.view);
}

private createDelegate(mode: TableMode): TableWidgetModel | undefined {
switch (mode) {
case TableMode.Detail:
return this.api.createChild<TableWidgetModel>(this.detail);
case TableMode.Tree:
return this.api.createChild<TableWidgetModel>(this.tree);
case TableMode.Flat:
return this.api.createChild<TableWidgetModel>(this.flat);
default:
return undefined;
}
private createDelegate(view: string): TableWidgetModel | undefined {
const found = this.views.find(v => v.view.toLowerCase() === view.toLowerCase());

return found ? this.api.createChild<TableWidgetModel>(found.template) : undefined;
}

public getData(): Observable<TableDataSource<TableRow>> {
Expand All @@ -93,7 +68,15 @@ export class ModeToggleTableWidgetModel extends TableWidgetBaseModel {
: [];
}

public getSearchAttribute(): string | undefined {
return this.delegateModel?.getSearchAttribute() ?? this.searchAttribute;
}

public getCheckboxFilterOption(): TableWidgetCheckboxFilterModel | undefined {
return this.delegateModel?.getCheckboxFilterOption() ?? this.checkboxFilterOption;
}

public getSelectFilterOptions(): TableWidgetSelectFilterModel[] {
return this.delegateModel?.getSelectFilterOptions() ?? this.selectFilterOptions;
}
}
Loading